From 64dfefc9e7e1abc02b483022635457910302d053 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:00:24 +0200 Subject: [PATCH 001/155] Remove usage of PublicKeyAlgorithm.EC --- pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java | 1 - .../pgpainless/key/modification/RefuseToAddWeakSubkeyTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java b/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java index 6da94bf0..35787138 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java +++ b/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java @@ -645,7 +645,6 @@ public final class Policy { minimalBitStrengths.put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000); // §7.2.2 minimalBitStrengths.put(PublicKeyAlgorithm.ECDH, 250); - minimalBitStrengths.put(PublicKeyAlgorithm.EC, 250); return new PublicKeyAlgorithmPolicy(minimalBitStrengths); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/RefuseToAddWeakSubkeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/RefuseToAddWeakSubkeyTest.java index e129e4be..f49a6271 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/RefuseToAddWeakSubkeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/RefuseToAddWeakSubkeyTest.java @@ -72,7 +72,6 @@ public class RefuseToAddWeakSubkeyTest { minimalBitStrengths.put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000); // §7.2.2 minimalBitStrengths.put(PublicKeyAlgorithm.ECDH, 250); - minimalBitStrengths.put(PublicKeyAlgorithm.EC, 250); PGPainless.getPolicy().setPublicKeyAlgorithmPolicy(new Policy.PublicKeyAlgorithmPolicy(minimalBitStrengths)); SecretKeyRingEditorInterface editor = PGPainless.modifyKeyRing(secretKeys); From c9dce319f8dcf377f496fcbbdced1ebe098d9097 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:01:12 +0200 Subject: [PATCH 002/155] Prepare for Kotlin conversion --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index d04b6345..a0f2d0e4 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,7 @@ buildscript { plugins { id 'ru.vyarus.animalsniffer' version '1.5.3' + id 'org.jetbrains.kotlin.jvm' version "1.8.10" } apply from: 'version.gradle' @@ -29,6 +30,7 @@ allprojects { apply plugin: 'eclipse' apply plugin: 'jacoco' apply plugin: 'checkstyle' + apply plugin: 'kotlin' compileJava { options.release = 8 From 94ad4cfbe7da3ed8925b80be8a40c025eab15776 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:01:54 +0200 Subject: [PATCH 003/155] Kotlin conversion: AEADAlgorithm --- .../pgpainless/algorithm/AEADAlgorithm.java | 100 ------------------ .../org/pgpainless/algorithm/AEADAlgorithm.kt | 30 ++++++ 2 files changed, 30 insertions(+), 100 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.java deleted file mode 100644 index a5885005..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.java +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; - -/** - * List of AEAD algorithms defined in crypto-refresh-06. - * - * @see - * Crypto-Refresh-06 §9.6 - AEAD Algorithms - */ -public enum AEADAlgorithm { - - EAX(1, 16, 16), - OCB(2, 15, 16), - GCM(3, 12, 16), - ; - - private final int algorithmId; - private final int ivLength; - private final int tagLength; - - private static final Map MAP = new HashMap<>(); - - static { - for (AEADAlgorithm h : AEADAlgorithm.values()) { - MAP.put(h.algorithmId, h); - } - } - - AEADAlgorithm(int id, int ivLength, int tagLength) { - this.algorithmId = id; - this.ivLength = ivLength; - this.tagLength = tagLength; - } - - /** - * Return the ID of the AEAD algorithm. - * - * @return algorithm ID - */ - public int getAlgorithmId() { - return algorithmId; - } - - /** - * Return the length (in octets) of the IV. - * - * @return iv length - */ - public int getIvLength() { - return ivLength; - } - - /** - * Return the length (in octets) of the authentication tag. - * - * @return tag length - */ - public int getTagLength() { - return tagLength; - } - - /** - * Return the {@link AEADAlgorithm} value that corresponds to the provided algorithm id. - * If an invalid algorithm id was provided, null is returned. - * - * @param id numeric id - * @return enum value - */ - @Nullable - public static AEADAlgorithm fromId(int id) { - return MAP.get(id); - } - - /** - * Return the {@link AEADAlgorithm} value that corresponds to the provided algorithm id. - * If an invalid algorithm id was provided, throw a {@link NoSuchElementException}. - * - * @param id algorithm id - * @return enum value - * @throws NoSuchElementException in case of an unknown algorithm id - */ - @Nonnull - public static AEADAlgorithm requireFromId(int id) { - AEADAlgorithm algorithm = fromId(id); - if (algorithm == null) { - throw new NoSuchElementException("No AEADAlgorithm found for id " + id); - } - return algorithm; - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.kt new file mode 100644 index 00000000..61672122 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.kt @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +enum class AEADAlgorithm( + val algorithmId: Int, + val ivLength: Int, + val tagLength: Int) { + EAX(1, 16, 16), + OCB(2, 15, 16), + GCM(3, 12, 16), + ; + + companion object { + @JvmStatic + fun fromId(id: Int): AEADAlgorithm? { + return values().firstOrNull { + algorithm -> algorithm.algorithmId == id + } + } + + @JvmStatic + fun requireFromId(id: Int): AEADAlgorithm { + return fromId(id) ?: + throw NoSuchElementException("No AEADAlgorithm found for id $id") + } + } +} \ No newline at end of file From 644700c8eefaec407ed267a7bbc3babcb6fafd7d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:02:13 +0200 Subject: [PATCH 004/155] Kotlin conversion: CertificationType --- ...ficationType.java => CertificationType.kt} | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) rename pgpainless-core/src/main/java/org/pgpainless/algorithm/{CertificationType.java => CertificationType.kt} (63%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.kt similarity index 63% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.java rename to pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.kt index f5c8ec7e..33025ad6 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.java +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.kt @@ -1,16 +1,12 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.algorithm; - -import javax.annotation.Nonnull; - -/** - * Subset of {@link SignatureType}, reduced to certification types. - */ -public enum CertificationType { +package org.pgpainless.algorithm +enum class CertificationType( + val signatureType: SignatureType +) { /** * The issuer of this certification does not make any particular assertion as to how well the certifier has * checked that the owner of the key is in fact the person described by the User ID. @@ -34,13 +30,5 @@ public enum CertificationType { POSITIVE(SignatureType.POSITIVE_CERTIFICATION), ; - private final SignatureType signatureType; - - CertificationType(@Nonnull SignatureType signatureType) { - this.signatureType = signatureType; - } - - public @Nonnull SignatureType asSignatureType() { - return signatureType; - } -} + fun asSignatureType() = signatureType +} \ No newline at end of file From dfb33a5ecbbf7c5d9ca13575ca15b26f3d56232a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:02:37 +0200 Subject: [PATCH 005/155] Kotlin conversion: CompressionAlgorithm --- .../algorithm/CompressionAlgorithm.java | 79 ------------------- .../algorithm/CompressionAlgorithm.kt | 50 ++++++++++++ 2 files changed, 50 insertions(+), 79 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.java deleted file mode 100644 index a2e78c5f..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.java +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; - -import org.bouncycastle.bcpg.CompressionAlgorithmTags; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Enumeration of possible compression algorithms. - * - * @see RFC4880: Compression Algorithm Tags - */ -public enum CompressionAlgorithm { - - UNCOMPRESSED (CompressionAlgorithmTags.UNCOMPRESSED), - ZIP (CompressionAlgorithmTags.ZIP), - ZLIB (CompressionAlgorithmTags.ZLIB), - BZIP2 (CompressionAlgorithmTags.BZIP2), - ; - - private static final Map MAP = new ConcurrentHashMap<>(); - - static { - for (CompressionAlgorithm c : CompressionAlgorithm.values()) { - MAP.put(c.algorithmId, c); - } - } - - /** - * Return the {@link CompressionAlgorithm} value that corresponds to the provided numerical id. - * If an invalid id is provided, null is returned. - * - * @param id id - * @return compression algorithm - */ - @Nullable - public static CompressionAlgorithm fromId(int id) { - return MAP.get(id); - } - - /** - * Return the {@link CompressionAlgorithm} value that corresponds to the provided numerical id. - * If an invalid id is provided, thrown an {@link NoSuchElementException}. - * - * @param id id - * @return compression algorithm - * @throws NoSuchElementException in case of an unmapped id - */ - @Nonnull - public static CompressionAlgorithm requireFromId(int id) { - CompressionAlgorithm algorithm = fromId(id); - if (algorithm == null) { - throw new NoSuchElementException("No CompressionAlgorithm found for id " + id); - } - return algorithm; - } - - private final int algorithmId; - - CompressionAlgorithm(int id) { - this.algorithmId = id; - } - - /** - * Return the numerical algorithm tag corresponding to this compression algorithm. - * @return id - */ - public int getAlgorithmId() { - return algorithmId; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.kt new file mode 100644 index 00000000..73179722 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.kt @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +/** + * Enumeration of possible compression algorithms. + * + * See also [RFC4880 - Compression Algorithm Tags](https://tools.ietf.org/html/rfc4880#section-9.3) + */ +enum class CompressionAlgorithm(val algorithmId: Int) { + + UNCOMPRESSED(0), + ZIP(1), + ZLIB(2), + BZIP2(3), + ; + + companion object { + + /** + * Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. + * If an invalid id is provided, null is returned. + * + * @param id id + * @return compression algorithm + */ + @JvmStatic + fun fromId(id: Int): CompressionAlgorithm? { + return values().firstOrNull { + c -> c.algorithmId == id + } + } + + /** + * Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. + * If an invalid id is provided, throw an [NoSuchElementException]. + * + * @param id id + * @return compression algorithm + * @throws NoSuchElementException in case of an unmapped id + */ + @JvmStatic + fun requireFromId(id: Int): CompressionAlgorithm { + return fromId(id) ?: + throw NoSuchElementException("No CompressionAlgorithm found for id $id") + } + } +} \ No newline at end of file From e9dceb4553642c027e3d45336e9de0882b6677f8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:02:59 +0200 Subject: [PATCH 006/155] Kotlin conversion: DocumentSignatureType --- .../algorithm/DocumentSignatureType.java | 35 ------------------- .../algorithm/DocumentSignatureType.kt | 20 +++++++++++ 2 files changed, 20 insertions(+), 35 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.java deleted file mode 100644 index 4dbc58da..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.java +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -/** - * Subset of {@link SignatureType}, used for signatures over documents. - */ -public enum DocumentSignatureType { - - /** - * Signature is calculated over the unchanged binary data. - */ - BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT), - - /** - * The signature is calculated over the text data with its line endings converted to - *
-     *     {@code <CR><LF>}
-     * 
. - */ - CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT), - ; - - final SignatureType signatureType; - - DocumentSignatureType(SignatureType signatureType) { - this.signatureType = signatureType; - } - - public SignatureType getSignatureType() { - return signatureType; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.kt new file mode 100644 index 00000000..b3cd17ac --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.kt @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +enum class DocumentSignatureType(val signatureType: SignatureType) { + + /** + * Signature is calculated over the unchanged binary data. + */ + BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT), + + /** + * The signature is calculated over the text data with its line endings + * converted to ``. + */ + CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT), + ; +} \ No newline at end of file From eb94aa6063a059be5a26ffd88c3ddc820f0e741b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:03:22 +0200 Subject: [PATCH 007/155] Kotlin conversion: EncryptionPurpose --- .../{EncryptionPurpose.java => EncryptionPurpose.kt} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename pgpainless-core/src/main/java/org/pgpainless/algorithm/{EncryptionPurpose.java => EncryptionPurpose.kt} (74%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/EncryptionPurpose.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/EncryptionPurpose.kt similarity index 74% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/EncryptionPurpose.java rename to pgpainless-core/src/main/java/org/pgpainless/algorithm/EncryptionPurpose.kt index 5eda30c0..f7e5ce2d 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/EncryptionPurpose.java +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/EncryptionPurpose.kt @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.algorithm; +package org.pgpainless.algorithm -public enum EncryptionPurpose { +enum class EncryptionPurpose { /** * The stream will encrypt communication that goes over the wire. * E.g. EMail, Chat... @@ -19,4 +19,4 @@ public enum EncryptionPurpose { * The stream will use keys with either flags to encrypt the data. */ ANY -} +} \ No newline at end of file From 98e9c0934d608fa33881092161b9dc09ea97826c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:03:43 +0200 Subject: [PATCH 008/155] Kotlin conversion: Feature --- .../org/pgpainless/algorithm/Feature.java | 152 ------------------ .../java/org/pgpainless/algorithm/Feature.kt | 83 ++++++++++ 2 files changed, 83 insertions(+), 152 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.java deleted file mode 100644 index d9aa9b90..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.java +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; - -import org.bouncycastle.bcpg.sig.Features; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * An enumeration of features that may be set in the {@link Features} subpacket. - * - * @see RFC4880: Features - */ -public enum Feature { - - /** - * Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification - * Detection Code Packets. - * - * @see - * RFC-4880 §5.14: Modification Detection Code Packet - */ - MODIFICATION_DETECTION(Features.FEATURE_MODIFICATION_DETECTION), - - /** - * Support for Authenticated Encryption with Additional Data (AEAD). - * If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets. - * - * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! - * NOTE: This value is currently RESERVED. - * - * @see - * AEAD Encrypted Data Packet - */ - GNUPG_AEAD_ENCRYPTED_DATA(Features.FEATURE_AEAD_ENCRYPTED_DATA), - - /** - * If a key announces this feature, it is a version 5 public key. - * The version 5 format is similar to the version 4 format except for the addition of a count for the key material. - * This count helps to parse secret key packets (which are an extension of the public key packet format) in the case - * of an unknown algorithm. - * In addition, fingerprints of version 5 keys are calculated differently from version 4 keys. - * - * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! - * NOTE: This value is currently RESERVED. - * - * @see - * Public-Key Packet Formats - */ - GNUPG_VERSION_5_PUBLIC_KEY(Features.FEATURE_VERSION_5_PUBLIC_KEY), - - /** - * Support for Symmetrically Encrypted Integrity Protected Data packet version 2. - * - * @see - * crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format - */ - MODIFICATION_DETECTION_2((byte) 0x08), - ; - - private static final Map MAP = new ConcurrentHashMap<>(); - - static { - for (Feature f : Feature.values()) { - MAP.put(f.featureId, f); - } - } - - /** - * Return the {@link Feature} encoded by the given id. - * If the id does not match any known features, return null. - * - * @param id feature id - * @return feature - */ - @Nullable - public static Feature fromId(byte id) { - return MAP.get(id); - } - - /** - * Return the {@link Feature} encoded by the given id. - * If the id does not match any known features, throw an {@link NoSuchElementException}. - * - * @param id feature id - * @return feature - * @throws NoSuchElementException if an unmatched feature id is encountered - */ - @Nonnull - public static Feature requireFromId(byte id) { - Feature feature = fromId(id); - if (feature == null) { - throw new NoSuchElementException("Unknown feature id encountered: " + id); - } - return feature; - } - - private final byte featureId; - - Feature(byte featureId) { - this.featureId = featureId; - } - - /** - * Return the id of the feature. - * - * @return feature id - */ - public byte getFeatureId() { - return featureId; - } - - /** - * Convert a bitmask into a list of {@link KeyFlag KeyFlags}. - * - * @param bitmask bitmask - * @return list of key flags encoded by the bitmask - */ - @Nonnull - public static List fromBitmask(int bitmask) { - List features = new ArrayList<>(); - for (Feature f : Feature.values()) { - if ((bitmask & f.featureId) != 0) { - features.add(f); - } - } - return features; - } - - /** - * Encode a list of {@link KeyFlag KeyFlags} into a bitmask. - * - * @param features list of flags - * @return bitmask - */ - public static byte toBitmask(Feature... features) { - byte mask = 0; - for (Feature f : features) { - mask |= f.featureId; - } - return mask; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.kt new file mode 100644 index 00000000..2e0058b5 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.kt @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +/** + * An enumeration of features that may be set in the feature subpacket. + * + * See [RFC4880: Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24) + */ +enum class Feature(val featureId: Byte) { + + /** + * Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification + * Detection Code Packets. + * + * See [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14) + */ + MODIFICATION_DETECTION(0x01), + + /** + * Support for Authenticated Encryption with Additional Data (AEAD). + * If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets. + * + * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! + * NOTE: This value is currently RESERVED. + * + * See [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-) + */ + GNUPG_AEAD_ENCRYPTED_DATA(0x02), + + /** + * If a key announces this feature, it is a version 5 public key. + * The version 5 format is similar to the version 4 format except for the addition of a count for the key material. + * This count helps to parse secret key packets (which are an extension of the public key packet format) in the case + * of an unknown algorithm. + * In addition, fingerprints of version 5 keys are calculated differently from version 4 keys. + * + * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! + * NOTE: This value is currently RESERVED. + * + * See [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats) + */ + GNUPG_VERSION_5_PUBLIC_KEY(0x04), + + /** + * Support for Symmetrically Encrypted Integrity Protected Data packet version 2. + * + * See [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd) + */ + MODIFICATION_DETECTION_2(0x08), + ; + + companion object { + @JvmStatic + fun fromId(id: Byte): Feature? { + return values().firstOrNull { + f -> f.featureId == id + } + } + + @JvmStatic + fun requireFromId(id: Byte): Feature { + return fromId(id) ?: + throw NoSuchElementException("Unknown feature id encountered: $id") + } + + @JvmStatic + fun fromBitmask(bitmask: Int): List { + return values().filter { + it.featureId.toInt() and bitmask != 0 + } + } + + @JvmStatic + fun toBitmask(vararg features: Feature): Byte { + return features.map { it.featureId.toInt() } + .reduceOrNull { mask, f -> mask or f }?.toByte() + ?: 0 + } + } +} \ No newline at end of file From 2c384907424bad3a9081fb2e6bcac7aad3e20c1a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:03:59 +0200 Subject: [PATCH 009/155] Kotlin conversion: HashAlgorithm --- .../pgpainless/algorithm/HashAlgorithm.java | 118 ------------------ .../org/pgpainless/algorithm/HashAlgorithm.kt | 76 +++++++++++ 2 files changed, 76 insertions(+), 118 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.java deleted file mode 100644 index 0b9368bb..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.java +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; - -import org.bouncycastle.bcpg.HashAlgorithmTags; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * An enumeration of different hashing algorithms. - * - * @see RFC4880: Hash Algorithms - */ -public enum HashAlgorithm { - @Deprecated - MD5 (HashAlgorithmTags.MD5, "MD5"), - SHA1 (HashAlgorithmTags.SHA1, "SHA1"), - RIPEMD160 (HashAlgorithmTags.RIPEMD160, "RIPEMD160"), - SHA256 (HashAlgorithmTags.SHA256, "SHA256"), - SHA384 (HashAlgorithmTags.SHA384, "SHA384"), - SHA512 (HashAlgorithmTags.SHA512, "SHA512"), - SHA224 (HashAlgorithmTags.SHA224, "SHA224"), - SHA3_256 (12, "SHA3-256"), - SHA3_512 (14, "SHA3-512"), - ; - - private static final Map ID_MAP = new HashMap<>(); - private static final Map NAME_MAP = new HashMap<>(); - - static { - for (HashAlgorithm h : HashAlgorithm.values()) { - ID_MAP.put(h.algorithmId, h); - NAME_MAP.put(h.name, h); - } - } - - /** - * Return the {@link HashAlgorithm} value that corresponds to the provided algorithm id. - * If an invalid algorithm id was provided, null is returned. - * - * @param id numeric id - * @return enum value - */ - @Nullable - public static HashAlgorithm fromId(int id) { - return ID_MAP.get(id); - } - - /** - * Return the {@link HashAlgorithm} value that corresponds to the provided algorithm id. - * If an invalid algorithm id was provided, throw a {@link NoSuchElementException}. - * - * @param id algorithm id - * @return enum value - * @throws NoSuchElementException in case of an unknown algorithm id - */ - @Nonnull - public static HashAlgorithm requireFromId(int id) { - HashAlgorithm algorithm = fromId(id); - if (algorithm == null) { - throw new NoSuchElementException("No HashAlgorithm found for id " + id); - } - return algorithm; - } - - /** - * Return the {@link HashAlgorithm} value that corresponds to the provided name. - * If an invalid algorithm name was provided, null is returned. - * - * @see RFC4880: §9.4 Hash Algorithms - * for a list of algorithms and names. - * - * @param name text name - * @return enum value - */ - @Nullable - public static HashAlgorithm fromName(String name) { - String algorithmName = name.toUpperCase(); - HashAlgorithm algorithm = NAME_MAP.get(algorithmName); - if (algorithm == null) { - algorithm = NAME_MAP.get(algorithmName.replace("-", "")); - } - return algorithm; - } - - private final int algorithmId; - private final String name; - - HashAlgorithm(int id, String name) { - this.algorithmId = id; - this.name = name; - } - - /** - * Return the numeric algorithm id of the hash algorithm. - * - * @return numeric id - */ - public int getAlgorithmId() { - return algorithmId; - } - - /** - * Return the text name of the hash algorithm. - * - * @return text name - */ - public String getAlgorithmName() { - return name; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.kt new file mode 100644 index 00000000..9c433efe --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +/** + * An enumeration of different hashing algorithms. + * + * See [RFC4880: Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-9.4) + */ +enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) { + + @Deprecated("MD5 is deprecated") + MD5 (1, "MD5"), + SHA1 (2, "SHA1"), + RIPEMD160 (3, "RIPEMD160"), + SHA256 (8, "SHA256"), + SHA384 (9, "SHA384"), + SHA512 (10, "SHA512"), + SHA224 (11, "SHA224"), + SHA3_256 (12, "SHA3-256"), + SHA3_512 (14, "SHA3-512"), + ; + + companion object { + /** + * Return the [HashAlgorithm] value that corresponds to the provided algorithm id. + * If an invalid algorithm id was provided, null is returned. + * + * @param id numeric id + * @return enum value + */ + @JvmStatic + fun fromId(id: Int): HashAlgorithm? { + return values().firstOrNull { + h -> h.algorithmId == id + } + } + + /** + * Return the [HashAlgorithm] value that corresponds to the provided algorithm id. + * If an invalid algorithm id was provided, throw a [NoSuchElementException]. + * + * @param id algorithm id + * @return enum value + * @throws NoSuchElementException in case of an unknown algorithm id + */ + @JvmStatic + fun requireFromId(id: Int): HashAlgorithm { + return fromId(id) ?: + throw NoSuchElementException("No HashAlgorithm found for id $id") + } + + /** + * Return the [HashAlgorithm] value that corresponds to the provided name. + * If an invalid algorithm name was provided, null is returned. + * + * See [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4) + * for a list of algorithms and names. + * + * @param name text name + * @return enum value + */ + @JvmStatic + fun fromName(name: String): HashAlgorithm? { + return name.uppercase().let { algoName -> + values().firstOrNull { + it.algorithmName == algoName + } ?: values().firstOrNull { + it.algorithmName == algoName.replace("-", "") + } + } + } + } +} \ No newline at end of file From 294c469a2940974353b0966c47ea1c363dbdb066 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Oct 2023 12:24:40 +0200 Subject: [PATCH 010/155] Kotlin conversion: KeyFlag --- .../org/pgpainless/algorithm/KeyFlag.java | 121 ------------------ .../java/org/pgpainless/algorithm/KeyFlag.kt | 92 +++++++++++++ 2 files changed, 92 insertions(+), 121 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.java deleted file mode 100644 index 149ef9d0..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.java +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import java.util.ArrayList; -import java.util.List; - -import org.bouncycastle.bcpg.sig.KeyFlags; - -/** - * Enumeration of different key flags. - * Key flags denote different capabilities of a key pair. - * - * @see RFC4880: Key Flags - */ -public enum KeyFlag { - - /** - * This key may be used to certify third-party keys. - */ - CERTIFY_OTHER (KeyFlags.CERTIFY_OTHER), - - /** - * This key may be used to sign data. - */ - SIGN_DATA (KeyFlags.SIGN_DATA), - - /** - * This key may be used to encrypt communications. - */ - ENCRYPT_COMMS (KeyFlags.ENCRYPT_COMMS), - - /** - * This key may be used to encrypt storage. - */ - ENCRYPT_STORAGE(KeyFlags.ENCRYPT_STORAGE), - - /** - * The private component of this key may have been split by a secret-sharing mechanism. - */ - SPLIT (KeyFlags.SPLIT), - - /** - * This key may be used for authentication. - */ - AUTHENTICATION (KeyFlags.AUTHENTICATION), - - /** - * The private component of this key may be in the possession of more than one person. - */ - SHARED (KeyFlags.SHARED), - ; - - private final int flag; - - KeyFlag(int flag) { - this.flag = flag; - } - - /** - * Return the numeric id of the {@link KeyFlag}. - * - * @return numeric id - */ - public int getFlag() { - return flag; - } - - /** - * Convert a bitmask into a list of {@link KeyFlag KeyFlags}. - * - * @param bitmask bitmask - * @return list of key flags encoded by the bitmask - */ - public static List fromBitmask(int bitmask) { - List flags = new ArrayList<>(); - for (KeyFlag f : KeyFlag.values()) { - if ((bitmask & f.flag) != 0) { - flags.add(f); - } - } - return flags; - } - - /** - * Encode a list of {@link KeyFlag KeyFlags} into a bitmask. - * - * @param flags list of flags - * @return bitmask - */ - public static int toBitmask(KeyFlag... flags) { - int mask = 0; - for (KeyFlag f : flags) { - mask |= f.getFlag(); - } - return mask; - } - - /** - * Return true if the provided bitmask has the bit for the provided flag set. - * Return false if the mask does not contain the flag. - * - * @param mask bitmask - * @param flag flag to be tested for - * @return true if flag is set, false otherwise - */ - public static boolean hasKeyFlag(int mask, KeyFlag flag) { - return (mask & flag.getFlag()) == flag.getFlag(); - } - - public static boolean containsAny(int mask, KeyFlag... flags) { - for (KeyFlag flag : flags) { - if (hasKeyFlag(mask, flag)) { - return true; - } - } - return false; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.kt new file mode 100644 index 00000000..d8686cd6 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.kt @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +enum class KeyFlag(val flag: Int) { + + /** + * This key may be used to certify third-party keys. + */ + CERTIFY_OTHER (1), + + /** + * This key may be used to sign data. + */ + SIGN_DATA (2), + + /** + * This key may be used to encrypt communications. + */ + ENCRYPT_COMMS (4), + + /** + * This key may be used to encrypt storage. + */ + ENCRYPT_STORAGE(8), + + /** + * The private component of this key may have been split by a secret-sharing mechanism. + */ + SPLIT (16), + + /** + * This key may be used for authentication. + */ + AUTHENTICATION (32), + + /** + * The private component of this key may be in the possession of more than one person. + */ + SHARED (128), + ; + + companion object { + + /** + * Convert a bitmask into a list of [KeyFlags][KeyFlag]. + * + * @param bitmask bitmask + * @return list of key flags encoded by the bitmask + */ + @JvmStatic + fun fromBitmask(bitmask: Int): List { + return values().filter { + it.flag and bitmask != 0 + } + } + + /** + * Encode a list of {@link KeyFlag KeyFlags} into a bitmask. + * + * @param flags list of flags + * @return bitmask + */ + @JvmStatic + fun toBitmask(vararg flags: KeyFlag): Int { + return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f } + ?: 0 + } + + /** + * Return true if the provided bitmask has the bit for the provided flag set. + * Return false if the mask does not contain the flag. + * + * @param mask bitmask + * @param flag flag to be tested for + * @return true if flag is set, false otherwise + */ + @JvmStatic + fun hasKeyFlag(mask: Int, flag: KeyFlag): Boolean { + return mask and flag.flag == flag.flag + } + + @JvmStatic + fun containsAny(mask: Int, vararg flags: KeyFlag): Boolean { + return flags.any { + hasKeyFlag(mask, it) + } + } + } +} \ No newline at end of file From eb07b94bcb2fab1cc633d8e412ca5fccde9676c7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:04:38 +0200 Subject: [PATCH 011/155] Kotlin conversion: OpenPgpPacket --- .../pgpainless/algorithm/OpenPgpPacket.java | 71 ------------------- .../org/pgpainless/algorithm/OpenPgpPacket.kt | 46 ++++++++++++ 2 files changed, 46 insertions(+), 71 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.java deleted file mode 100644 index 41e3fb08..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.java +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import org.bouncycastle.bcpg.PacketTags; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; - -public enum OpenPgpPacket { - PKESK(PacketTags.PUBLIC_KEY_ENC_SESSION), - SIG(PacketTags.SIGNATURE), - SKESK(PacketTags.SYMMETRIC_KEY_ENC_SESSION), - OPS(PacketTags.ONE_PASS_SIGNATURE), - SK(PacketTags.SECRET_KEY), - PK(PacketTags.PUBLIC_KEY), - SSK(PacketTags.SECRET_SUBKEY), - COMP(PacketTags.COMPRESSED_DATA), - SED(PacketTags.SYMMETRIC_KEY_ENC), - MARKER(PacketTags.MARKER), - LIT(PacketTags.LITERAL_DATA), - TRUST(PacketTags.TRUST), - UID(PacketTags.USER_ID), - PSK(PacketTags.PUBLIC_SUBKEY), - UATTR(PacketTags.USER_ATTRIBUTE), - SEIPD(PacketTags.SYM_ENC_INTEGRITY_PRO), - MDC(PacketTags.MOD_DETECTION_CODE), - - EXP_1(PacketTags.EXPERIMENTAL_1), - EXP_2(PacketTags.EXPERIMENTAL_2), - EXP_3(PacketTags.EXPERIMENTAL_3), - EXP_4(PacketTags.EXPERIMENTAL_4), - ; - - static final Map MAP = new HashMap<>(); - - static { - for (OpenPgpPacket p : OpenPgpPacket.values()) { - MAP.put(p.getTag(), p); - } - } - - final int tag; - - @Nullable - public static OpenPgpPacket fromTag(int tag) { - return MAP.get(tag); - } - - @Nonnull - public static OpenPgpPacket requireFromTag(int tag) { - OpenPgpPacket p = fromTag(tag); - if (p == null) { - throw new NoSuchElementException("No OpenPGP packet known for tag " + tag); - } - return p; - } - - OpenPgpPacket(int tag) { - this.tag = tag; - } - - int getTag() { - return tag; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.kt new file mode 100644 index 00000000..f05599bd --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.kt @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +enum class OpenPgpPacket(val tag: Int) { + PKESK(1), + SIG(2), + SKESK(3), + OPS(4), + SK(5), + PK(6), + SSK(7), + COMP(8), + SED(9), + MARKER(10), + LIT(11), + TRUST(12), + UID(13), + PSK(14), + UATTR(17), + SEIPD(18), + MDC(19), + + EXP_1(60), + EXP_2(61), + EXP_3(62), + EXP_4(63), + ; + + companion object { + @JvmStatic + fun fromTag(tag: Int): OpenPgpPacket? { + return values().firstOrNull { + it.tag == tag + } + } + + @JvmStatic + fun requireFromTag(tag: Int): OpenPgpPacket { + return fromTag(tag) ?: + throw NoSuchElementException("No OpenPGP packet known for tag $tag") + } + } +} \ No newline at end of file From 608edc2378e502a08136214574a432429578ca9c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:04:56 +0200 Subject: [PATCH 012/155] Kotlin conversion: PublicKeyAlgorithm --- .../algorithm/PublicKeyAlgorithm.java | 163 ------------------ .../algorithm/PublicKeyAlgorithm.kt | 96 +++++++++++ 2 files changed, 96 insertions(+), 163 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.java deleted file mode 100644 index c7599db9..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.java +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; - -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Enumeration of public key algorithms as defined in RFC4880. - * - * @see RFC4880: Public-Key Algorithms - */ -public enum PublicKeyAlgorithm { - - /** - * RSA capable of encryption and signatures. - */ - RSA_GENERAL (PublicKeyAlgorithmTags.RSA_GENERAL, true, true), - - /** - * RSA with usage encryption. - * - * @deprecated see Deprecation notice - */ - @Deprecated - RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT, false, true), - - /** - * RSA with usage of creating signatures. - * - * @deprecated see Deprecation notice - */ - @Deprecated - RSA_SIGN (PublicKeyAlgorithmTags.RSA_SIGN, true, false), - - /** - * ElGamal with usage encryption. - */ - ELGAMAL_ENCRYPT (PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, false, true), - - /** - * Digital Signature Algorithm. - */ - DSA (PublicKeyAlgorithmTags.DSA, true, false), - - /** - * EC is deprecated. - * @deprecated use {@link #ECDH} instead. - */ - @Deprecated - EC (PublicKeyAlgorithmTags.EC, false, true), - - /** - * Elliptic Curve Diffie-Hellman. - */ - ECDH (PublicKeyAlgorithmTags.ECDH, false, true), - - /** - * Elliptic Curve Digital Signature Algorithm. - */ - ECDSA (PublicKeyAlgorithmTags.ECDSA, true, false), - - /** - * ElGamal General. - * - * @deprecated see Deprecation notice - */ - @Deprecated - ELGAMAL_GENERAL (PublicKeyAlgorithmTags.ELGAMAL_GENERAL, true, true), - - /** - * Diffie-Hellman key exchange algorithm. - */ - DIFFIE_HELLMAN (PublicKeyAlgorithmTags.DIFFIE_HELLMAN, false, true), - - /** - * Digital Signature Algorithm based on twisted Edwards Curves. - */ - EDDSA (PublicKeyAlgorithmTags.EDDSA, true, false), - ; - - private static final Map MAP = new ConcurrentHashMap<>(); - - static { - for (PublicKeyAlgorithm p : PublicKeyAlgorithm.values()) { - MAP.put(p.algorithmId, p); - } - } - - /** - * Return the {@link PublicKeyAlgorithm} that corresponds to the provided algorithm id. - * If an invalid id is provided, null is returned. - * - * @param id numeric algorithm id - * @return algorithm or null - */ - @Nullable - public static PublicKeyAlgorithm fromId(int id) { - return MAP.get(id); - } - - /** - * Return the {@link PublicKeyAlgorithm} that corresponds to the provided algorithm id. - * If an invalid id is provided, throw a {@link NoSuchElementException}. - * - * @param id numeric algorithm id - * @return algorithm - * @throws NoSuchElementException in case of an unmatched algorithm id - */ - @Nonnull - public static PublicKeyAlgorithm requireFromId(int id) { - PublicKeyAlgorithm algorithm = fromId(id); - if (algorithm == null) { - throw new NoSuchElementException("No PublicKeyAlgorithm found for id " + id); - } - return algorithm; - } - - private final int algorithmId; - private final boolean signingCapable; - private final boolean encryptionCapable; - - PublicKeyAlgorithm(int algorithmId, boolean signingCapable, boolean encryptionCapable) { - this.algorithmId = algorithmId; - this.signingCapable = signingCapable; - this.encryptionCapable = encryptionCapable; - } - - /** - * Return the numeric identifier of the public key algorithm. - * - * @return id - */ - public int getAlgorithmId() { - return algorithmId; - } - - /** - * Return true if this public key algorithm is able to create signatures. - * - * @return true if the algorithm can sign - */ - public boolean isSigningCapable() { - return signingCapable; - } - - /** - * Return true if this public key algorithm can be used as an encryption algorithm. - * - * @return true if the algorithm can encrypt - */ - public boolean isEncryptionCapable() { - return encryptionCapable; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.kt new file mode 100644 index 00000000..9fad8e7d --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.kt @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +/** + * Enumeration of public key algorithms as defined in RFC4880. + * + * See [RFC4880: Public-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.1) + */ +enum class PublicKeyAlgorithm( + val algorithmId: Int, + val signingCapable: Boolean, + val encryptionCapable: Boolean) { + + /** + * RSA capable of encryption and signatures. + */ + RSA_GENERAL (1, true, true), + + /** + * RSA with usage encryption. + * + * @deprecated see Deprecation notice + */ + @Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL", + ReplaceWith("RSA_GENERAL")) + RSA_ENCRYPT (2, false, true), + + /** + * RSA with usage of creating signatures. + * + * @deprecated see Deprecation notice + */ + @Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL", + ReplaceWith("RSA_GENERAL")) + RSA_SIGN (3, true, false), + + /** + * ElGamal with usage encryption. + */ + ELGAMAL_ENCRYPT (16, false, true), + + /** + * Digital Signature Algorithm. + */ + DSA (17, true, false), + + /** + * Elliptic Curve Diffie-Hellman. + */ + ECDH (18, false, true), + + /** + * Elliptic Curve Digital Signature Algorithm. + */ + ECDSA (19, true, false), + + /** + * ElGamal General. + * + * @deprecated see Deprecation notice + */ + @Deprecated("ElGamal is deprecated") + ELGAMAL_GENERAL (20, true, true), + + /** + * Diffie-Hellman key exchange algorithm. + */ + DIFFIE_HELLMAN (21, false, true), + + /** + * Digital Signature Algorithm based on twisted Edwards Curves. + */ + EDDSA (22, true, false), + ; + + fun isSigningCapable(): Boolean = signingCapable + fun isEncryptionCapable(): Boolean = encryptionCapable + + companion object { + @JvmStatic + fun fromId(id: Int): PublicKeyAlgorithm? { + return values().firstOrNull { + it.algorithmId == id + } + } + + @JvmStatic + fun requireFromId(id: Int): PublicKeyAlgorithm { + return fromId(id) ?: + throw NoSuchElementException("No PublicKeyAlgorithm found for id $id") + } + } +} \ No newline at end of file From c8bfcc807be71fc5cd99e29f2b59af4d539a1037 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:05:13 +0200 Subject: [PATCH 013/155] Kotlin conversion: RevocationState --- .../pgpainless/algorithm/RevocationState.java | 131 ------------------ .../pgpainless/algorithm/RevocationState.kt | 87 ++++++++++++ 2 files changed, 87 insertions(+), 131 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.java deleted file mode 100644 index 8e4a60d3..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.java +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import org.pgpainless.util.DateUtil; - -import javax.annotation.Nonnull; -import java.util.Date; -import java.util.NoSuchElementException; - -public final class RevocationState implements Comparable { - - private final RevocationStateType type; - private final Date date; - - private RevocationState(RevocationStateType type) { - this(type, null); - } - - private RevocationState(RevocationStateType type, Date date) { - this.type = type; - if (type == RevocationStateType.softRevoked && date == null) { - throw new NullPointerException("If type is 'softRevoked' then date cannot be null."); - } - this.date = date; - } - - public static RevocationState notRevoked() { - return new RevocationState(RevocationStateType.notRevoked); - } - - public static RevocationState softRevoked(@Nonnull Date date) { - return new RevocationState(RevocationStateType.softRevoked, date); - } - - public static RevocationState hardRevoked() { - return new RevocationState(RevocationStateType.hardRevoked); - } - - public RevocationStateType getType() { - return type; - } - - public @Nonnull Date getDate() { - if (!isSoftRevocation()) { - throw new NoSuchElementException("RevocationStateType is not equal to 'softRevoked'. Cannot extract date."); - } - return date; - } - - public boolean isHardRevocation() { - return getType() == RevocationStateType.hardRevoked; - } - - public boolean isSoftRevocation() { - return getType() == RevocationStateType.softRevoked; - } - - public boolean isNotRevoked() { - return getType() == RevocationStateType.notRevoked; - } - - @Override - public String toString() { - String out = getType().toString(); - if (isSoftRevocation()) { - out = out + " (" + DateUtil.formatUTCDate(date) + ")"; - } - return out; - } - - @Override - public int compareTo(@Nonnull RevocationState o) { - switch (getType()) { - case notRevoked: - if (o.isNotRevoked()) { - return 0; - } else { - return -1; - } - - case softRevoked: - if (o.isNotRevoked()) { - return 1; - } else if (o.isSoftRevocation()) { - // Compare soft dates in reverse - return o.getDate().compareTo(getDate()); - } else { - return -1; - } - - case hardRevoked: - if (o.isHardRevocation()) { - return 0; - } else { - return 1; - } - - default: - throw new AssertionError("Unknown type: " + type); - } - } - - @Override - public int hashCode() { - return type.hashCode() * 31 + (isSoftRevocation() ? getDate().hashCode() : 0); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (!(obj instanceof RevocationState)) { - return false; - } - RevocationState other = (RevocationState) obj; - if (getType() != other.getType()) { - return false; - } - if (isSoftRevocation()) { - return DateUtil.toSecondsPrecision(getDate()).getTime() == DateUtil.toSecondsPrecision(other.getDate()).getTime(); - } - return true; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.kt new file mode 100644 index 00000000..d5d95d45 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.kt @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +import org.pgpainless.util.DateUtil +import java.lang.AssertionError +import java.util.* +import kotlin.NoSuchElementException + +class RevocationState private constructor( + val type: RevocationStateType, + private val _date: Date?): Comparable { + + val date: Date + get() { + if (!isSoftRevocation()) { + throw NoSuchElementException("RevocationStateType is not equal to 'softRevoked'. Cannot extract date.") + } + return _date!! + } + + private constructor(type: RevocationStateType): this(type, null) + + fun isSoftRevocation() = type == RevocationStateType.softRevoked + fun isHardRevocation() = type == RevocationStateType.hardRevoked + fun isNotRevoked() = type == RevocationStateType.notRevoked + + companion object { + @JvmStatic + fun notRevoked() = RevocationState(RevocationStateType.notRevoked) + + @JvmStatic + fun softRevoked(date: Date) = RevocationState(RevocationStateType.softRevoked, date) + + @JvmStatic + fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked) + } + + override fun compareTo(other: RevocationState): Int { + return when(type) { + RevocationStateType.notRevoked -> + if (other.isNotRevoked()) 0 + else -1 + RevocationStateType.softRevoked -> + if (other.isNotRevoked()) 1 + // Compare soft dates in reverse + else if (other.isSoftRevocation()) other.date.compareTo(date) + else -1 + RevocationStateType.hardRevoked -> + if (other.isHardRevocation()) 0 + else 1 + else -> throw AssertionError("Unknown type: $type") + } + } + + override fun toString(): String { + return buildString { + append(type) + if (isSoftRevocation()) append(" (${DateUtil.formatUTCDate(date)})") + } + } + + override fun hashCode(): Int { + return type.hashCode() * 31 + if (isSoftRevocation()) date.hashCode() else 0 + } + + override fun equals(other: Any?): Boolean { + if (other == null) { + return false + } + if (this === other) { + return true + } + if (other !is RevocationState) { + return false + } + if (type != other.type) { + return false + } + if (isSoftRevocation()) { + return DateUtil.toSecondsPrecision(date).time == DateUtil.toSecondsPrecision(other.date).time + } + return true + } +} \ No newline at end of file From 2fb7e6c3a3fad7ab634ba1df5739b0909c8ce5f8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:05:33 +0200 Subject: [PATCH 014/155] Kotlin conversion: RevocationStateType --- .../{RevocationStateType.java => RevocationStateType.kt} | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) rename pgpainless-core/src/main/java/org/pgpainless/algorithm/{RevocationStateType.java => RevocationStateType.kt} (68%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationStateType.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationStateType.kt similarity index 68% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationStateType.java rename to pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationStateType.kt index d1757255..bf18e27f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationStateType.java +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationStateType.kt @@ -1,11 +1,10 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.algorithm; - -public enum RevocationStateType { +package org.pgpainless.algorithm +enum class RevocationStateType { /** * Certificate is not revoked. */ @@ -20,4 +19,4 @@ public enum RevocationStateType { * Certificate is revoked with a hard revocation. */ hardRevoked -} +} \ No newline at end of file From f0082d3fb71be308bb599680fc05d5ac96ca5736 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:05:54 +0200 Subject: [PATCH 015/155] Kotlin conversion: SignatureSubpacket --- ...reSubpacket.java => SignatureSubpacket.kt} | 263 +++++++----------- 1 file changed, 101 insertions(+), 162 deletions(-) rename pgpainless-core/src/main/java/org/pgpainless/algorithm/{SignatureSubpacket.java => SignatureSubpacket.kt} (59%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureSubpacket.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureSubpacket.kt similarity index 59% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureSubpacket.java rename to pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureSubpacket.kt index 9429f0c6..fcb0c90a 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureSubpacket.java +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureSubpacket.kt @@ -1,77 +1,40 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.algorithm; +package org.pgpainless.algorithm -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import static org.bouncycastle.bcpg.SignatureSubpacketTags.ATTESTED_CERTIFICATIONS; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.CREATION_TIME; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.EMBEDDED_SIGNATURE; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.EXPIRE_TIME; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.EXPORTABLE; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.FEATURES; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.INTENDED_RECIPIENT_FINGERPRINT; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.ISSUER_FINGERPRINT; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.ISSUER_KEY_ID; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_EXPIRE_TIME; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_FLAGS; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.KEY_SERVER_PREFS; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.NOTATION_DATA; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.PLACEHOLDER; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.POLICY_URL; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_COMP_ALGS; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_HASH_ALGS; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_KEY_SERV; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.PREFERRED_SYM_ALGS; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.PRIMARY_USER_ID; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.REG_EXP; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCABLE; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_KEY; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.REVOCATION_REASON; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.SIGNATURE_TARGET; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.SIGNER_USER_ID; -import static org.bouncycastle.bcpg.SignatureSubpacketTags.TRUST_SIG; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; +import org.bouncycastle.bcpg.SignatureSubpacketTags.* /** * Enumeration of possible subpackets that might be found in the hashed and unhashed area of an OpenPGP signature. * - * @see RFC4880: Signature Subpacket Specification + * See [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1) */ -public enum SignatureSubpacket { - +enum class SignatureSubpacket(val code: Int) { /** * The time the signature was made. * MUST be present in the hashed area of the signature. * - * @see Signature Creation Time + * See [Signature Creation Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.4) */ - signatureCreationTime(CREATION_TIME), + signatureCreationTime(2), /** * The validity period of the signature. This is the number of seconds * after the signature creation time that the signature expires. If * this is not present or has a value of zero, it never expires. * - * @see Signature Expiration Time + * See [Signature Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.10) */ - signatureExpirationTime(EXPIRE_TIME), + signatureExpirationTime(3), /** * Denotes whether the signature is exportable for other users. * - * @see Exportable Certification + * See [Exportable Certification](https://tools.ietf.org/html/rfc4880#section-5.2.3.11) */ - exportableCertification(EXPORTABLE), + exportableCertification(4), /** * Signer asserts that the key is not only valid but also trustworthy at @@ -87,9 +50,9 @@ public enum SignatureSubpacket { * greater indicate complete trust. Implementations SHOULD emit values * of 60 for partial trust and 120 for complete trust. * - * @see Trust Signature + * See [Trust Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.13) */ - trustSignature(TRUST_SIG), + trustSignature(5), /** * Used in conjunction with trust Signature packets (of level greater 0) to @@ -100,9 +63,9 @@ public enum SignatureSubpacket { * "almost public domain" regular expression [REGEX] package. A * description of the syntax is found in Section 8 below. * - * @see Regular Expression + * See [Regular Expression](https://tools.ietf.org/html/rfc4880#section-5.2.3.14) */ - regularExpression(REG_EXP), + regularExpression(6), /** * Signature's revocability status. The packet body contains a Boolean @@ -112,9 +75,9 @@ public enum SignatureSubpacket { * signature for the life of his key. If this packet is not present, * the signature is revocable. * - * @see Revocable + * See [Revocable](https://tools.ietf.org/html/rfc4880#section-5.2.3.12) */ - revocable(REVOCABLE), + revocable(7), /** * The validity period of the key. This is the number of seconds after @@ -122,14 +85,14 @@ public enum SignatureSubpacket { * or has a value of zero, the key never expires. This is found only on * a self-signature. * - * @see Key Expiration Time + * See [Key Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.6) */ - keyExpirationTime(KEY_EXPIRE_TIME), + keyExpirationTime(9), /** * Placeholder for backwards compatibility. */ - placeholder(PLACEHOLDER), + placeholder(10), /** * Symmetric algorithm numbers that indicate which algorithms the keyholder @@ -138,9 +101,9 @@ public enum SignatureSubpacket { * algorithms listed are supported by the recipient's software. * This is only found on a self-signature. * - * @see Preferred Symmetric Algorithms + * See [Preferred Symmetric Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.7) */ - preferredSymmetricAlgorithms(PREFERRED_SYM_ALGS), + preferredSymmetricAlgorithms(11), /** * Authorizes the specified key to issue revocation signatures for this @@ -159,16 +122,16 @@ public enum SignatureSubpacket { * isolate this subpacket within a separate signature so that it is not * combined with other subpackets that need to be exported. * - * @see Revocation Key + * See [Revocation Key](https://tools.ietf.org/html/rfc4880#section-5.2.3.15) */ - revocationKey(REVOCATION_KEY), + revocationKey(12), /** * The OpenPGP Key ID of the key issuing the signature. * - * @see Issuer Key ID + * See [Issuer Key ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.5) */ - issuerKeyId(ISSUER_KEY_ID), + issuerKeyId(16), /** * This subpacket describes a "notation" on the signature that the @@ -178,9 +141,9 @@ public enum SignatureSubpacket { * the signature cares to make. The "flags" field holds four octets of * flags. * - * @see Notation Data + * See [Notation Data](https://tools.ietf.org/html/rfc4880#section-5.2.3.16) */ - notationData(NOTATION_DATA), + notationData(20), /** * Message digest algorithm numbers that indicate which algorithms the @@ -188,9 +151,9 @@ public enum SignatureSubpacket { * algorithms, the list is ordered. * This is only found on a self-signature. * - * @see Preferred Hash Algorithms + * See [Preferred Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.8) */ - preferredHashAlgorithms(PREFERRED_HASH_ALGS), + preferredHashAlgorithms(21), /** * Compression algorithm numbers that indicate which algorithms the @@ -200,9 +163,9 @@ public enum SignatureSubpacket { * software might have no compression software in that implementation. * This is only found on a self-signature. * - * @see Preferred Compressio Algorithms + * See [Preferred Compressio Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.9) */ - preferredCompressionAlgorithms(PREFERRED_COMP_ALGS), + preferredCompressionAlgorithms(22), /** * This is a list of one-bit flags that indicate preferences that the @@ -210,9 +173,9 @@ public enum SignatureSubpacket { * undefined flags MUST be zero. * This is found only on a self-signature. * - * @see Key Server Preferences + * See [Key Server Preferences](https://tools.ietf.org/html/rfc4880#section-5.2.3.17) */ - keyServerPreferences(KEY_SERVER_PREFS), + keyServerPreferences(23), /** * This is a URI of a key server that the keyholder prefers be used for @@ -221,9 +184,9 @@ public enum SignatureSubpacket { * key server can actually be a copy of the key retrieved by ftp, http, * finger, etc. * - * @see Preferred Key Server + * See [Preferred Key Server](https://tools.ietf.org/html/rfc4880#section-5.2.3.18) */ - preferredKeyServers(PREFERRED_KEY_SERV), + preferredKeyServers(24), /** * This is a flag in a User ID's self-signature that states whether this @@ -242,17 +205,17 @@ public enum SignatureSubpacket { * different and independent "primaries" -- one for User IDs, and one * for User Attributes. * - * @see Primary User-ID + * See [Primary User-ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.19) */ - primaryUserId(PRIMARY_USER_ID), + primaryUserId(25), /** * This subpacket contains a URI of a document that describes the policy * under which the signature was issued. * - * @see Policy URL + * See [Policy URL](https://tools.ietf.org/html/rfc4880#section-5.2.3.20) */ - policyUrl(POLICY_URL), + policyUrl(26), /** * This subpacket contains a list of binary flags that hold information @@ -261,9 +224,9 @@ public enum SignatureSubpacket { * list is shorter than an implementation expects, the unstated flags * are considered to be zero. * - * @see Key Flags + * See [Key Flags](https://tools.ietf.org/html/rfc4880#section-5.2.3.21) */ - keyFlags(KEY_FLAGS), + keyFlags(27), /** * This subpacket allows a keyholder to state which User ID is @@ -272,9 +235,9 @@ public enum SignatureSubpacket { * personal communications. This subpacket allows such a keyholder to * state which of their roles is making a signature. * - * @see Signer's User ID + * See [Signer's User ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.22) */ - signerUserId(SIGNER_USER_ID), + signerUserId(28), /** * This subpacket is used only in key revocation and certification @@ -291,9 +254,9 @@ public enum SignatureSubpacket { * 32 - User ID information is no longer valid (cert revocations) * 100-110 - Private Use * - * @see Reason for Revocation + * See [Reason for Revocation](https://tools.ietf.org/html/rfc4880#section-5.2.3.23) */ - revocationReason(REVOCATION_REASON), + revocationReason(29), /** * The Features subpacket denotes which advanced OpenPGP features a @@ -305,9 +268,9 @@ public enum SignatureSubpacket { * This subpacket is similar to a preferences subpacket, and only * appears in a self-signature. * - * @see Features + * See [Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24) */ - features(FEATURES), + features(30), /** * This subpacket identifies a specific target signature to which a @@ -321,18 +284,18 @@ public enum SignatureSubpacket { * signature. For example, a target signature with a SHA-1 hash MUST * have 20 octets of hash data. * - * @see Signature Target + * See [Signature Target](https://tools.ietf.org/html/rfc4880#section-5.2.3.25) */ - signatureTarget(SIGNATURE_TARGET), + signatureTarget(31), /** * This subpacket contains a complete Signature packet body as * specified in Section 5.2 above. It is useful when one signature * needs to refer to, or be incorporated in, another signature. * - * @see Embedded Signature + * See [Embedded Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.26) */ - embeddedSignature(EMBEDDED_SIGNATURE), + embeddedSignature(32), /** * The OpenPGP Key fingerprint of the key issuing the signature. This @@ -344,9 +307,9 @@ public enum SignatureSubpacket { * Note that the length N of the fingerprint for a version 4 key is 20 * octets; for a version 5 key N is 32. * - * @see Issuer Fingerprint + * See [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28) */ - issuerFingerprint(ISSUER_FINGERPRINT), + issuerFingerprint(33), /** * AEAD algorithm numbers that indicate which AEAD algorithms the @@ -357,9 +320,9 @@ public enum SignatureSubpacket { * Note that support for the AEAD Encrypted Data packet in the general * is indicated by a Feature Flag. * - * @see Preferred AEAD Algorithms + * See [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8) */ - preferredAEADAlgorithms(PREFERRED_AEAD_ALGORITHMS), + preferredAEADAlgorithms(39), /** * The OpenPGP Key fingerprint of the intended recipient primary key. @@ -372,9 +335,9 @@ public enum SignatureSubpacket { * Note that the length N of the fingerprint for a version 4 key is 20 * octets; for a version 5 key N is 32. * - * @see Intended Recipient Fingerprint + * See [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29) */ - intendedRecipientFingerprint(INTENDED_RECIPIENT_FINGERPRINT), + intendedRecipientFingerprint(35), /** * This subpacket MUST only appear as a hashed subpacket of an @@ -388,75 +351,51 @@ public enum SignatureSubpacket { * Attested Certification subpacket in any generated Attestation Key * Signature. * - * @see Attested Certification + * See [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30) */ - attestedCertification(ATTESTED_CERTIFICATIONS) + attestedCertification(37) ; - - private static final Map MAP = new ConcurrentHashMap<>(); - static { - for (SignatureSubpacket p : values()) { - MAP.put(p.code, p); - } - } - - private final int code; - - SignatureSubpacket(int code) { - this.code = code; - } - - /** - * Return the numerical identifier of the {@link SignatureSubpacket}. - * @return id - */ - public int getCode() { - return code; - } - - /** - * Return the {@link SignatureSubpacket} that corresponds to the provided id. - * If an unmatched code is presented, return null. - * - * @param code id - * @return signature subpacket - */ - @Nullable - public static SignatureSubpacket fromCode(int code) { - return MAP.get(code); - } - - /** - * Return the {@link SignatureSubpacket} that corresponds to the provided code. - * - * @param code code - * @return signature subpacket - * @throws NoSuchElementException in case of an unmatched subpacket tag - */ - @Nonnull - public static SignatureSubpacket requireFromCode(int code) { - SignatureSubpacket tag = fromCode(code); - if (tag == null) { - throw new NoSuchElementException("No SignatureSubpacket tag found with code " + code); - } - return tag; - } - - /** - * Convert an array of signature subpacket tags into a list of {@link SignatureSubpacket SignatureSubpackets}. - * - * @param codes array of codes - * @return list of subpackets - */ - public static List fromCodes(int[] codes) { - List tags = new ArrayList<>(); - for (int code : codes) { - try { - tags.add(requireFromCode(code)); - } catch (NoSuchElementException e) { - // skip + + companion object { + + /** + * Return the [SignatureSubpacket] that corresponds to the provided id. + * If an unmatched code is presented, return null. + * + * @param code id + * @return signature subpacket + */ + @JvmStatic + fun fromCode(code: Int): SignatureSubpacket? { + return values().firstOrNull { + it.code == code + } + } + + /** + * Return the [SignatureSubpacket] that corresponds to the provided code. + * + * @param code code + * @return signature subpacket + * @throws NoSuchElementException in case of an unmatched subpacket tag + */ + @JvmStatic + fun requireFromCode(code: Int): SignatureSubpacket { + return fromCode(code) ?: + throw NoSuchElementException("No SignatureSubpacket tag found with code $code") + } + + /** + * Convert an array of signature subpacket tags into a list of [SignatureSubpacket SignatureSubpackets]. + * + * @param codes array of codes + * @return list of subpackets + */ + @JvmStatic + fun fromCodes(vararg codes: Int): List { + return codes.toList().mapNotNull { + fromCode(it) } } - return tags; } -} +} \ No newline at end of file From 595b9c73793e075e0dece9b1d31b0498ef925d0a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:06:12 +0200 Subject: [PATCH 016/155] Kotlin conversion: SignatureType --- .../{SignatureType.java => SignatureType.kt} | 187 +++++++++--------- 1 file changed, 95 insertions(+), 92 deletions(-) rename pgpainless-core/src/main/java/org/pgpainless/algorithm/{SignatureType.java => SignatureType.kt} (58%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.kt similarity index 58% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.java rename to pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.kt index c2f02989..1b708a03 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.java +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.kt @@ -1,29 +1,25 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.algorithm; +package org.pgpainless.algorithm -import org.bouncycastle.openpgp.PGPSignature; - -import javax.annotation.Nonnull; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import org.bouncycastle.openpgp.PGPSignature /** * Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1 - * See {@link org.bouncycastle.openpgp.PGPSignature} for comparison. + * See [PGPSignature] for comparison. * - * @see rfc4880 §5.2.1. Signature Types + * See [rfc4880 §5.2.1. Signature Types](https://tools.ietf.org/html/rfc4880#section-5.11) */ -public enum SignatureType { +enum class SignatureType(val code: Int) { /** * Signature of a binary document. * This means the signer owns it, created it, or certifies that it * has not been modified. */ - BINARY_DOCUMENT(PGPSignature.BINARY_DOCUMENT), + BINARY_DOCUMENT(0x00), /** * Signature of a canonical text document. @@ -31,7 +27,7 @@ public enum SignatureType { * has not been modified. The signature is calculated over the text * data with its line endings converted to {@code }. */ - CANONICAL_TEXT_DOCUMENT(PGPSignature.CANONICAL_TEXT_DOCUMENT), + CANONICAL_TEXT_DOCUMENT(0x01), /** * Standalone signature. @@ -40,7 +36,7 @@ public enum SignatureType { * binary document. Note that it doesn't make sense to have a V3 * standalone signature. */ - STANDALONE(PGPSignature.STAND_ALONE), + STANDALONE(0x02), /** * Generic certification of a User ID and Public-Key packet. @@ -48,28 +44,28 @@ public enum SignatureType { * assertion as to how well the certifier has checked that the owner * of the key is in fact the person described by the User ID. */ - GENERIC_CERTIFICATION(PGPSignature.DEFAULT_CERTIFICATION), + GENERIC_CERTIFICATION(0x10), /** * Persona certification of a User ID and Public-Key packet. * The issuer of this certification has not done any verification of * the claim that the owner of this key is the User ID specified. */ - NO_CERTIFICATION(PGPSignature.NO_CERTIFICATION), + NO_CERTIFICATION(0x11), /** * Casual certification of a User ID and Public-Key packet. * The issuer of this certification has done some casual * verification of the claim of identity. */ - CASUAL_CERTIFICATION(PGPSignature.CASUAL_CERTIFICATION), + CASUAL_CERTIFICATION(0x12), /** * Positive certification of a User ID and Public-Key packet. * The issuer of this certification has done substantial * verification of the claim of identity. */ - POSITIVE_CERTIFICATION(PGPSignature.POSITIVE_CERTIFICATION), + POSITIVE_CERTIFICATION(0x13), /** * Subkey Binding Signature. @@ -78,20 +74,20 @@ public enum SignatureType { * directly on the primary key and subkey, and not on any User ID or * other packets. A signature that binds a signing subkey MUST have * an Embedded Signature subpacket in this binding signature that - * contains a {@link #PRIMARYKEY_BINDING} signature made by the + * contains a [#PRIMARYKEY_BINDING] signature made by the * signing subkey on the primary key and subkey. */ - SUBKEY_BINDING(PGPSignature.SUBKEY_BINDING), + SUBKEY_BINDING(0x18), /** * Primary Key Binding Signature * This signature is a statement by a signing subkey, indicating * that it is owned by the primary key and subkey. This signature - * is calculated the same way as a {@link #SUBKEY_BINDING} signature: + * is calculated the same way as a [#SUBKEY_BINDING] signature: * directly on the primary key and subkey, and not on any User ID or * other packets. */ - PRIMARYKEY_BINDING(PGPSignature.PRIMARYKEY_BINDING), + PRIMARYKEY_BINDING(0x19), /** * Signature directly on a key @@ -103,7 +99,7 @@ public enum SignatureType { * about the key itself, rather than the binding between a key and a * name. */ - DIRECT_KEY(PGPSignature.DIRECT_KEY), + DIRECT_KEY(0x1f), /** * Key revocation signature @@ -112,7 +108,7 @@ public enum SignatureType { * key being revoked, or by an authorized revocation key, should be * considered valid revocation signatures. */ - KEY_REVOCATION(PGPSignature.KEY_REVOCATION), + KEY_REVOCATION(0x20), /** * Subkey revocation signature @@ -122,26 +118,26 @@ public enum SignatureType { * by an authorized revocation key, should be considered valid * revocation signatures. */ - SUBKEY_REVOCATION(PGPSignature.SUBKEY_REVOCATION), + SUBKEY_REVOCATION(0x28), /** * Certification revocation signature * This signature revokes an earlier User ID certification signature - * (signature class 0x10 through 0x13) or signature {@link #DIRECT_KEY}. + * (signature class 0x10 through 0x13) or signature [#DIRECT_KEY]. * It should be issued by the same key that issued the * revoked signature or an authorized revocation key. The signature * is computed over the same data as the certificate that it * revokes, and should have a later creation date than that * certificate. */ - CERTIFICATION_REVOCATION(PGPSignature.CERTIFICATION_REVOCATION), + CERTIFICATION_REVOCATION(0x30), /** * Timestamp signature. * This signature is only meaningful for the timestamp contained in * it. */ - TIMESTAMP(PGPSignature.TIMESTAMP), + TIMESTAMP(0x40), /** * Third-Party Confirmation signature. @@ -156,70 +152,77 @@ public enum SignatureType { THIRD_PARTY_CONFIRMATION(0x50) ; - private static final Map map = new ConcurrentHashMap<>(); - static { - for (SignatureType sigType : SignatureType.values()) { - map.put(sigType.getCode(), sigType); + companion object { + + /** + * Convert a numerical id into a [SignatureType]. + * + * @param code numeric id + * @return signature type enum + */ + @JvmStatic + fun fromCode(code: Int): SignatureType? { + return values().firstOrNull { + it.code == code + } + } + + /** + * Convert a numerical id into a [SignatureType]. + * + * @param code numeric id + * @return signature type enum + * @throws NoSuchElementException in case of an unmatched signature type code + */ + @JvmStatic + fun requireFromCode(code: Int): SignatureType { + return fromCode(code) ?: + throw NoSuchElementException("Signature type 0x${Integer.toHexString(code)} appears to be invalid.") + } + + /** + * Convert a numerical id into a [SignatureType]. + * + * @param code numeric id + * @return signature type enum + * @throws IllegalArgumentException in case of an unmatched signature type code + */ + @JvmStatic + @Deprecated("Deprecated in favor of requireFromCode", + ReplaceWith("requireFromCode")) + fun valueOf(code: Int): SignatureType { + try { + return requireFromCode(code) + } catch (e: NoSuchElementException) { + throw IllegalArgumentException(e.message) + } + } + + @JvmStatic + fun isRevocationSignature(signatureType: Int): Boolean { + return isRevocationSignature(valueOf(signatureType)) + } + + @JvmStatic + fun isRevocationSignature(signatureType: SignatureType): Boolean { + return when (signatureType) { + BINARY_DOCUMENT, + CANONICAL_TEXT_DOCUMENT, + STANDALONE, + GENERIC_CERTIFICATION, + NO_CERTIFICATION, + CASUAL_CERTIFICATION, + POSITIVE_CERTIFICATION, + SUBKEY_BINDING, + PRIMARYKEY_BINDING, + DIRECT_KEY, + TIMESTAMP, + THIRD_PARTY_CONFIRMATION -> false + KEY_REVOCATION, + SUBKEY_REVOCATION, + CERTIFICATION_REVOCATION -> true + else -> throw IllegalArgumentException("Unknown signature type: $signatureType") + } } } - - /** - * Convert a numerical id into a {@link SignatureType}. - * - * @param code numeric id - * @return signature type enum - * @throws IllegalArgumentException in case of an unmatched signature type code - */ - @Nonnull - public static SignatureType valueOf(int code) { - SignatureType type = map.get(code); - if (type != null) { - return type; - } - throw new IllegalArgumentException("Signature type 0x" + Integer.toHexString(code) + " appears to be invalid."); - } - - private final int code; - - SignatureType(int code) { - this.code = code; - } - - /** - * Return the numeric id of the signature type enum. - * - * @return numeric id - */ - public int getCode() { - return code; - } - - public static boolean isRevocationSignature(int signatureType) { - return isRevocationSignature(SignatureType.valueOf(signatureType)); - } - - public static boolean isRevocationSignature(SignatureType signatureType) { - switch (signatureType) { - case BINARY_DOCUMENT: - case CANONICAL_TEXT_DOCUMENT: - case STANDALONE: - case GENERIC_CERTIFICATION: - case NO_CERTIFICATION: - case CASUAL_CERTIFICATION: - case POSITIVE_CERTIFICATION: - case SUBKEY_BINDING: - case PRIMARYKEY_BINDING: - case DIRECT_KEY: - case TIMESTAMP: - case THIRD_PARTY_CONFIRMATION: - return false; - case KEY_REVOCATION: - case SUBKEY_REVOCATION: - case CERTIFICATION_REVOCATION: - return true; - default: - throw new IllegalArgumentException("Unknown signature type: " + signatureType); - } - } - -} +} \ No newline at end of file From 5da58904b65ab84cb6969fae7c63262e556922be Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:06:29 +0200 Subject: [PATCH 017/155] Kotlin conversion: StreamEncoding --- .../pgpainless/algorithm/StreamEncoding.java | 101 ------------------ .../pgpainless/algorithm/StreamEncoding.kt | 71 ++++++++++++ 2 files changed, 71 insertions(+), 101 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.java deleted file mode 100644 index b0617bbb..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.java +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; - -import org.bouncycastle.openpgp.PGPLiteralData; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Enumeration of possible encoding formats of the content of the literal data packet. - * - * @see RFC4880: Literal Data Packet - */ -public enum StreamEncoding { - - /** - * The Literal packet contains binary data. - */ - BINARY(PGPLiteralData.BINARY), - - /** - * The Literal packet contains text data, and thus may need line ends converted to local form, or other - * text-mode changes. - */ - TEXT(PGPLiteralData.TEXT), - - /** - * Indication that the implementation believes that the literal data contains UTF-8 text. - */ - UTF8(PGPLiteralData.UTF8), - - /** - * Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions. - * RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one). - * Both of these local modes are deprecated. - */ - @Deprecated - LOCAL('l'), - ; - - private final char code; - - private static final Map MAP = new ConcurrentHashMap<>(); - static { - for (StreamEncoding f : StreamEncoding.values()) { - MAP.put(f.code, f); - } - // RFC 1991 [RFC1991] incorrectly stated local mode flag as '1', see doc of LOCAL. - MAP.put('1', LOCAL); - } - - StreamEncoding(char code) { - this.code = code; - } - - /** - * Return the code identifier of the encoding. - * - * @return identifier - */ - public char getCode() { - return code; - } - - /** - * Return the {@link StreamEncoding} corresponding to the provided code identifier. - * If no matching encoding is found, return null. - * - * @param code identifier - * @return encoding enum - */ - @Nullable - public static StreamEncoding fromCode(int code) { - return MAP.get((char) code); - } - - /** - * Return the {@link StreamEncoding} corresponding to the provided code identifier. - * If no matching encoding is found, throw a {@link NoSuchElementException}. - * - * @param code identifier - * @return encoding enum - * - * @throws NoSuchElementException in case of an unmatched identifier - */ - @Nonnull - public static StreamEncoding requireFromCode(int code) { - StreamEncoding encoding = fromCode(code); - if (encoding == null) { - throw new NoSuchElementException("No StreamEncoding found for code " + code); - } - return encoding; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.kt new file mode 100644 index 00000000..391797b1 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.kt @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +/** + * Enumeration of possible encoding formats of the content of the literal data packet. + * + * See [RFC4880: Literal Data Packet](https://tools.ietf.org/html/rfc4880#section-5.9) + */ +enum class StreamEncoding(val code: Char) { + + /** + * The Literal packet contains binary data. + */ + BINARY('b'), + + /** + * The Literal packet contains text data, and thus may need line ends converted to local form, or other + * text-mode changes. + */ + TEXT('t'), + + /** + * Indication that the implementation believes that the literal data contains UTF-8 text. + */ + UTF8('u'), + + /** + * Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions. + * RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one). + * Both of these local modes are deprecated. + */ + @Deprecated("LOCAL is deprecated.") + LOCAL('l'), + ; + + + companion object { + /** + * Return the [StreamEncoding] corresponding to the provided code identifier. + * If no matching encoding is found, return null. + * + * @param code identifier + * @return encoding enum + */ + @JvmStatic + fun fromCode(code: Int): StreamEncoding? { + return values().firstOrNull { + it.code == code.toChar() + } ?: if (code == 1) return LOCAL else null + } + + /** + * Return the [StreamEncoding] corresponding to the provided code identifier. + * If no matching encoding is found, throw a [NoSuchElementException]. + * + * @param code identifier + * @return encoding enum + * + * @throws NoSuchElementException in case of an unmatched identifier + */ + @JvmStatic + fun requireFromCode(code: Int): StreamEncoding { + return fromCode(code) ?: + throw NoSuchElementException("No StreamEncoding found for code $code") + } + } + +} \ No newline at end of file From 3a62b391979abfc050e6311ea54a67afe439bebd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:06:51 +0200 Subject: [PATCH 018/155] Kotlin conversion: SymmetricKeyAlgorithm --- .../algorithm/SymmetricKeyAlgorithm.java | 150 ------------------ .../algorithm/SymmetricKeyAlgorithm.kt | 120 ++++++++++++++ 2 files changed, 120 insertions(+), 150 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.java deleted file mode 100644 index e04f21a5..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.java +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; - -import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Enumeration of possible symmetric encryption algorithms. - * - * @see RFC4880: Symmetric-Key Algorithms - */ -public enum SymmetricKeyAlgorithm { - - /** - * Plaintext or unencrypted data. - */ - NULL (SymmetricKeyAlgorithmTags.NULL), - - /** - * IDEA is deprecated. - * @deprecated use a different algorithm. - */ - @Deprecated - IDEA (SymmetricKeyAlgorithmTags.IDEA), - - /** - * TripleDES (DES-EDE - 168 bit key derived from 192). - */ - TRIPLE_DES (SymmetricKeyAlgorithmTags.TRIPLE_DES), - - /** - * CAST5 (128-bit key, as per RFC2144). - */ - CAST5 (SymmetricKeyAlgorithmTags.CAST5), - - /** - * Blowfish (128-bit key, 16 rounds). - */ - BLOWFISH (SymmetricKeyAlgorithmTags.BLOWFISH), - - /** - * Reserved in RFC4880. - * SAFER-SK128 (13 rounds) - */ - SAFER (SymmetricKeyAlgorithmTags.SAFER), - - /** - * Reserved in RFC4880. - * Reserved for DES/SK - */ - DES (SymmetricKeyAlgorithmTags.DES), - - /** - * AES with 128-bit key. - */ - AES_128 (SymmetricKeyAlgorithmTags.AES_128), - - /** - * AES with 192-bit key. - */ - AES_192 (SymmetricKeyAlgorithmTags.AES_192), - - /** - * AES with 256-bit key. - */ - AES_256 (SymmetricKeyAlgorithmTags.AES_256), - - /** - * Twofish with 256-bit key. - */ - TWOFISH (SymmetricKeyAlgorithmTags.TWOFISH), - - /** - * Reserved for Camellia with 128-bit key. - */ - CAMELLIA_128 (SymmetricKeyAlgorithmTags.CAMELLIA_128), - - /** - * Reserved for Camellia with 192-bit key. - */ - CAMELLIA_192 (SymmetricKeyAlgorithmTags.CAMELLIA_192), - - /** - * Reserved for Camellia with 256-bit key. - */ - CAMELLIA_256 (SymmetricKeyAlgorithmTags.CAMELLIA_256), - ; - - private static final Map MAP = new ConcurrentHashMap<>(); - - static { - for (SymmetricKeyAlgorithm s : SymmetricKeyAlgorithm.values()) { - MAP.put(s.algorithmId, s); - } - } - - /** - * Return the {@link SymmetricKeyAlgorithm} enum that corresponds to the provided numeric id. - * If an invalid id is provided, null is returned. - * - * @param id numeric algorithm id - * @return symmetric key algorithm enum - */ - @Nullable - public static SymmetricKeyAlgorithm fromId(int id) { - return MAP.get(id); - } - - /** - * Return the {@link SymmetricKeyAlgorithm} enum that corresponds to the provided numeric id. - * If an invalid id is provided, throw a {@link NoSuchElementException}. - * - * @param id numeric algorithm id - * @return symmetric key algorithm enum - * - * @throws NoSuchElementException if an unmatched id is provided - */ - @Nonnull - public static SymmetricKeyAlgorithm requireFromId(int id) { - SymmetricKeyAlgorithm algorithm = fromId(id); - if (algorithm == null) { - throw new NoSuchElementException("No SymmetricKeyAlgorithm found for id " + id); - } - return algorithm; - } - - private final int algorithmId; - - SymmetricKeyAlgorithm(int algorithmId) { - this.algorithmId = algorithmId; - } - - /** - * Return the numeric algorithm id of the enum. - * - * @return numeric id - */ - public int getAlgorithmId() { - return algorithmId; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt new file mode 100644 index 00000000..ae72ff05 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +/** + * Enumeration of possible symmetric encryption algorithms. + * + * See [RFC4880: Symmetric-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.2) + */ +enum class SymmetricKeyAlgorithm(val algorithmId: Int) { + + /** + * Plaintext or unencrypted data. + */ + NULL (0), + + /** + * IDEA is deprecated. + * @deprecated use a different algorithm. + */ + @Deprecated("IDEA is deprecated.") + IDEA (1), + + /** + * TripleDES (DES-EDE - 168 bit key derived from 192). + */ + TRIPLE_DES (2), + + /** + * CAST5 (128-bit key, as per RFC2144). + */ + CAST5 (3), + + /** + * Blowfish (128-bit key, 16 rounds). + */ + BLOWFISH (4), + + /** + * Reserved in RFC4880. + * SAFER-SK128 (13 rounds) + */ + SAFER (5), + + /** + * Reserved in RFC4880. + * Reserved for DES/SK + */ + DES (6), + + /** + * AES with 128-bit key. + */ + AES_128 (7), + + /** + * AES with 192-bit key. + */ + AES_192 (8), + + /** + * AES with 256-bit key. + */ + AES_256 (9), + + /** + * Twofish with 256-bit key. + */ + TWOFISH (10), + + /** + * Reserved for Camellia with 128-bit key. + */ + CAMELLIA_128 (11), + + /** + * Reserved for Camellia with 192-bit key. + */ + CAMELLIA_192 (12), + + /** + * Reserved for Camellia with 256-bit key. + */ + CAMELLIA_256 (13), + ; + + companion object { + + /** + * Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. + * If an invalid id is provided, null is returned. + * + * @param id numeric algorithm id + * @return symmetric key algorithm enum + */ + @JvmStatic + fun fromId(id: Int): SymmetricKeyAlgorithm? { + return values().firstOrNull { + it.algorithmId == id + } + } + + /** + * Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. + * If an invalid id is provided, throw a [NoSuchElementException]. + * + * @param id numeric algorithm id + * @return symmetric key algorithm enum + * + * @throws NoSuchElementException if an unmatched id is provided + */ + @JvmStatic + fun requireFromId(id: Int): SymmetricKeyAlgorithm { + return fromId(id) ?: + throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id") + } + } +} \ No newline at end of file From 382817dc5fa2f36273771662503e9e303261901f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:07:02 +0200 Subject: [PATCH 019/155] Kotlin conversion: Trustworthiness --- .../pgpainless/algorithm/Trustworthiness.java | 188 ------------------ .../pgpainless/algorithm/Trustworthiness.kt | 140 +++++++++++++ 2 files changed, 140 insertions(+), 188 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.java deleted file mode 100644 index 26e66e9c..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.java +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -/** - * Facade class for {@link org.bouncycastle.bcpg.sig.TrustSignature}. - * A trust signature subpacket marks the trustworthiness of a certificate and defines its capabilities to act - * as a trusted introducer. - */ -public class Trustworthiness { - - private final int amount; - private final int depth; - - public static final int THRESHOLD_FULLY_CONVINCED = 120; // greater or equal is fully trusted - public static final int MARGINALLY_CONVINCED = 60; // default value for marginally convinced - public static final int NOT_TRUSTED = 0; // 0 is not trusted - - public Trustworthiness(int amount, int depth) { - this.amount = capAmount(amount); - this.depth = capDepth(depth); - } - - /** - * Get the trust amount. - * This value means how confident the issuer of the signature is in validity of the binding. - * - * @return trust amount - */ - public int getAmount() { - return amount; - } - - /** - * Get the depth of the trust signature. - * This value controls, whether the certificate can act as a trusted introducer. - * - * @return depth - */ - public int getDepth() { - return depth; - } - - /** - * Returns true, if the trust amount is equal to 0. - * This means the key is not trusted. - * - * Otherwise return false - * @return true if untrusted - */ - public boolean isNotTrusted() { - return getAmount() == NOT_TRUSTED; - } - - /** - * Return true if the certificate is at least marginally trusted. - * That is the case, if the trust amount is greater than 0. - * - * @return true if the cert is at least marginally trusted - */ - public boolean isMarginallyTrusted() { - return getAmount() > NOT_TRUSTED; - } - - /** - * Return true if the certificate is fully trusted. That is the case if the trust amount is - * greater than or equal to 120. - * - * @return true if the cert is fully trusted - */ - public boolean isFullyTrusted() { - return getAmount() >= THRESHOLD_FULLY_CONVINCED; - } - - /** - * Return true, if the cert is an introducer. That is the case if the depth is greater 0. - * - * @return true if introducer - */ - public boolean isIntroducer() { - return getDepth() >= 1; - } - - /** - * Return true, if the certified cert can introduce certificates with trust depth of
otherDepth
. - * - * @param otherDepth other certifications trust depth - * @return true if the cert can introduce the other - */ - public boolean canIntroduce(int otherDepth) { - return getDepth() > otherDepth; - } - - /** - * Return true, if the certified cert can introduce certificates with the given
other
trust depth. - * - * @param other other certificates trust depth - * @return true if the cert can introduce the other - */ - public boolean canIntroduce(Trustworthiness other) { - return canIntroduce(other.getDepth()); - } - - /** - * This means that we are fully convinced of the trustworthiness of the key. - * - * @return builder - */ - public static Builder fullyTrusted() { - return new Builder(THRESHOLD_FULLY_CONVINCED); - } - - /** - * This means that we are marginally (partially) convinced of the trustworthiness of the key. - * - * @return builder - */ - public static Builder marginallyTrusted() { - return new Builder(MARGINALLY_CONVINCED); - } - - /** - * This means that we do not trust the key. - * Can be used to overwrite previous trust. - * - * @return builder - */ - public static Builder untrusted() { - return new Builder(NOT_TRUSTED); - } - - public static final class Builder { - - private final int amount; - - private Builder(int amount) { - this.amount = amount; - } - - /** - * The key is a trusted introducer (depth 1). - * Certifications made by this key are considered trustworthy. - * - * @return trust - */ - public Trustworthiness introducer() { - return new Trustworthiness(amount, 1); - } - - /** - * The key is a meta introducer (depth 2). - * This key can introduce trusted introducers of depth 1. - * - * @return trust - */ - public Trustworthiness metaIntroducer() { - return new Trustworthiness(amount, 2); - } - - /** - * The key is a meta introducer of depth
n
. - * This key can introduce meta introducers of depth
n - 1
. - * - * @param n depth - * @return trust - */ - public Trustworthiness metaIntroducerOfDepth(int n) { - return new Trustworthiness(amount, n); - } - } - - private static int capAmount(int amount) { - if (amount < 0 || amount > 255) { - throw new IllegalArgumentException("Trust amount MUST be a value between 0 and 255"); - } - return amount; - } - - private static int capDepth(int depth) { - if (depth < 0 || depth > 255) { - throw new IllegalArgumentException("Trust depth MUST be a value between 0 and 255"); - } - return depth; - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.kt new file mode 100644 index 00000000..b1a1b7dd --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.kt @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +/** + * Facade class for [org.bouncycastle.bcpg.sig.TrustSignature]. + * A trust signature subpacket marks the trustworthiness of a certificate and defines its capabilities to act + * as a trusted introducer. + */ +class Trustworthiness(amount: Int, depth: Int) { + val depth = capDepth(depth) + val amount = capAmount(amount) + + /** + * Returns true, if the trust amount is equal to 0. + * This means the key is not trusted. + * + * Otherwise return false + * @return true if untrusted + */ + fun isNotTrusted() = amount == NOT_TRUSTED + + /** + * Return true if the certificate is at least marginally trusted. + * That is the case, if the trust amount is greater than 0. + * + * @return true if the cert is at least marginally trusted + */ + fun isMarginallyTrusted() = amount > NOT_TRUSTED + + /** + * Return true if the certificate is fully trusted. That is the case if the trust amount is + * greater than or equal to 120. + * + * @return true if the cert is fully trusted + */ + fun isFullyTrusted() = amount >= THRESHOLD_FULLY_CONVINCED + + /** + * Return true, if the cert is an introducer. That is the case if the depth is greater 0. + * + * @return true if introducer + */ + fun isIntroducer() = depth >= 1 + + /** + * Return true, if the certified cert can introduce certificates with trust depth of
otherDepth
. + * + * @param otherDepth other certifications trust depth + * @return true if the cert can introduce the other + */ + fun canIntroduce(otherDepth: Int) = depth > otherDepth + + /** + * Return true, if the certified cert can introduce certificates with the given
other
trust depth. + * + * @param other other certificates trust depth + * @return true if the cert can introduce the other + */ + fun canIntroduce(other: Trustworthiness) = canIntroduce(other.depth) + + companion object { + + const val THRESHOLD_FULLY_CONVINCED = 120 // greater or equal is fully trusted + const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced + const val NOT_TRUSTED = 0 // 0 is not trusted + + /** + * This means that we are fully convinced of the trustworthiness of the key. + * + * @return builder + */ + @JvmStatic + fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED) + + /** + * This means that we are marginally (partially) convinced of the trustworthiness of the key. + * + * @return builder + */ + @JvmStatic + fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED) + + /** + * This means that we do not trust the key. + * Can be used to overwrite previous trust. + * + * @return builder + */ + @JvmStatic + fun untrusted() = Builder(NOT_TRUSTED) + + @JvmStatic + private fun capAmount(amount: Int): Int { + if (amount !in 0..255) { + throw IllegalArgumentException("Trust amount MUST be a value between 0 and 255") + } + return amount + } + + @JvmStatic + private fun capDepth(depth: Int): Int { + if (depth !in 0..255) { + throw IllegalArgumentException("Trust depth MUST be a value between 0 and 255") + } + return depth + } + } + + class Builder(val amount: Int) { + + /** + * The key is a trusted introducer (depth 1). + * Certifications made by this key are considered trustworthy. + * + * @return trust + */ + fun introducer() = Trustworthiness(amount, 1) + + /** + * The key is a meta introducer (depth 2). + * This key can introduce trusted introducers of depth 1. + * + * @return trust + */ + fun metaIntroducer() = Trustworthiness(amount, 2) + + /** + * The key is a meta introducer of depth
n
. + * This key can introduce meta introducers of depth
n - 1
. + * + * @param n depth + * @return trust + */ + fun metaIntroducerOfDepth(d: Int) = Trustworthiness(amount, d) + } + +} \ No newline at end of file From b5d8126e48dfd2a6a9f13ae546637fa28aff212e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:18:19 +0200 Subject: [PATCH 020/155] Kotlin conversion: AlgorithmSuite --- .../pgpainless/algorithm/AlgorithmSuite.java | 63 ------------------- .../pgpainless/algorithm/AlgorithmSuite.kt | 41 ++++++++++++ 2 files changed, 41 insertions(+), 63 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.java deleted file mode 100644 index e155e367..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.java +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm; - -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * The {@link AlgorithmSuite} class is consulted when new OpenPGP keys are being generated to set - * preferred algorithms on the key. - */ -public class AlgorithmSuite { - - private static final AlgorithmSuite defaultAlgorithmSuite = new AlgorithmSuite( - Arrays.asList( - SymmetricKeyAlgorithm.AES_256, - SymmetricKeyAlgorithm.AES_192, - SymmetricKeyAlgorithm.AES_128), - Arrays.asList( - HashAlgorithm.SHA512, - HashAlgorithm.SHA384, - HashAlgorithm.SHA256, - HashAlgorithm.SHA224), - Arrays.asList( - CompressionAlgorithm.ZLIB, - CompressionAlgorithm.BZIP2, - CompressionAlgorithm.ZIP, - CompressionAlgorithm.UNCOMPRESSED) - ); - - private final Set symmetricKeyAlgorithms; - private final Set hashAlgorithms; - private final Set compressionAlgorithms; - - public AlgorithmSuite(List symmetricKeyAlgorithms, - List hashAlgorithms, - List compressionAlgorithms) { - this.symmetricKeyAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(symmetricKeyAlgorithms)); - this.hashAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(hashAlgorithms)); - this.compressionAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(compressionAlgorithms)); - } - - public Set getSymmetricKeyAlgorithms() { - return new LinkedHashSet<>(symmetricKeyAlgorithms); - } - - public Set getHashAlgorithms() { - return new LinkedHashSet<>(hashAlgorithms); - } - - public Set getCompressionAlgorithms() { - return new LinkedHashSet<>(compressionAlgorithms); - } - - public static AlgorithmSuite getDefaultAlgorithmSuite() { - return defaultAlgorithmSuite; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt new file mode 100644 index 00000000..9bb2182e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt @@ -0,0 +1,41 @@ +package org.pgpainless.algorithm + +class AlgorithmSuite( + symmetricKeyAlgorithms: List, + hashAlgorithms: List, + compressionAlgorithms: List) { + + val symmetricKeyAlgorithms: Set = symmetricKeyAlgorithms.toSet() + val hashAlgorithms: Set = hashAlgorithms.toSet() + val compressionAlgorithms: Set = compressionAlgorithms.toSet() + + companion object { + + @JvmStatic + val defaultSymmetricKeyAlgorithms = listOf( + SymmetricKeyAlgorithm.AES_256, + SymmetricKeyAlgorithm.AES_192, + SymmetricKeyAlgorithm.AES_128) + + @JvmStatic + val defaultHashAlgorithms = listOf( + HashAlgorithm.SHA512, + HashAlgorithm.SHA384, + HashAlgorithm.SHA256, + HashAlgorithm.SHA224) + + @JvmStatic + val defaultCompressionAlgorithms = listOf( + CompressionAlgorithm.ZLIB, + CompressionAlgorithm.BZIP2, + CompressionAlgorithm.ZIP, + CompressionAlgorithm.UNCOMPRESSED) + + @JvmStatic + val defaultAlgorithmSuite = AlgorithmSuite( + defaultSymmetricKeyAlgorithms, + defaultHashAlgorithms, + defaultCompressionAlgorithms) + } + +} \ No newline at end of file From fb235d78109e2e0fc2c4ddd6e75efcbd3dd3613e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 17:34:52 +0200 Subject: [PATCH 021/155] Kotlin conversion: HashAlgorithmNegotiator --- .../negotiation/HashAlgorithmNegotiator.java | 70 ------------------ .../negotiation/HashAlgorithmNegotiator.kt | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 70 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.java deleted file mode 100644 index 18fe53f9..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.java +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm.negotiation; - -import java.util.Set; - -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.policy.Policy; - -/** - * Interface for a class that negotiates {@link HashAlgorithm HashAlgorithms}. - * - * You can provide your own implementation using custom logic by implementing the - * {@link #negotiateHashAlgorithm(Set)} method. - */ -public interface HashAlgorithmNegotiator { - - /** - * Pick one {@link HashAlgorithm} from the ordered set of acceptable algorithms. - * - * @param orderedHashAlgorithmPreferencesSet hash algorithm preferences - * @return picked algorithms - */ - HashAlgorithm negotiateHashAlgorithm(Set orderedHashAlgorithmPreferencesSet); - - /** - * Return an instance that negotiates {@link HashAlgorithm HashAlgorithms} used for non-revocation signatures - * based on the given {@link Policy}. - * - * @param policy algorithm policy - * @return negotiator - */ - static HashAlgorithmNegotiator negotiateSignatureHashAlgorithm(Policy policy) { - return negotiateByPolicy(policy.getSignatureHashAlgorithmPolicy()); - } - - /** - * Return an instance that negotiates {@link HashAlgorithm HashAlgorithms} used for revocation signatures - * based on the given {@link Policy}. - * - * @param policy algorithm policy - * @return negotiator - */ - static HashAlgorithmNegotiator negotiateRevocationSignatureAlgorithm(Policy policy) { - return negotiateByPolicy(policy.getRevocationSignatureHashAlgorithmPolicy()); - } - - /** - * Return an instance that negotiates {@link HashAlgorithm HashAlgorithms} based on the given - * {@link Policy.HashAlgorithmPolicy}. - * - * @param hashAlgorithmPolicy algorithm policy for hash algorithms - * @return negotiator - */ - static HashAlgorithmNegotiator negotiateByPolicy(Policy.HashAlgorithmPolicy hashAlgorithmPolicy) { - return new HashAlgorithmNegotiator() { - @Override - public HashAlgorithm negotiateHashAlgorithm(Set orderedPreferencesSet) { - for (HashAlgorithm preference : orderedPreferencesSet) { - if (hashAlgorithmPolicy.isAcceptable(preference)) { - return preference; - } - } - return hashAlgorithmPolicy.defaultHashAlgorithm(); - } - }; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt new file mode 100644 index 00000000..98cbe522 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm.negotiation + +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.policy.Policy + +/** + * Interface for a class that negotiates [HashAlgorithms][HashAlgorithm]. + * + * You can provide your own implementation using custom logic by implementing the + * [negotiateHashAlgorithm(Set)] method. + */ +interface HashAlgorithmNegotiator { + + /** + * Pick one [HashAlgorithm] from the ordered set of acceptable algorithms. + * + * @param orderedPrefs hash algorithm preferences + * @return picked algorithms + */ + fun negotiateHashAlgorithm(orderedPrefs: Set): HashAlgorithm + + companion object { + + /** + * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for non-revocation signatures + * based on the given [Policy]. + * + * @param policy algorithm policy + * @return negotiator + */ + @JvmStatic + fun negotiateSignatureHashAlgorithm(policy: Policy): HashAlgorithmNegotiator { + return negotiateByPolicy(policy.signatureHashAlgorithmPolicy) + } + + /** + * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation signatures + * based on the given [Policy]. + * + * @param policy algorithm policy + * @return negotiator + */ + @JvmStatic + fun negotiateRevocationSignatureAlgorithm(policy: Policy): HashAlgorithmNegotiator { + return negotiateByPolicy(policy.revocationSignatureHashAlgorithmPolicy) + } + + /** + * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] based on the given + * [Policy.HashAlgorithmPolicy]. + * + * @param hashAlgorithmPolicy algorithm policy for hash algorithms + * @return negotiator + */ + @JvmStatic + fun negotiateByPolicy(hashAlgorithmPolicy: Policy.HashAlgorithmPolicy): HashAlgorithmNegotiator { + return object: HashAlgorithmNegotiator { + override fun negotiateHashAlgorithm(orderedPrefs: Set): HashAlgorithm { + return orderedPrefs.firstOrNull { + hashAlgorithmPolicy.isAcceptable(it) + } ?: hashAlgorithmPolicy.defaultHashAlgorithm() + } + + } + } + } +} \ No newline at end of file From cf7a7f3aca8c2c071c283b26e6dd5e5b1d320d2f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 18:14:09 +0200 Subject: [PATCH 022/155] Kotlin conversion: SymmetricKeyAlgorithmNegotiator --- .../SymmetricKeyAlgorithmNegotiator.java | 105 ------------------ .../SymmetricKeyAlgorithmNegotiator.kt | 77 +++++++++++++ 2 files changed, 77 insertions(+), 105 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.java deleted file mode 100644 index de8ced24..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.java +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.algorithm.negotiation; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.policy.Policy; - -/** - * Interface for symmetric key algorithm negotiation. - */ -public interface SymmetricKeyAlgorithmNegotiator { - - /** - * Negotiate a symmetric encryption algorithm. - * If the override is non-null, it will be returned instead of performing an actual negotiation. - * Otherwise, the list of ordered sets containing the preferences of different recipient keys will be - * used to determine a suitable symmetric encryption algorithm. - * - * @param policy algorithm policy - * @param override algorithm override (if not null, return this) - * @param keyPreferences list of preferences per key - * @return negotiated algorithm - */ - SymmetricKeyAlgorithm negotiate( - Policy.SymmetricKeyAlgorithmPolicy policy, - SymmetricKeyAlgorithm override, - List> keyPreferences); - - /** - * Return an instance that negotiates a {@link SymmetricKeyAlgorithm} by selecting the most popular acceptable - * algorithm from the list of preferences. - * - * This negotiator has the best chances to select an algorithm which is understood by all recipients. - * - * @return negotiator that selects by popularity - */ - static SymmetricKeyAlgorithmNegotiator byPopularity() { - return new SymmetricKeyAlgorithmNegotiator() { - @Override - public SymmetricKeyAlgorithm negotiate( - Policy.SymmetricKeyAlgorithmPolicy policy, - SymmetricKeyAlgorithm override, - List> preferences) { - if (override == SymmetricKeyAlgorithm.NULL) { - throw new IllegalArgumentException("Algorithm override cannot be NULL (plaintext)."); - } - - if (override != null) { - return override; - } - - // Count score (occurrences) of each algorithm - Map supportWeight = new LinkedHashMap<>(); - for (Set keyPreferences : preferences) { - for (SymmetricKeyAlgorithm preferred : keyPreferences) { - if (supportWeight.containsKey(preferred)) { - supportWeight.put(preferred, supportWeight.get(preferred) + 1); - } else { - supportWeight.put(preferred, 1); - } - } - } - - // Pivot the score map - Map> byScore = new HashMap<>(); - for (SymmetricKeyAlgorithm algorithm : supportWeight.keySet()) { - int score = supportWeight.get(algorithm); - List withSameScore = byScore.get(score); - if (withSameScore == null) { - withSameScore = new ArrayList<>(); - byScore.put(score, withSameScore); - } - withSameScore.add(algorithm); - } - - List scores = new ArrayList<>(byScore.keySet()); - - // Sort map and iterate from highest to lowest score - Collections.sort(scores); - for (int i = scores.size() - 1; i >= 0; i--) { - int score = scores.get(i); - List withSameScore = byScore.get(score); - // Select best algorithm - SymmetricKeyAlgorithm best = policy.selectBest(withSameScore); - if (best != null) { - return best; - } - } - - // If no algorithm is acceptable, choose fallback - return policy.getDefaultSymmetricKeyAlgorithm(); - } - }; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt new file mode 100644 index 00000000..1a6dfe7f --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm.negotiation + +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.policy.Policy +import java.lang.IllegalArgumentException + +interface SymmetricKeyAlgorithmNegotiator { + + /** + * Negotiate a symmetric encryption algorithm. + * If the override is non-null, it will be returned instead of performing an actual negotiation. + * Otherwise, the list of ordered sets containing the preferences of different recipient keys will be + * used to determine a suitable symmetric encryption algorithm. + * + * @param policy algorithm policy + * @param override algorithm override (if not null, return this) + * @param keyPreferences list of preferences per key + * @return negotiated algorithm + */ + fun negotiate(policy: Policy.SymmetricKeyAlgorithmPolicy, + override: SymmetricKeyAlgorithm?, + keyPreferences: List>): SymmetricKeyAlgorithm + + companion object { + @JvmStatic + fun byPopularity(): SymmetricKeyAlgorithmNegotiator { + return object: SymmetricKeyAlgorithmNegotiator { + override fun negotiate( + policy: Policy.SymmetricKeyAlgorithmPolicy, + override: SymmetricKeyAlgorithm?, + keyPreferences: List>): + SymmetricKeyAlgorithm { + if (override == SymmetricKeyAlgorithm.NULL) { + throw IllegalArgumentException("Algorithm override cannot be NULL (plaintext).") + } + + if (override != null) { + return override + } + + // algorithm to #occurrences + val supportWeight = buildMap { + keyPreferences.forEach { keyPreference -> + keyPreference.forEach { pref -> + put(pref, getOrDefault(pref, 0) as Int + 1) + } + } + } + + // Pivot map and sort by popularity ascending + // score to list(algo) + val byScore = supportWeight.toList() + .map { e -> e.second to e.first } + .groupBy { e -> e.first } + .map { e -> e.key to e.value.map { it.second }.toList() } + .associate { e -> e } + .toSortedMap() + + // iterate in reverse over algorithms + for (e in byScore.entries.reversed()) { + val best = policy.selectBest(e.value) + if (best != null) { + return best + } + } + + return policy.defaultSymmetricKeyAlgorithm + } + + } + } + } +} \ No newline at end of file From 5a5b604411e123a4b5e7a0ba48a48d58a802fcd8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 18:14:17 +0200 Subject: [PATCH 023/155] Add missing license header --- .../src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt b/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt index 9bb2182e..0e4997fc 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.algorithm class AlgorithmSuite( From b91e19fc39f8d2ec3f92aa5924740472eca26f04 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Aug 2023 18:36:57 +0200 Subject: [PATCH 024/155] Kotlin conversion: PGPainless --- .../main/java/org/pgpainless/PGPainless.java | 238 ------------------ .../main/java/org/pgpainless/PGPainless.kt | 171 +++++++++++++ 2 files changed, 171 insertions(+), 238 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/PGPainless.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/PGPainless.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java deleted file mode 100644 index 3da54177..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Date; -import javax.annotation.Nonnull; - -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.decryption_verification.DecryptionBuilder; -import org.pgpainless.decryption_verification.DecryptionStream; -import org.pgpainless.encryption_signing.EncryptionBuilder; -import org.pgpainless.encryption_signing.EncryptionStream; -import org.pgpainless.key.certification.CertifyCertificate; -import org.pgpainless.key.generation.KeyRingBuilder; -import org.pgpainless.key.generation.KeyRingTemplates; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor; -import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface; -import org.pgpainless.key.parsing.KeyRingReader; -import org.pgpainless.key.util.KeyRingUtils; -import org.pgpainless.policy.Policy; -import org.pgpainless.util.ArmorUtils; - -public final class PGPainless { - - private PGPainless() { - - } - - /** - * Generate a fresh OpenPGP key ring from predefined templates. - * @return templates - */ - @Nonnull - public static KeyRingTemplates generateKeyRing() { - return new KeyRingTemplates(); - } - - /** - * Build a custom OpenPGP key ring. - * - * @return builder - */ - @Nonnull - public static KeyRingBuilder buildKeyRing() { - return new KeyRingBuilder(); - } - - /** - * Read an existing OpenPGP key ring. - * @return builder - */ - @Nonnull - public static KeyRingReader readKeyRing() { - return new KeyRingReader(); - } - - /** - * Extract a public key certificate from a secret key. - * - * @param secretKey secret key - * @return public key certificate - */ - @Nonnull - public static PGPPublicKeyRing extractCertificate(@Nonnull PGPSecretKeyRing secretKey) { - return KeyRingUtils.publicKeyRingFrom(secretKey); - } - - /** - * Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key server) together. - * - * @param originalCopy local, older copy of the cert - * @param updatedCopy updated, newer copy of the cert - * @return merged certificate - * @throws PGPException in case of an error - */ - @Nonnull - public static PGPPublicKeyRing mergeCertificate( - @Nonnull PGPPublicKeyRing originalCopy, - @Nonnull PGPPublicKeyRing updatedCopy) - throws PGPException { - return PGPPublicKeyRing.join(originalCopy, updatedCopy); - } - - /** - * Wrap a key or certificate in ASCII armor. - * - * @param key key or certificate - * @return ascii armored string - * - * @throws IOException in case of an error in the {@link ArmoredOutputStream} - */ - @Nonnull - public static String asciiArmor(@Nonnull PGPKeyRing key) - throws IOException { - if (key instanceof PGPSecretKeyRing) { - return ArmorUtils.toAsciiArmoredString((PGPSecretKeyRing) key); - } else { - return ArmorUtils.toAsciiArmoredString((PGPPublicKeyRing) key); - } - } - - /** - * Wrap the detached signature in ASCII armor. - * - * @param signature detached signature - * @return ascii armored string - * - * @throws IOException in case of an error in the {@link ArmoredOutputStream} - */ - @Nonnull - public static String asciiArmor(@Nonnull PGPSignature signature) - throws IOException { - return ArmorUtils.toAsciiArmoredString(signature); - } - - /** - * Wrap a key of certificate in ASCII armor and write the result into the given {@link OutputStream}. - * - * @param key key or certificate - * @param outputStream output stream - * - * @throws IOException in case of an error ion the {@link ArmoredOutputStream} - */ - public static void asciiArmor(@Nonnull PGPKeyRing key, @Nonnull OutputStream outputStream) - throws IOException { - ArmoredOutputStream armorOut = ArmorUtils.toAsciiArmoredStream(key, outputStream); - key.encode(armorOut); - armorOut.close(); - } - - /** - * Create an {@link EncryptionStream}, which can be used to encrypt and/or sign data using OpenPGP. - * - * @return builder - */ - @Nonnull - public static EncryptionBuilder encryptAndOrSign() { - return new EncryptionBuilder(); - } - - /** - * Create a {@link DecryptionStream}, which can be used to decrypt and/or verify data using OpenPGP. - * - * @return builder - */ - @Nonnull - public static DecryptionBuilder decryptAndOrVerify() { - return new DecryptionBuilder(); - } - - /** - * Make changes to a secret key. - * This method can be used to change key expiration dates and passphrases, or add/revoke subkeys. - *

- * After making the desired changes in the builder, the modified key ring can be extracted using {@link SecretKeyRingEditorInterface#done()}. - * - * @param secretKeys secret key ring - * @return builder - */ - @Nonnull - public static SecretKeyRingEditorInterface modifyKeyRing(@Nonnull PGPSecretKeyRing secretKeys) { - return modifyKeyRing(secretKeys, new Date()); - } - - /** - * Make changes to a secret key at the given reference time. - * This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys. - *

- * After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}. - * - * @param secretKeys secret key ring - * @param referenceTime reference time used as signature creation date - * @return builder - */ - @Nonnull - public static SecretKeyRingEditorInterface modifyKeyRing(@Nonnull PGPSecretKeyRing secretKeys, - @Nonnull Date referenceTime) { - return new SecretKeyRingEditor(secretKeys, referenceTime); - } - - /** - * Quickly access information about a {@link org.bouncycastle.openpgp.PGPPublicKeyRing} / {@link PGPSecretKeyRing}. - * This method can be used to determine expiration dates, key flags and other information about a key. - *

- * To evaluate a key at a given date (e.g. to determine if the key was allowed to create a certain signature) - * use {@link #inspectKeyRing(PGPKeyRing, Date)} instead. - * - * @param keyRing key ring - * @return access object - */ - @Nonnull - public static KeyRingInfo inspectKeyRing(@Nonnull PGPKeyRing keyRing) { - return new KeyRingInfo(keyRing); - } - - /** - * Quickly access information about a {@link org.bouncycastle.openpgp.PGPPublicKeyRing} / {@link PGPSecretKeyRing}. - * This method can be used to determine expiration dates, key flags and other information about a key at a specific time. - * - * @param keyRing key ring - * @param referenceTime date of inspection - * @return access object - */ - @Nonnull - public static KeyRingInfo inspectKeyRing(@Nonnull PGPKeyRing keyRing, @Nonnull Date referenceTime) { - return new KeyRingInfo(keyRing, referenceTime); - } - - /** - * Access, and make changes to PGPainless policy on acceptable/default algorithms etc. - * - * @return policy - */ - @Nonnull - public static Policy getPolicy() { - return Policy.getInstance(); - } - - /** - * Create different kinds of signatures on other keys. - * - * @return builder - */ - @Nonnull - public static CertifyCertificate certify() { - return new CertifyCertificate(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.kt b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.kt new file mode 100644 index 00000000..a52b38ae --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.kt @@ -0,0 +1,171 @@ +package org.pgpainless + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.decryption_verification.DecryptionBuilder +import org.pgpainless.encryption_signing.EncryptionBuilder +import org.pgpainless.key.certification.CertifyCertificate +import org.pgpainless.key.generation.KeyRingBuilder +import org.pgpainless.key.generation.KeyRingTemplates +import org.pgpainless.key.info.KeyRingInfo +import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor +import org.pgpainless.key.parsing.KeyRingReader +import org.pgpainless.key.util.KeyRingUtils +import org.pgpainless.policy.Policy +import org.pgpainless.util.ArmorUtils +import java.io.OutputStream +import java.util.* + +class PGPainless private constructor() { + + companion object { + + /** + * Generate a fresh OpenPGP key ring from predefined templates. + * @return templates + */ + @JvmStatic + fun generateKeyRing() = KeyRingTemplates() + + /** + * Build a custom OpenPGP key ring. + * + * @return builder + */ + @JvmStatic + fun buildKeyRing() = KeyRingBuilder() + + /** + * Read an existing OpenPGP key ring. + * @return builder + */ + @JvmStatic + fun readKeyRing() = KeyRingReader() + + /** + * Extract a public key certificate from a secret key. + * + * @param secretKey secret key + * @return public key certificate + */ + @JvmStatic + fun extractCertificate(secretKey: PGPSecretKeyRing) = + KeyRingUtils.publicKeyRingFrom(secretKey) + + /** + * Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key server) together. + * + * @param originalCopy local, older copy of the cert + * @param updatedCopy updated, newer copy of the cert + * @return merged certificate + * @throws PGPException in case of an error + */ + @JvmStatic + fun mergeCertificate(originalCopy: PGPPublicKeyRing, + updatedCopy: PGPPublicKeyRing) = + PGPPublicKeyRing.join(originalCopy, updatedCopy) + + /** + * Wrap a key or certificate in ASCII armor. + * + * @param key key or certificate + * @return ascii armored string + * + * @throws IOException in case of an error during the armoring process + */ + @JvmStatic + fun asciiArmor(key: PGPKeyRing) = + if (key is PGPSecretKeyRing) + ArmorUtils.toAsciiArmoredString(key) + else + ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing) + + /** + * Wrap a key of certificate in ASCII armor and write the result into the given [OutputStream]. + * + * @param key key or certificate + * @param outputStream output stream + * + * @throws IOException in case of an error during the armoring process + */ + @JvmStatic + fun asciiArmor(key: PGPKeyRing, outputStream: OutputStream) { + val armorOut = ArmorUtils.toAsciiArmoredStream(key, outputStream) + key.encode(armorOut) + armorOut.close() + } + + /** + * Wrap the detached signature in ASCII armor. + * + * @param signature detached signature + * @return ascii armored string + * + * @throws IOException in case of an error during the armoring process + */ + @JvmStatic + fun asciiArmor(signature: PGPSignature) = ArmorUtils.toAsciiArmoredString(signature) + + /** + * Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using OpenPGP. + * + * @return builder + */ + @JvmStatic + fun encryptAndOrSign() = EncryptionBuilder() + + /** + * Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using OpenPGP. + * + * @return builder + */ + @JvmStatic + fun decryptAndOrVerify() = DecryptionBuilder() + + /** + * Make changes to a secret key at the given reference time. + * This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys. + *

+ * After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}. + * + * @param secretKeys secret key ring + * @param referenceTime reference time used as signature creation date + * @return builder + */ + @JvmStatic + @JvmOverloads + fun modifyKeyRing(secretKey: PGPSecretKeyRing, referenceTime: Date = Date()) = + SecretKeyRingEditor(secretKey, referenceTime) + + /** + * Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] / [PGPSecretKeyRing]. + * This method can be used to determine expiration dates, key flags and other information about a key at a specific time. + * + * @param keyRing key ring + * @param referenceTime date of inspection + * @return access object + */ + @JvmStatic + @JvmOverloads + fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) = + KeyRingInfo(key, referenceTime) + + /** + * Access, and make changes to PGPainless policy on acceptable/default algorithms etc. + * + * @return policy + */ + @JvmStatic + fun getPolicy() = Policy.getInstance() + + /** + * Create different kinds of signatures on other keys. + * + * @return builder + */ + @JvmStatic + fun certify() = CertifyCertificate() + } +} \ No newline at end of file From 075ca4baa3a2997b9519521a68040393e3ecee68 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Aug 2023 14:31:50 +0200 Subject: [PATCH 025/155] Kotlin conversion: CertificateAuthenticity --- .../CertificateAuthenticity.java | 139 ------------------ .../authentication/CertificateAuthenticity.kt | 57 +++++++ 2 files changed, 57 insertions(+), 139 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.java b/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.java deleted file mode 100644 index ac059646..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.java +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.authentication; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPPublicKeyRing; - -public class CertificateAuthenticity { - - private final String userId; - private final PGPPublicKeyRing certificate; - private final Map certificationChains = new HashMap<>(); - private final int targetAmount; - - public CertificateAuthenticity(@Nonnull PGPPublicKeyRing certificate, - @Nonnull String userId, - @Nonnull Map certificationChains, - int targetAmount) { - this.userId = userId; - this.certificate = certificate; - this.certificationChains.putAll(certificationChains); - this.targetAmount = targetAmount; - } - - @Nonnull - public String getUserId() { - return userId; - } - - @Nonnull - public PGPPublicKeyRing getCertificate() { - return certificate; - } - - public int getTotalTrustAmount() { - int total = 0; - for (int v : certificationChains.values()) { - total += v; - } - return total; - } - - /** - * Return the degree of authentication of the binding in percent. - * 100% means full authentication. - * Values smaller than 100% mean partial authentication. - * - * @return authenticity in percent - */ - public int getAuthenticityPercentage() { - return targetAmount * 100 / getTotalTrustAmount(); - } - - /** - * Return true, if the binding is authenticated to a sufficient degree. - * - * @return true if total gathered evidence outweighs the target trust amount. - */ - public boolean isAuthenticated() { - return targetAmount <= getTotalTrustAmount(); - } - - /** - * Return a map of {@link CertificationChain CertificationChains} and their respective effective trust amount. - * The effective trust amount of a path might be smaller than its actual trust amount, for example if nodes of a - * path are used multiple times. - * - * @return map of certification chains and their effective trust amounts - */ - @Nonnull - public Map getCertificationChains() { - return Collections.unmodifiableMap(certificationChains); - } - - public static class CertificationChain { - private final int trustAmount; - private final List chainLinks = new ArrayList<>(); - - /** - * A chain of certifications. - * - * @param trustAmount actual trust amount of the chain - * @param chainLinks links of the chain, starting at the trust-root, ending at the target. - */ - public CertificationChain(int trustAmount, @Nonnull List chainLinks) { - this.trustAmount = trustAmount; - this.chainLinks.addAll(chainLinks); - } - - /** - * Actual trust amount of the certification chain. - * @return trust amount - */ - public int getTrustAmount() { - return trustAmount; - } - - /** - * Return all links in the chain, starting at the trust-root and ending at the target. - * @return chain links - */ - @Nonnull - public List getChainLinks() { - return Collections.unmodifiableList(chainLinks); - } - } - - /** - * A chain link contains a node in the trust chain. - */ - public static class ChainLink { - private final PGPPublicKeyRing certificate; - - /** - * Create a chain link. - * @param certificate node in the trust chain - */ - public ChainLink(@Nonnull PGPPublicKeyRing certificate) { - this.certificate = certificate; - } - - /** - * Return the certificate that belongs to the node. - * @return certificate - */ - @Nonnull - public PGPPublicKeyRing getCertificate() { - return certificate; - } - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.kt b/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.kt new file mode 100644 index 00000000..2024c710 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.kt @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.authentication + +import org.bouncycastle.openpgp.PGPPublicKeyRing + +class CertificateAuthenticity(val userId: String, + val certificate: PGPPublicKeyRing, + val certificationChains: Map, + val targetAmount: Int) { + + val totalTrustAmount: Int + get() = certificationChains.values.sum() + + + /** + * Return the degree of authentication of the binding in percent. + * 100% means full authentication. + * Values smaller than 100% mean partial authentication. + * + * @return authenticity in percent + */ + val authenticityPercentage: Int + get() = targetAmount * 100 / totalTrustAmount + + /** + * Return true, if the binding is authenticated to a sufficient degree. + * + * @return true if total gathered evidence outweighs the target trust amount. + */ + val authenticated: Boolean + get() = targetAmount <= totalTrustAmount + + fun isAuthenticated() = authenticated +} + +/** + * A chain of certifications. + * + * @param trustAmount actual trust amount of the chain + * @param chainLinks links of the chain, starting at the trust-root, ending at the target. + */ +class CertificationChain( + val trustAmount: Int, + val chainLinks: List) { + +} + +/** + * A chain link contains a node in the trust chain. + */ +class ChainLink( + val certificate: PGPPublicKeyRing) { + +} \ No newline at end of file From 58951ce19a0c6c5993ea5c6e36be0a6bfee1f80a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Aug 2023 14:32:28 +0200 Subject: [PATCH 026/155] Get rid of animalsniffer --- build.gradle | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/build.gradle b/build.gradle index a0f2d0e4..540037a8 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,6 @@ buildscript { } plugins { - id 'ru.vyarus.animalsniffer' version '1.5.3' id 'org.jetbrains.kotlin.jvm' version "1.8.10" } @@ -43,18 +42,6 @@ allprojects { onlyIf { !sourceSets.main.allSource.files.isEmpty() } } - // For library modules, enable android api compatibility check - if (it.name != 'pgpainless-cli') { - // animalsniffer - apply plugin: 'ru.vyarus.animalsniffer' - dependencies { - signature "net.sf.androidscents.signature:android-api-level-${pgpainlessMinAndroidSdk}:2.3.3_r2@signature" - } - animalsniffer { - sourceSets = [sourceSets.main] - } - } - // checkstyle checkstyle { toolVersion = '10.12.1' From 84e554fc0d5561871ddbc8bf9ed2b6cbb18613f8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Aug 2023 14:38:38 +0200 Subject: [PATCH 027/155] Kotlin conversion: CertificateAuthority --- ...Authority.java => CertificateAuthority.kt} | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) rename pgpainless-core/src/main/java/org/pgpainless/authentication/{CertificateAuthority.java => CertificateAuthority.kt} (69%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthority.java b/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthority.kt similarity index 69% rename from pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthority.java rename to pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthority.kt index 36bf9e5f..e510f48a 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthority.java +++ b/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthority.kt @@ -4,11 +4,8 @@ package org.pgpainless.authentication; -import org.pgpainless.key.OpenPgpFingerprint; - -import javax.annotation.Nonnull; -import java.util.Date; -import java.util.List; +import org.pgpainless.key.OpenPgpFingerprint +import java.util.* /** * Interface for a CA that can authenticate trust-worthy certificates. @@ -17,7 +14,7 @@ import java.util.List; * @see PGPainless-WOT * @see OpenPGP Web of Trust */ -public interface CertificateAuthority { +interface CertificateAuthority { /** * Determine the authenticity of the binding between the given fingerprint and the userId. @@ -33,11 +30,11 @@ public interface CertificateAuthority { * 60 = partially authenticated...) * @return information about the authenticity of the binding */ - CertificateAuthenticity authenticateBinding(@Nonnull OpenPgpFingerprint fingerprint, - @Nonnull String userId, - boolean email, - @Nonnull Date referenceTime, - int targetAmount); + fun authenticateBinding(fingerprint: OpenPgpFingerprint, + userId: String, + email: Boolean, + referenceTime: Date, + targetAmount: Int): CertificateAuthenticity; /** * Lookup certificates, which carry a trustworthy binding to the given userId. @@ -50,10 +47,10 @@ public interface CertificateAuthority { * 60 = partially authenticated...) * @return list of identified bindings */ - List lookupByUserId(@Nonnull String userId, - boolean email, - @Nonnull Date referenceTime, - int targetAmount); + fun lookupByUserId(userId: String, + email: Boolean, + referenceTime: Date, + targetAmount: Int): List /** * Identify trustworthy bindings for a certificate. @@ -65,7 +62,7 @@ public interface CertificateAuthority { * 60 = partially authenticated...) * @return list of identified bindings */ - List identifyByFingerprint(@Nonnull OpenPgpFingerprint fingerprint, - @Nonnull Date referenceTime, - int targetAmount); + fun identifyByFingerprint(fingerprint: OpenPgpFingerprint, + referenceTime: Date, + targetAmount: Int): List } From 344421a0f48834040e3971ba1ade83b2d3c6efb1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Aug 2023 15:56:44 +0200 Subject: [PATCH 028/155] Kolin conversion: Some OpenPgpFingerprint classes --- .../pgpainless/key/OpenPgpFingerprint.java | 189 ------------------ .../org/pgpainless/key/OpenPgpFingerprint.kt | 158 +++++++++++++++ .../pgpainless/key/OpenPgpV4Fingerprint.java | 149 -------------- .../pgpainless/key/OpenPgpV4Fingerprint.kt | 65 ++++++ .../pgpainless/key/_64DigitFingerprint.java | 119 ----------- .../org/pgpainless/key/_64DigitFingerprint.kt | 74 +++++++ 6 files changed, 297 insertions(+), 457 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.kt delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.kt delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java deleted file mode 100644 index 13804061..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key; - -import java.nio.charset.Charset; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.util.encoders.Hex; - -/** - * Abstract super class of different version OpenPGP fingerprints. - * - */ -public abstract class OpenPgpFingerprint implements CharSequence, Comparable { - @SuppressWarnings("CharsetObjectCanBeUsed") - protected static final Charset utf8 = Charset.forName("UTF-8"); - protected final String fingerprint; - - /** - * Return the fingerprint of the given key. - * This method automatically matches key versions to fingerprint implementations. - * - * @param key key - * @return fingerprint - */ - public static OpenPgpFingerprint of(PGPSecretKey key) { - return of(key.getPublicKey()); - } - - /** - * Return the fingerprint of the given key. - * This method automatically matches key versions to fingerprint implementations. - * - * @param key key - * @return fingerprint - */ - public static OpenPgpFingerprint of(PGPPublicKey key) { - if (key.getVersion() == 4) { - return new OpenPgpV4Fingerprint(key); - } - if (key.getVersion() == 5) { - return new OpenPgpV5Fingerprint(key); - } - if (key.getVersion() == 6) { - return new OpenPgpV6Fingerprint(key); - } - throw new IllegalArgumentException("OpenPGP keys of version " + key.getVersion() + " are not supported."); - } - - /** - * Return the fingerprint of the primary key of the given key ring. - * This method automatically matches key versions to fingerprint implementations. - * - * @param ring key ring - * @return fingerprint - */ - public static OpenPgpFingerprint of(PGPKeyRing ring) { - return of(ring.getPublicKey()); - } - - /** - * Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. - * If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint. - * In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended - * to know the version of the key beforehand. - * - * @param fingerprint fingerprint - * @return parsed fingerprint - * @deprecated Use the constructor methods of the versioned fingerprint subclasses instead. - */ - @Deprecated - public static OpenPgpFingerprint parse(String fingerprint) { - String fp = fingerprint.replace(" ", "").trim().toUpperCase(); - if (fp.matches("^[0-9A-F]{40}$")) { - return new OpenPgpV4Fingerprint(fp); - } - if (fp.matches("^[0-9A-F]{64}$")) { - // Might be v5 or v6 :/ - return new _64DigitFingerprint(fp); - } - throw new IllegalArgumentException("Fingerprint does not appear to match any known fingerprint patterns."); - } - - /** - * Parse a binary OpenPGP fingerprint into an {@link OpenPgpFingerprint} object. - * - * @param binaryFingerprint binary representation of the fingerprint - * @return parsed fingerprint - * @deprecated use the parse() methods of the versioned fingerprint subclasses instead. - */ - @Deprecated - public static OpenPgpFingerprint parseFromBinary(byte[] binaryFingerprint) { - String hex = Hex.toHexString(binaryFingerprint).toUpperCase(); - return parse(hex); - } - - public OpenPgpFingerprint(String fingerprint) { - String fp = fingerprint.replace(" ", "").trim().toUpperCase(); - if (!isValid(fp)) { - throw new IllegalArgumentException( - String.format("Fingerprint '%s' does not appear to be a valid OpenPGP V%d fingerprint.", fingerprint, getVersion()) - ); - } - this.fingerprint = fp; - } - - public OpenPgpFingerprint(@Nonnull byte[] bytes) { - this(new String(bytes, utf8)); - } - - public OpenPgpFingerprint(PGPPublicKey key) { - this(Hex.encode(key.getFingerprint())); - if (key.getVersion() != getVersion()) { - throw new IllegalArgumentException(String.format("Key is not a v%d OpenPgp key.", getVersion())); - } - } - - public OpenPgpFingerprint(@Nonnull PGPPublicKeyRing ring) { - this(ring.getPublicKey()); - } - - public OpenPgpFingerprint(@Nonnull PGPSecretKeyRing ring) { - this(ring.getPublicKey()); - } - - public OpenPgpFingerprint(@Nonnull PGPKeyRing ring) { - this(ring.getPublicKey()); - } - - /** - * Return the version of the fingerprint. - * - * @return version - */ - public abstract int getVersion(); - - /** - * Check, whether the fingerprint consists of 40 valid hexadecimal characters. - * @param fp fingerprint to check. - * @return true if fingerprint is valid. - */ - protected abstract boolean isValid(@Nonnull String fp); - - /** - * Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. - * This method can be implemented for V4 and V5 fingerprints. - * V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated. - * - * @see - * RFC-4880 §12.2: Key IDs and Fingerprints - * @return key id - */ - public abstract long getKeyId(); - - @Override - public int length() { - return fingerprint.length(); - } - - @Override - public char charAt(int i) { - return fingerprint.charAt(i); - } - - @Override - public CharSequence subSequence(int i, int i1) { - return fingerprint.subSequence(i, i1); - } - - @Override - @Nonnull - public String toString() { - return fingerprint; - } - - /** - * Return a pretty printed representation of the fingerprint. - * - * @return pretty printed fingerprint - */ - public abstract String prettyPrint(); -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.kt b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.kt new file mode 100644 index 00000000..9c7409de --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.kt @@ -0,0 +1,158 @@ +package org.pgpainless.key + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.util.encoders.Hex +import java.nio.charset.Charset + +/** + * Abstract super class of different version OpenPGP fingerprints. + * + */ +abstract class OpenPgpFingerprint : CharSequence, Comparable { + val fingerprint: String + + /** + * Return the version of the fingerprint. + * + * @return version + */ + abstract fun getVersion(): Int + + /** + * Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. + * This method can be implemented for V4 and V5 fingerprints. + * V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated. + * + * @see + * RFC-4880 §12.2: Key IDs and Fingerprints + * @return key id + */ + abstract val keyId: Long + + constructor(fingerprint: String) { + val prep = fingerprint.replace(" ", "").trim().uppercase() + if (!isValid(prep)) { + throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.") + } + this.fingerprint = prep + } + + constructor(bytes: ByteArray): this(Hex.toHexString(bytes)) + + constructor(key: PGPPublicKey): this(key.fingerprint) { + if (key.version != getVersion()) { + throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.") + } + } + + constructor(key: PGPSecretKey): this(key.publicKey) + + constructor(keys: PGPKeyRing): this(keys.publicKey) + + /** + * Check, whether the fingerprint consists of 40 valid hexadecimal characters. + * @param fp fingerprint to check. + * @return true if fingerprint is valid. + */ + protected abstract fun isValid(fingerprint: String): Boolean + + override val length: Int + get() = fingerprint.length + + override fun get(index: Int) = fingerprint.get(index) + + override fun subSequence(startIndex: Int, endIndex: Int) = fingerprint.subSequence(startIndex, endIndex) + override fun compareTo(other: OpenPgpFingerprint): Int { + return fingerprint.compareTo(other.fingerprint) + } + + override fun equals(other: Any?): Boolean { + return toString() == other.toString() + } + + override fun hashCode(): Int { + return toString().hashCode() + } + + override fun toString(): String = fingerprint + + abstract fun prettyPrint(): String + + companion object { + @JvmStatic + val utf8: Charset = Charset.forName("UTF-8") + + /** + * Return the fingerprint of the given key. + * This method automatically matches key versions to fingerprint implementations. + * + * @param key key + * @return fingerprint + */ + @JvmStatic + fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey) + + /** + * Return the fingerprint of the given key. + * This method automatically matches key versions to fingerprint implementations. + * + * @param key key + * @return fingerprint + */ + @JvmStatic + fun of (key: PGPPublicKey): OpenPgpFingerprint = when(key.version) { + 4 -> OpenPgpV4Fingerprint(key) + 5 -> OpenPgpV5Fingerprint(key) + 6 -> OpenPgpV6Fingerprint(key) + else -> throw IllegalArgumentException("OpenPGP keys of version ${key.version} are not supported.") + } + + /** + * Return the fingerprint of the primary key of the given key ring. + * This method automatically matches key versions to fingerprint implementations. + * + * @param ring key ring + * @return fingerprint + */ + @JvmStatic + fun of (keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey) + + /** + * Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. + * If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint. + * In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended + * to know the version of the key beforehand. + * + * @param fingerprint fingerprint + * @return parsed fingerprint + * @deprecated Use the constructor methods of the versioned fingerprint subclasses instead. + */ + @JvmStatic + @Deprecated("Use the constructor methods of the versioned fingerprint subclasses instead.") + fun parse(fingerprint: String): OpenPgpFingerprint { + val prep = fingerprint.replace(" ", "").trim().uppercase() + if (prep.matches("^[0-9A-F]{40}$".toRegex())) { + return OpenPgpV4Fingerprint(prep) + } + if (prep.matches("^[0-9A-F]{64}$".toRegex())) { + // Might be v5 or v6 :/ + return _64DigitFingerprint(prep) + } + throw IllegalArgumentException("Fingerprint does not appear to match any known fingerprint pattern.") + } + + /** + * Parse a binary OpenPGP fingerprint into an {@link OpenPgpFingerprint} object. + * + * @param binaryFingerprint binary representation of the fingerprint + * @return parsed fingerprint + * @deprecated use the parse() methods of the versioned fingerprint subclasses instead. + */ + @JvmStatic + @Deprecated("use the parse() methods of the versioned fingerprint subclasses instead.") + fun parseFromBinary(binaryFingerprint: ByteArray): OpenPgpFingerprint = + parse(Hex.toHexString(binaryFingerprint)) + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.java deleted file mode 100644 index 13a79201..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.java +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key; - -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.util.encoders.Hex; - -/** - * This class represents a hex encoded, uppercase OpenPGP v4 fingerprint. - */ -public class OpenPgpV4Fingerprint extends OpenPgpFingerprint { - - public static final String SCHEME = "openpgp4fpr"; - - /** - * Create an {@link OpenPgpV4Fingerprint}. - * - * @param fingerprint uppercase hexadecimal fingerprint of length 40 - */ - public OpenPgpV4Fingerprint(@Nonnull String fingerprint) { - super(fingerprint); - } - - public OpenPgpV4Fingerprint(@Nonnull byte[] bytes) { - super(Hex.encode(bytes)); - } - - public OpenPgpV4Fingerprint(@Nonnull PGPPublicKey key) { - super(key); - } - - public OpenPgpV4Fingerprint(@Nonnull PGPSecretKey key) { - this(key.getPublicKey()); - } - - public OpenPgpV4Fingerprint(@Nonnull PGPPublicKeyRing ring) { - super(ring); - } - - public OpenPgpV4Fingerprint(@Nonnull PGPSecretKeyRing ring) { - super(ring); - } - - public OpenPgpV4Fingerprint(@Nonnull PGPKeyRing ring) { - super(ring); - } - - @Override - public int getVersion() { - return 4; - } - - @Override - protected boolean isValid(@Nonnull String fp) { - return fp.matches("^[0-9A-F]{40}$"); - } - - @Override - public long getKeyId() { - byte[] bytes = Hex.decode(toString().getBytes(utf8)); - ByteBuffer buf = ByteBuffer.wrap(bytes); - - // The key id is the right-most 8 bytes (conveniently a long) - // We have to cast here in order to be compatible with java 8 - // https://github.com/eclipse/jetty.project/issues/3244 - ((Buffer) buf).position(12); // 20 - 8 bytes = offset 12 - - return buf.getLong(); - } - - @Override - public String prettyPrint() { - String fp = toString(); - StringBuilder pretty = new StringBuilder(); - for (int i = 0; i < 5; i++) { - pretty.append(fp, i * 4, (i + 1) * 4).append(' '); - } - pretty.append(' '); - for (int i = 5; i < 9; i++) { - pretty.append(fp, i * 4, (i + 1) * 4).append(' '); - } - pretty.append(fp, 36, 40); - return pretty.toString(); - } - - @Override - public boolean equals(Object other) { - if (other == null) { - return false; - } - - if (!(other instanceof CharSequence)) { - return false; - } - - return this.toString().equals(other.toString()); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - /** - * Return the fingerprint as an openpgp4fpr {@link URI}. - * An example would be 'openpgp4fpr:7F9116FEA90A5983936C7CFAA027DB2F3E1E118A'. - * - * @return openpgp4fpr fingerprint uri - * @see openpgp4fpr URI scheme - */ - public URI toUri() { - try { - return new URI(OpenPgpV4Fingerprint.SCHEME, toString(), null); - } catch (URISyntaxException e) { - throw new AssertionError(e); - } - } - - /** - * Convert an openpgp4fpr URI to an {@link OpenPgpV4Fingerprint}. - * - * @param uri {@link URI} with scheme 'openpgp4fpr' - * @return fingerprint parsed from the uri - * @see openpgp4fpr URI scheme - */ - public static OpenPgpV4Fingerprint fromUri(URI uri) { - if (!SCHEME.equals(uri.getScheme())) { - throw new IllegalArgumentException("URI scheme MUST equal '" + SCHEME + "'"); - } - return new OpenPgpV4Fingerprint(uri.getSchemeSpecificPart()); - } - - @Override - public int compareTo(@Nonnull OpenPgpFingerprint openPgpFingerprint) { - return toString().compareTo(openPgpFingerprint.toString()); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.kt b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.kt new file mode 100644 index 00000000..72c31f4e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.kt @@ -0,0 +1,65 @@ +package org.pgpainless.key + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.util.encoders.Hex +import java.net.URI +import java.nio.Buffer +import java.nio.ByteBuffer +import java.nio.charset.Charset + +class OpenPgpV4Fingerprint: OpenPgpFingerprint { + + constructor(fingerprint: String): super(fingerprint) + constructor(bytes: ByteArray): super(bytes) + constructor(key: PGPPublicKey): super(key) + constructor(key: PGPSecretKey): super(key) + constructor(keys: PGPKeyRing): super(keys) + + override fun getVersion() = 4 + + override val keyId: Long + get() { + val bytes = Hex.decode(toString().toByteArray(Charset.forName("UTF-8"))) + val buf = ByteBuffer.wrap(bytes) + + // The key id is the right-most 8 bytes (conveniently a long) + // We have to cast here in order to be compatible with java 8 + // https://github.com/eclipse/jetty.project/issues/3244 + (buf as Buffer).position(12) // 20 - 8 bytes = offset 12 + return buf.getLong() + } + + override fun isValid(fingerprint: String): Boolean { + return fingerprint.matches("^[0-9A-F]{40}$".toRegex()) + } + + fun toUri(): URI = URI(SCHEME, toString(), null) + + override fun prettyPrint(): String { + return buildString { + for (i in 0..4) { + append(fingerprint, i * 4, (i + 1) * 4).append(' ') + } + append(' ') + for (i in 5 .. 8) { + append(fingerprint, i * 4, (i + 1) * 4).append(' ') + } + append(fingerprint, 36, 40) + } + } + + companion object { + @JvmStatic + val SCHEME = "openpgp4fpr" + + @JvmStatic + fun fromUri(uri: URI): OpenPgpV4Fingerprint { + if (SCHEME != uri.scheme) { + throw IllegalArgumentException("URI scheme MUST equal '$SCHEME'.") + } + return OpenPgpV4Fingerprint(uri.schemeSpecificPart) + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.java deleted file mode 100644 index 11f18058..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.java +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key; - -import java.nio.Buffer; -import java.nio.ByteBuffer; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.util.encoders.Hex; - -/** - * This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. - * Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the - * key version. - */ -public class _64DigitFingerprint extends OpenPgpFingerprint { - - /** - * Create an {@link _64DigitFingerprint}. - * - * @param fingerprint uppercase hexadecimal fingerprint of length 64 - */ - protected _64DigitFingerprint(@Nonnull String fingerprint) { - super(fingerprint); - } - - protected _64DigitFingerprint(@Nonnull byte[] bytes) { - super(Hex.encode(bytes)); - } - - protected _64DigitFingerprint(@Nonnull PGPPublicKey key) { - super(key); - } - - protected _64DigitFingerprint(@Nonnull PGPSecretKey key) { - this(key.getPublicKey()); - } - - protected _64DigitFingerprint(@Nonnull PGPPublicKeyRing ring) { - super(ring); - } - - protected _64DigitFingerprint(@Nonnull PGPSecretKeyRing ring) { - super(ring); - } - - protected _64DigitFingerprint(@Nonnull PGPKeyRing ring) { - super(ring); - } - - @Override - public int getVersion() { - return -1; // might be v5 or v6 - } - - @Override - protected boolean isValid(@Nonnull String fp) { - return fp.matches("^[0-9A-F]{64}$"); - } - - @Override - public long getKeyId() { - byte[] bytes = Hex.decode(toString().getBytes(utf8)); - ByteBuffer buf = ByteBuffer.wrap(bytes); - - // The key id is the left-most 8 bytes (conveniently a long). - // We have to cast here in order to be compatible with java 8 - // https://github.com/eclipse/jetty.project/issues/3244 - ((Buffer) buf).position(0); - - return buf.getLong(); - } - - @Override - public String prettyPrint() { - String fp = toString(); - StringBuilder pretty = new StringBuilder(); - - for (int i = 0; i < 4; i++) { - pretty.append(fp, i * 8, (i + 1) * 8).append(' '); - } - pretty.append(' '); - for (int i = 4; i < 7; i++) { - pretty.append(fp, i * 8, (i + 1) * 8).append(' '); - } - pretty.append(fp, 56, 64); - return pretty.toString(); - } - - @Override - public boolean equals(Object other) { - if (other == null) { - return false; - } - - if (!(other instanceof CharSequence)) { - return false; - } - - return this.toString().equals(other.toString()); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public int compareTo(OpenPgpFingerprint openPgpFingerprint) { - return toString().compareTo(openPgpFingerprint.toString()); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.kt b/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.kt new file mode 100644 index 00000000..0f7dd705 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.kt @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import javax.annotation.Nonnull; + +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.util.encoders.Hex; + +/** + * This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. + * Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the + * key version. + */ +open class _64DigitFingerprint: OpenPgpFingerprint { + + /** + * Create an {@link _64DigitFingerprint}. + * + * @param fingerprint uppercase hexadecimal fingerprint of length 64 + */ + constructor(fingerprint: String): super(fingerprint) + constructor(bytes: ByteArray): super(bytes) + constructor(key: PGPPublicKey): super(key) + constructor(key: PGPSecretKey): super(key) + constructor(keys: PGPKeyRing): super(keys) + + override val keyId: Long + get() { + val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8"))) + val buf = ByteBuffer.wrap(bytes); + + // The key id is the left-most 8 bytes (conveniently a long). + // We have to cast here in order to be compatible with java 8 + // https://github.com/eclipse/jetty.project/issues/3244 + (buf as Buffer).position(0); + + return buf.getLong() + } + + override fun getVersion(): Int { + return -1 // might be v5 or v6 + } + + override fun isValid(fingerprint: String): Boolean { + return fingerprint.matches(("^[0-9A-F]{64}$".toRegex())) + } + + override fun toString(): String { + return super.toString() + } + + override fun prettyPrint(): String { + return buildString { + for (i in 0 until 4) { + append(fingerprint, i * 8, (i + 1) * 8).append(' '); + } + append(' '); + for (i in 4 until 7) { + append(fingerprint, i * 8, (i + 1) * 8).append(' '); + } + append(fingerprint, 56, 64); + } + } +} From d36e878bf9824d05c3e41008ccf9c2535649b258 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 8 Aug 2023 15:45:55 +0200 Subject: [PATCH 029/155] Kotlin conversion: OpenPgpFingerprints --- .../pgpainless/key/OpenPgpV5Fingerprint.java | 58 ------------------- .../pgpainless/key/OpenPgpV5Fingerprint.kt | 25 ++++++++ .../pgpainless/key/OpenPgpV6Fingerprint.java | 58 ------------------- .../pgpainless/key/OpenPgpV6Fingerprint.kt | 25 ++++++++ 4 files changed, 50 insertions(+), 116 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.kt delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.java deleted file mode 100644 index a0a3d1f4..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.java +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key; - -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; - -/** - * This class represents a hex encoded, upper case OpenPGP v5 fingerprint. - */ -public class OpenPgpV5Fingerprint extends _64DigitFingerprint { - - /** - * Create an {@link OpenPgpV5Fingerprint}. - * - * @param fingerprint uppercase hexadecimal fingerprint of length 64 - */ - public OpenPgpV5Fingerprint(@Nonnull String fingerprint) { - super(fingerprint); - } - - public OpenPgpV5Fingerprint(@Nonnull byte[] bytes) { - super(bytes); - } - - public OpenPgpV5Fingerprint(@Nonnull PGPPublicKey key) { - super(key); - } - - public OpenPgpV5Fingerprint(@Nonnull PGPSecretKey key) { - this(key.getPublicKey()); - } - - public OpenPgpV5Fingerprint(@Nonnull PGPPublicKeyRing ring) { - super(ring); - } - - public OpenPgpV5Fingerprint(@Nonnull PGPSecretKeyRing ring) { - super(ring); - } - - public OpenPgpV5Fingerprint(@Nonnull PGPKeyRing ring) { - super(ring); - } - - @Override - public int getVersion() { - return 5; - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.kt b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.kt new file mode 100644 index 00000000..b82a3482 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.kt @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey + +/** + * This class represents a hex encoded uppercase OpenPGP v5 fingerprint. + */ +class OpenPgpV5Fingerprint: _64DigitFingerprint { + + constructor(fingerprint: String): super(fingerprint) + constructor(key: PGPPublicKey): super(key) + constructor(key: PGPSecretKey): super(key) + constructor(keys: PGPKeyRing): super(keys) + constructor(bytes: ByteArray): super(bytes) + + override fun getVersion(): Int { + return 5 + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.java deleted file mode 100644 index 79cc1715..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.java +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key; - -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; - -/** - * This class represents a hex encoded, upper case OpenPGP v6 fingerprint. - */ -public class OpenPgpV6Fingerprint extends _64DigitFingerprint { - - /** - * Create an {@link OpenPgpV6Fingerprint}. - * - * @param fingerprint uppercase hexadecimal fingerprint of length 64 - */ - public OpenPgpV6Fingerprint(@Nonnull String fingerprint) { - super(fingerprint); - } - - public OpenPgpV6Fingerprint(@Nonnull byte[] bytes) { - super(bytes); - } - - public OpenPgpV6Fingerprint(@Nonnull PGPPublicKey key) { - super(key); - } - - public OpenPgpV6Fingerprint(@Nonnull PGPSecretKey key) { - this(key.getPublicKey()); - } - - public OpenPgpV6Fingerprint(@Nonnull PGPPublicKeyRing ring) { - super(ring); - } - - public OpenPgpV6Fingerprint(@Nonnull PGPSecretKeyRing ring) { - super(ring); - } - - public OpenPgpV6Fingerprint(@Nonnull PGPKeyRing ring) { - super(ring); - } - - @Override - public int getVersion() { - return 6; - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.kt b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.kt new file mode 100644 index 00000000..11cf05b0 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.kt @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey + +/** + * This class represents a hex encoded, uppercase OpenPGP v6 fingerprint. + */ +class OpenPgpV6Fingerprint: _64DigitFingerprint { + + constructor(fingerprint: String): super(fingerprint) + constructor(key: PGPPublicKey): super(key) + constructor(key: PGPSecretKey): super(key) + constructor(keys: PGPKeyRing): super(keys) + constructor(bytes: ByteArray): super(bytes) + + override fun getVersion(): Int { + return 6 + } +} \ No newline at end of file From ca6e18d701870fc95272970b3cda320fca22651c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 8 Aug 2023 16:09:18 +0200 Subject: [PATCH 030/155] Kotlin conversion: SubkeyIdentifier --- .../org/pgpainless/key/SubkeyIdentifier.java | 146 ------------------ .../org/pgpainless/key/SubkeyIdentifier.kt | 53 +++++++ 2 files changed, 53 insertions(+), 146 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.java b/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.java deleted file mode 100644 index 8bfd7f7c..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.java +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key; - -import java.util.NoSuchElementException; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; - -/** - * Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring, - * as well as the subkeys fingerprint. - */ -public class SubkeyIdentifier { - - private final OpenPgpFingerprint primaryKeyFingerprint; - private final OpenPgpFingerprint subkeyFingerprint; - - /** - * Create a {@link SubkeyIdentifier} from a {@link PGPKeyRing}. - * The identifier will point to the primary key of the provided ring. - * - * @param keyRing key ring - */ - public SubkeyIdentifier(PGPKeyRing keyRing) { - this(keyRing, keyRing.getPublicKey().getKeyID()); - } - - /** - * Create a {@link SubkeyIdentifier} from a {@link PGPKeyRing} and the subkeys key id. - * {@link #getPrimaryKeyFingerprint()} will return the {@link OpenPgpFingerprint} of the keyrings primary key, - * while {@link #getSubkeyFingerprint()} will return the subkeys fingerprint. - * - * @param keyRing keyring the subkey belongs to - * @param keyId keyid of the subkey - */ - public SubkeyIdentifier(@Nonnull PGPKeyRing keyRing, long keyId) { - PGPPublicKey subkey = keyRing.getPublicKey(keyId); - if (subkey == null) { - throw new NoSuchElementException("Key ring does not contain subkey with id " + Long.toHexString(keyId)); - } - this.primaryKeyFingerprint = OpenPgpFingerprint.of(keyRing); - this.subkeyFingerprint = OpenPgpFingerprint.of(subkey); - } - - public SubkeyIdentifier(@Nonnull PGPKeyRing keyRing, @Nonnull OpenPgpFingerprint subkeyFingerprint) { - this(OpenPgpFingerprint.of(keyRing), subkeyFingerprint); - } - - /** - * Create a {@link SubkeyIdentifier} that identifies the primary key with the given fingerprint. - * This means, both {@link #getPrimaryKeyFingerprint()} and {@link #getSubkeyFingerprint()} return the same. - * - * @param primaryKeyFingerprint fingerprint of the identified key - */ - public SubkeyIdentifier(@Nonnull OpenPgpFingerprint primaryKeyFingerprint) { - this(primaryKeyFingerprint, primaryKeyFingerprint); - } - - /** - * Create a {@link SubkeyIdentifier} which points to the subkey with the given subkeyFingerprint, - * which has a primary key with the given primaryKeyFingerprint. - * - * @param primaryKeyFingerprint fingerprint of the primary key - * @param subkeyFingerprint fingerprint of the subkey - */ - public SubkeyIdentifier(@Nonnull OpenPgpFingerprint primaryKeyFingerprint, @Nonnull OpenPgpFingerprint subkeyFingerprint) { - this.primaryKeyFingerprint = primaryKeyFingerprint; - this.subkeyFingerprint = subkeyFingerprint; - } - - public @Nonnull OpenPgpFingerprint getFingerprint() { - return getSubkeyFingerprint(); - } - - public long getKeyId() { - return getSubkeyId(); - } - - /** - * Return the {@link OpenPgpFingerprint} of the primary key of the identified key. - * This might be the same as {@link #getSubkeyFingerprint()} if the identified subkey is the primary key. - * - * @return primary key fingerprint - */ - public @Nonnull OpenPgpFingerprint getPrimaryKeyFingerprint() { - return primaryKeyFingerprint; - } - - /** - * Return the key id of the primary key of the identified key. - * This might be the same as {@link #getSubkeyId()} if the identified subkey is the primary key. - * - * @return primary key id - */ - public long getPrimaryKeyId() { - return getPrimaryKeyFingerprint().getKeyId(); - } - - /** - * Return the {@link OpenPgpFingerprint} of the identified subkey. - * - * @return subkey fingerprint - */ - public @Nonnull OpenPgpFingerprint getSubkeyFingerprint() { - return subkeyFingerprint; - } - - /** - * Return the key id of the identified subkey. - * - * @return subkey id - */ - public long getSubkeyId() { - return getSubkeyFingerprint().getKeyId(); - } - - @Override - public int hashCode() { - return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (!(obj instanceof SubkeyIdentifier)) { - return false; - } - SubkeyIdentifier other = (SubkeyIdentifier) obj; - return getPrimaryKeyFingerprint().equals(other.getPrimaryKeyFingerprint()) - && getSubkeyFingerprint().equals(other.getSubkeyFingerprint()); - } - - @Override - public String toString() { - return getSubkeyFingerprint() + " " + getPrimaryKeyFingerprint(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.kt new file mode 100644 index 00000000..61b45874 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.kt @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.pgpainless.key.util.KeyIdUtil + +/** + * Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring, + * as well as the subkeys fingerprint. + */ +class SubkeyIdentifier( + val primaryKeyFingerprint: OpenPgpFingerprint, + val subkeyFingerprint: OpenPgpFingerprint) { + + constructor(fingerprint: OpenPgpFingerprint): this(fingerprint, fingerprint) + constructor(keys: PGPKeyRing): this(keys.publicKey) + constructor(key: PGPPublicKey): this(OpenPgpFingerprint.of(key)) + constructor(keys: PGPKeyRing, keyId: Long): this( + OpenPgpFingerprint.of(keys.publicKey), + OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?: + throw NoSuchElementException("OpenPGP key does not contain subkey ${KeyIdUtil.formatKeyId(keyId)}"))) + constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint) + + val keyId = subkeyFingerprint.keyId + val fingerprint = subkeyFingerprint + + val subkeyId = subkeyFingerprint.keyId + val primaryKeyId = primaryKeyFingerprint.keyId + + override fun equals(other: Any?): Boolean { + if (other == null) { + return false + } + if (this === other) { + return true + } + if (other !is SubkeyIdentifier) { + return false + } + + return primaryKeyFingerprint.equals(other.primaryKeyFingerprint) && subkeyFingerprint.equals(other.subkeyFingerprint) + } + + override fun hashCode(): Int { + return primaryKeyFingerprint.hashCode() + 31 * subkeyFingerprint.hashCode() + } + + override fun toString(): String = "$subkeyFingerprint $primaryKeyFingerprint" +} \ No newline at end of file From b68061373dcae8e032a3a7b9e8e28254ca906231 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Aug 2023 11:45:49 +0200 Subject: [PATCH 031/155] Wip: Kolin conversion of Policy --- .../java/org/pgpainless/policy/Policy2.kt | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/policy/Policy2.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy2.kt b/pgpainless-core/src/main/java/org/pgpainless/policy/Policy2.kt new file mode 100644 index 00000000..e17b9653 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/policy/Policy2.kt @@ -0,0 +1,132 @@ +package org.pgpainless.policy + +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.util.DateUtil +import java.util.* + +class Policy2 { + + /** + * Create a HashAlgorithmPolicy which accepts all [HashAlgorithms][HashAlgorithm] from the + * given map, if the queried usage date is BEFORE the respective termination date. + * A termination date value of

null
means no termination, resulting in the algorithm being + * acceptable, regardless of usage date. + * + * @param defaultHashAlgorithm default hash algorithm + * @param algorithmTerminationDates map of acceptable algorithms and their termination dates + */ + class HashAlgorithmPolicy( + val defaultHashAlgorithm: HashAlgorithm, + val acceptableHashAlgorithmsAndTerminationDates: Map) { + + /** + * Create a [HashAlgorithmPolicy] which accepts all [HashAlgorithms][HashAlgorithm] listed in + * the given list, regardless of usage date. + * + * @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation fails) + * @param acceptableHashAlgorithms list of acceptable hash algorithms + */ + constructor(defaultHashAlgorithm: HashAlgorithm, acceptableHashAlgorithms: List) : + this(defaultHashAlgorithm, acceptableHashAlgorithms.associateWith { null }) + + fun isAcceptable(hashAlgorithm: HashAlgorithm) = isAcceptable(hashAlgorithm, Date()) + + /** + * Return true, if the given algorithm is acceptable for the given usage date. + * + * @param hashAlgorithm algorithm + * @param referenceTime usage date (e.g. signature creation time) + * + * @return acceptance + */ + fun isAcceptable(hashAlgorithm: HashAlgorithm, referenceTime: Date): Boolean { + if (!acceptableHashAlgorithmsAndTerminationDates.containsKey(hashAlgorithm)) + return false + val terminationDate = acceptableHashAlgorithmsAndTerminationDates[hashAlgorithm] + if (terminationDate == null) { + return true + } + return terminationDate > referenceTime + } + + fun isAcceptable(algorithmId: Int, referenceTime: Date): Boolean { + HashAlgorithm.fromId(algorithmId).let { + if (it == null) { + return false + } + return isAcceptable(it, referenceTime) + } + } + + fun isAcceptable(algorithmId: Int) = isAcceptable(algorithmId, Date()) + + companion object { + @JvmStatic + val smartSignatureHashAlgorithmPolicy = HashAlgorithmPolicy(HashAlgorithm.SHA512, buildMap { + put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")) + put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) + put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) + put(HashAlgorithm.SHA224, null) + put(HashAlgorithm.SHA256, null) + put(HashAlgorithm.SHA384, null) + put(HashAlgorithm.SHA512, null) + put(HashAlgorithm.SHA3_256, null) + put(HashAlgorithm.SHA3_512, null) + }) + + /** + * [HashAlgorithmPolicy] which only accepts signatures made using algorithms which are acceptable + * according to 2022 standards. + * + * Particularly this policy only accepts algorithms from the SHA2 and SHA3 families. + * + * @return static signature algorithm policy + */ + @JvmStatic + val static2022SignatureHashAlgorithmPolicy = + HashAlgorithmPolicy(HashAlgorithm.SHA512, listOf( + HashAlgorithm.SHA224, + HashAlgorithm.SHA256, + HashAlgorithm.SHA384, + HashAlgorithm.SHA512, + HashAlgorithm.SHA3_256, + HashAlgorithm.SHA3_512)) + + /** + * Hash algorithm policy for revocation signatures, which accepts SHA1 and SHA2 algorithms, as well as RIPEMD160. + * + * @return static revocation signature hash algorithm policy + */ + @JvmStatic + val static2022RevocationSignatureHashAlgorithmPolicy = + HashAlgorithmPolicy(HashAlgorithm.SHA512, listOf( + HashAlgorithm.RIPEMD160, + HashAlgorithm.SHA1, + HashAlgorithm.SHA224, + HashAlgorithm.SHA256, + HashAlgorithm.SHA384, + HashAlgorithm.SHA512, + HashAlgorithm.SHA3_256, + HashAlgorithm.SHA3_512)) + } + } + + class CompressionAlgorithmPolicy( + val defaultCompressionAlgorithm: CompressionAlgorithm, + val acceptableCompressionAlgorithms: List) { + + fun isAcceptable(algorithm: CompressionAlgorithm) = acceptableCompressionAlgorithms.contains(algorithm) + fun isAcceptable(algorithmId: Int) = + } + + companion object { + private var INSTANCE: Policy2? = null + fun getInstance(): Policy2 { + if (INSTANCE == null) { + INSTANCE = Policy2() + } + return INSTANCE!! + } + } +} \ No newline at end of file From e57e74163c8c860493553b3d110b4261db383cb2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Aug 2023 13:15:00 +0200 Subject: [PATCH 032/155] Finish Policy conversion and move kotlin classes to src/kotlin/ --- .../algorithm/negotiation/package-info.java | 8 - .../pgpainless/algorithm/package-info.java | 8 - .../authentication/package-info.java | 8 - .../java/org/pgpainless/policy/Policy.java | 732 ------------------ .../java/org/pgpainless/policy/Policy2.kt | 132 ---- .../org/pgpainless/policy/package-info.java | 8 - .../org/pgpainless/PGPainless.kt | 4 + .../org/pgpainless/algorithm/AEADAlgorithm.kt | 0 .../pgpainless/algorithm/AlgorithmSuite.kt | 0 .../pgpainless/algorithm/CertificationType.kt | 0 .../algorithm/CompressionAlgorithm.kt | 0 .../algorithm/DocumentSignatureType.kt | 0 .../pgpainless/algorithm/EncryptionPurpose.kt | 0 .../org/pgpainless/algorithm/Feature.kt | 0 .../org/pgpainless/algorithm/HashAlgorithm.kt | 0 .../org/pgpainless/algorithm/KeyFlag.kt | 0 .../org/pgpainless/algorithm/OpenPgpPacket.kt | 0 .../algorithm/PublicKeyAlgorithm.kt | 0 .../pgpainless/algorithm/RevocationState.kt | 0 .../algorithm/RevocationStateType.kt | 0 .../algorithm/SignatureSubpacket.kt | 0 .../org/pgpainless/algorithm/SignatureType.kt | 0 .../pgpainless/algorithm/StreamEncoding.kt | 0 .../algorithm/SymmetricKeyAlgorithm.kt | 0 .../pgpainless/algorithm/Trustworthiness.kt | 0 .../negotiation/HashAlgorithmNegotiator.kt | 0 .../SymmetricKeyAlgorithmNegotiator.kt | 0 .../authentication/CertificateAuthenticity.kt | 0 .../authentication/CertificateAuthority.kt | 0 .../org/pgpainless/key/OpenPgpFingerprint.kt | 4 + .../pgpainless/key/OpenPgpV4Fingerprint.kt | 4 + .../pgpainless/key/OpenPgpV5Fingerprint.kt | 0 .../pgpainless/key/OpenPgpV6Fingerprint.kt | 0 .../org/pgpainless/key/SubkeyIdentifier.kt | 0 .../org/pgpainless/key/_64DigitFingerprint.kt | 0 .../kotlin/org/pgpainless/policy/Policy.kt | 359 +++++++++ 36 files changed, 371 insertions(+), 896 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/package-info.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/algorithm/package-info.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/authentication/package-info.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/policy/Policy2.kt delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/policy/package-info.java rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/PGPainless.kt (98%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/AEADAlgorithm.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/AlgorithmSuite.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/CertificationType.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/CompressionAlgorithm.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/DocumentSignatureType.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/EncryptionPurpose.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/Feature.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/HashAlgorithm.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/KeyFlag.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/OpenPgpPacket.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/PublicKeyAlgorithm.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/RevocationState.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/RevocationStateType.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/SignatureSubpacket.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/SignatureType.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/StreamEncoding.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/Trustworthiness.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/authentication/CertificateAuthenticity.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/authentication/CertificateAuthority.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/key/OpenPgpFingerprint.kt (98%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/key/OpenPgpV4Fingerprint.kt (94%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/key/OpenPgpV5Fingerprint.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/key/OpenPgpV6Fingerprint.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/key/SubkeyIdentifier.kt (100%) rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/key/_64DigitFingerprint.kt (100%) create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/package-info.java deleted file mode 100644 index 3969d8df..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to algorithm negotiation. - */ -package org.pgpainless.algorithm.negotiation; diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/package-info.java deleted file mode 100644 index c2f5f5ff..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Enums which map to OpenPGP's algorithm IDs. - */ -package org.pgpainless.algorithm; diff --git a/pgpainless-core/src/main/java/org/pgpainless/authentication/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/authentication/package-info.java deleted file mode 100644 index 495ab1f7..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/authentication/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes and interfaces related to certificate authenticity. - */ -package org.pgpainless.authentication; diff --git a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java b/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java deleted file mode 100644 index 35787138..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java +++ /dev/null @@ -1,732 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.policy; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; - -import javax.annotation.Nonnull; - -import org.pgpainless.algorithm.AlgorithmSuite; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.util.DateUtil; -import org.pgpainless.util.NotationRegistry; - -/** - * Policy class used to configure acceptable algorithm suites etc. - */ -public final class Policy { - - private static Policy INSTANCE; - - private HashAlgorithmPolicy signatureHashAlgorithmPolicy = - HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(); - private HashAlgorithmPolicy revocationSignatureHashAlgorithmPolicy = - HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(); - private SymmetricKeyAlgorithmPolicy symmetricKeyEncryptionAlgorithmPolicy = - SymmetricKeyAlgorithmPolicy.symmetricKeyEncryptionPolicy2022(); - private SymmetricKeyAlgorithmPolicy symmetricKeyDecryptionAlgorithmPolicy = - SymmetricKeyAlgorithmPolicy.symmetricKeyDecryptionPolicy2022(); - private CompressionAlgorithmPolicy compressionAlgorithmPolicy = - CompressionAlgorithmPolicy.anyCompressionAlgorithmPolicy(); - private PublicKeyAlgorithmPolicy publicKeyAlgorithmPolicy = - PublicKeyAlgorithmPolicy.bsi2021PublicKeyAlgorithmPolicy(); - private final NotationRegistry notationRegistry = new NotationRegistry(); - - private AlgorithmSuite keyGenerationAlgorithmSuite = AlgorithmSuite.getDefaultAlgorithmSuite(); - - // Signers User-ID is soon to be deprecated. - private SignerUserIdValidationLevel signerUserIdValidationLevel = SignerUserIdValidationLevel.DISABLED; - - private boolean enableKeyParameterValidation = false; - - public enum SignerUserIdValidationLevel { - /** - * PGPainless will verify {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets in signatures strictly. - * This means, that signatures with Signer's User-ID subpackets containing a value that does not match the signer key's - * user-id exactly, will be rejected. - * E.g. Signer's user-id "alice@pgpainless.org", User-ID: "Alice <alice@pgpainless.org>" does not - * match exactly and is therefore rejected. - */ - STRICT, - - /** - * PGPainless will ignore {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets on signature. - */ - DISABLED - } - - Policy() { - } - - /** - * Return the singleton instance of PGPainless' policy. - * - * @return singleton instance - */ - public static Policy getInstance() { - if (INSTANCE == null) { - INSTANCE = new Policy(); - } - return INSTANCE; - } - - /** - * Return the hash algorithm policy for signatures. - * @return hash algorithm policy - */ - public HashAlgorithmPolicy getSignatureHashAlgorithmPolicy() { - return signatureHashAlgorithmPolicy; - } - - /** - * Set a custom hash algorithm policy for signatures. - * - * @param policy custom policy - */ - public void setSignatureHashAlgorithmPolicy(HashAlgorithmPolicy policy) { - if (policy == null) { - throw new NullPointerException("Policy cannot be null."); - } - this.signatureHashAlgorithmPolicy = policy; - } - - /** - * Return the hash algorithm policy for revocations. - * This policy is separate from {@link #getSignatureHashAlgorithmPolicy()}, as PGPainless by default uses a - * less strict policy when it comes to acceptable algorithms. - * - * @return revocation signature hash algorithm policy - */ - public HashAlgorithmPolicy getRevocationSignatureHashAlgorithmPolicy() { - return revocationSignatureHashAlgorithmPolicy; - } - - /** - * Set a custom hash algorithm policy for revocations. - * - * @param policy custom policy - */ - public void setRevocationSignatureHashAlgorithmPolicy(HashAlgorithmPolicy policy) { - if (policy == null) { - throw new NullPointerException("Policy cannot be null."); - } - this.revocationSignatureHashAlgorithmPolicy = policy; - } - - /** - * Return the symmetric encryption algorithm policy for encryption. - * This policy defines which symmetric algorithms are acceptable when producing encrypted messages. - * - * @return symmetric algorithm policy for encryption - */ - public SymmetricKeyAlgorithmPolicy getSymmetricKeyEncryptionAlgorithmPolicy() { - return symmetricKeyEncryptionAlgorithmPolicy; - } - - /** - * Return the symmetric encryption algorithm policy for decryption. - * This policy defines which symmetric algorithms are acceptable when decrypting encrypted messages. - * - * @return symmetric algorithm policy for decryption - */ - public SymmetricKeyAlgorithmPolicy getSymmetricKeyDecryptionAlgorithmPolicy() { - return symmetricKeyDecryptionAlgorithmPolicy; - } - - /** - * Set a custom symmetric encryption algorithm policy for encrypting messages. - * - * @param policy custom policy - */ - public void setSymmetricKeyEncryptionAlgorithmPolicy(SymmetricKeyAlgorithmPolicy policy) { - if (policy == null) { - throw new NullPointerException("Policy cannot be null."); - } - this.symmetricKeyEncryptionAlgorithmPolicy = policy; - } - - /** - * Set a custom symmetric encryption algorithm policy for decrypting messages. - * - * @param policy custom policy - */ - public void setSymmetricKeyDecryptionAlgorithmPolicy(SymmetricKeyAlgorithmPolicy policy) { - if (policy == null) { - throw new NullPointerException("Policy cannot be null."); - } - this.symmetricKeyDecryptionAlgorithmPolicy = policy; - } - - public CompressionAlgorithmPolicy getCompressionAlgorithmPolicy() { - return compressionAlgorithmPolicy; - } - - public void setCompressionAlgorithmPolicy(CompressionAlgorithmPolicy policy) { - if (policy == null) { - throw new NullPointerException("Compression policy cannot be null."); - } - this.compressionAlgorithmPolicy = policy; - } - - /** - * Return the current public key algorithm policy. - * - * @return public key algorithm policy - */ - public PublicKeyAlgorithmPolicy getPublicKeyAlgorithmPolicy() { - return publicKeyAlgorithmPolicy; - } - - /** - * Set a custom public key algorithm policy. - * - * @param publicKeyAlgorithmPolicy custom policy - */ - public void setPublicKeyAlgorithmPolicy(PublicKeyAlgorithmPolicy publicKeyAlgorithmPolicy) { - if (publicKeyAlgorithmPolicy == null) { - throw new NullPointerException("Public key algorithm policy cannot be null."); - } - this.publicKeyAlgorithmPolicy = publicKeyAlgorithmPolicy; - } - - public static final class SymmetricKeyAlgorithmPolicy { - - private final SymmetricKeyAlgorithm defaultSymmetricKeyAlgorithm; - private final List acceptableSymmetricKeyAlgorithms; - - public SymmetricKeyAlgorithmPolicy(SymmetricKeyAlgorithm defaultSymmetricKeyAlgorithm, List acceptableSymmetricKeyAlgorithms) { - this.defaultSymmetricKeyAlgorithm = defaultSymmetricKeyAlgorithm; - this.acceptableSymmetricKeyAlgorithms = Collections.unmodifiableList(acceptableSymmetricKeyAlgorithms); - } - - /** - * Return the default symmetric key algorithm. - * This algorithm is used as a fallback when no consensus about symmetric algorithms can be reached. - * - * @return default symmetric encryption algorithm - */ - public SymmetricKeyAlgorithm getDefaultSymmetricKeyAlgorithm() { - return defaultSymmetricKeyAlgorithm; - } - - /** - * Return true if the given symmetric encryption algorithm is acceptable by this policy. - * - * @param algorithm algorithm - * @return true if algorithm is acceptable, false otherwise - */ - public boolean isAcceptable(SymmetricKeyAlgorithm algorithm) { - return acceptableSymmetricKeyAlgorithms.contains(algorithm); - } - - /** - * Return true if the given symmetric encryption algorithm is acceptable by this policy. - * - * @param algorithmId algorithm - * @return true if algorithm is acceptable, false otherwise - */ - public boolean isAcceptable(int algorithmId) { - try { - SymmetricKeyAlgorithm algorithm = SymmetricKeyAlgorithm.requireFromId(algorithmId); - return isAcceptable(algorithm); - } catch (NoSuchElementException e) { - // Unknown algorithm is not acceptable - return false; - } - } - - /** - * The default symmetric encryption algorithm policy of PGPainless. - * - * @return default symmetric encryption algorithm policy - * @deprecated not expressive - will be removed in a future release - */ - @Deprecated - public static SymmetricKeyAlgorithmPolicy defaultSymmetricKeyEncryptionAlgorithmPolicy() { - return symmetricKeyEncryptionPolicy2022(); - } - - /** - * Policy for symmetric encryption algorithms in the context of message production (encryption). - * This suite contains algorithms that are deemed safe to use in 2022. - * - * @return 2022 symmetric key encryption algorithm policy - */ - public static SymmetricKeyAlgorithmPolicy symmetricKeyEncryptionPolicy2022() { - return new SymmetricKeyAlgorithmPolicy(SymmetricKeyAlgorithm.AES_128, Arrays.asList( - // Reject: Unencrypted, IDEA, TripleDES, CAST5, Blowfish - SymmetricKeyAlgorithm.AES_256, - SymmetricKeyAlgorithm.AES_192, - SymmetricKeyAlgorithm.AES_128, - SymmetricKeyAlgorithm.TWOFISH, - SymmetricKeyAlgorithm.CAMELLIA_256, - SymmetricKeyAlgorithm.CAMELLIA_192, - SymmetricKeyAlgorithm.CAMELLIA_128 - )); - } - - /** - * The default symmetric decryption algorithm policy of PGPainless. - * - * @return default symmetric decryption algorithm policy - * @deprecated not expressive - will be removed in a future update - */ - @Deprecated - public static SymmetricKeyAlgorithmPolicy defaultSymmetricKeyDecryptionAlgorithmPolicy() { - return symmetricKeyDecryptionPolicy2022(); - } - - /** - * Policy for symmetric key encryption algorithms in the context of message consumption (decryption). - * This suite contains algorithms that are deemed safe to use in 2022. - * - * @return 2022 symmetric key decryption algorithm policy - */ - public static SymmetricKeyAlgorithmPolicy symmetricKeyDecryptionPolicy2022() { - return new SymmetricKeyAlgorithmPolicy(SymmetricKeyAlgorithm.AES_128, Arrays.asList( - // Reject: Unencrypted, IDEA, TripleDES, Blowfish - SymmetricKeyAlgorithm.CAST5, - SymmetricKeyAlgorithm.AES_256, - SymmetricKeyAlgorithm.AES_192, - SymmetricKeyAlgorithm.AES_128, - SymmetricKeyAlgorithm.TWOFISH, - SymmetricKeyAlgorithm.CAMELLIA_256, - SymmetricKeyAlgorithm.CAMELLIA_192, - SymmetricKeyAlgorithm.CAMELLIA_128 - )); - } - - /** - * Select the best acceptable algorithm from the options list. - * The best algorithm is the first algorithm we encounter in our list of acceptable algorithms that - * is also contained in the list of options. - * - * - * @param options list of algorithm options - * @return best - */ - public SymmetricKeyAlgorithm selectBest(List options) { - for (SymmetricKeyAlgorithm acceptable : acceptableSymmetricKeyAlgorithms) { - if (options.contains(acceptable)) { - return acceptable; - } - } - return null; - } - } - - public static final class HashAlgorithmPolicy { - - private final HashAlgorithm defaultHashAlgorithm; - private final Map acceptableHashAlgorithmsAndTerminationDates; - - /** - * Create a {@link HashAlgorithmPolicy} which accepts all {@link HashAlgorithm HashAlgorithms} from the - * given map, if the queried usage date is BEFORE the respective termination date. - * A termination date value of
null
means no termination, resulting in the algorithm being - * acceptable, regardless of usage date. - * - * @param defaultHashAlgorithm default hash algorithm - * @param algorithmTerminationDates map of acceptable algorithms and their termination dates - */ - public HashAlgorithmPolicy(@Nonnull HashAlgorithm defaultHashAlgorithm, @Nonnull Map algorithmTerminationDates) { - this.defaultHashAlgorithm = defaultHashAlgorithm; - this.acceptableHashAlgorithmsAndTerminationDates = algorithmTerminationDates; - } - - /** - * Create a {@link HashAlgorithmPolicy} which accepts all {@link HashAlgorithm HashAlgorithms} listed in - * the given list, regardless of usage date. - * - * @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation fails) - * @param acceptableHashAlgorithms list of acceptable hash algorithms - */ - public HashAlgorithmPolicy(@Nonnull HashAlgorithm defaultHashAlgorithm, @Nonnull List acceptableHashAlgorithms) { - this(defaultHashAlgorithm, Collections.unmodifiableMap(listToMap(acceptableHashAlgorithms))); - } - - private static Map listToMap(@Nonnull List algorithms) { - Map algorithmsAndTerminationDates = new HashMap<>(); - for (HashAlgorithm algorithm : algorithms) { - algorithmsAndTerminationDates.put(algorithm, null); - } - return algorithmsAndTerminationDates; - } - - /** - * Return the default hash algorithm. - * This algorithm is used as a fallback when no consensus about hash algorithms can be reached. - * - * @return default hash algorithm - */ - public HashAlgorithm defaultHashAlgorithm() { - return defaultHashAlgorithm; - } - - /** - * Return true if the given hash algorithm is currently acceptable by this policy. - * - * @param hashAlgorithm hash algorithm - * @return true if the hash algorithm is acceptable, false otherwise - */ - public boolean isAcceptable(@Nonnull HashAlgorithm hashAlgorithm) { - return isAcceptable(hashAlgorithm, new Date()); - } - - /** - * Return true if the given hash algorithm is currently acceptable by this policy. - * - * @param algorithmId hash algorithm - * @return true if the hash algorithm is acceptable, false otherwise - */ - public boolean isAcceptable(int algorithmId) { - try { - HashAlgorithm algorithm = HashAlgorithm.requireFromId(algorithmId); - return isAcceptable(algorithm); - } catch (NoSuchElementException e) { - // Unknown algorithm is not acceptable - return false; - } - } - - /** - * Return true, if the given algorithm is acceptable for the given usage date. - * - * @param hashAlgorithm algorithm - * @param usageDate usage date (e.g. signature creation time) - * - * @return acceptance - */ - public boolean isAcceptable(@Nonnull HashAlgorithm hashAlgorithm, @Nonnull Date usageDate) { - if (!acceptableHashAlgorithmsAndTerminationDates.containsKey(hashAlgorithm)) { - return false; - } - - // Check termination date - Date terminationDate = acceptableHashAlgorithmsAndTerminationDates.get(hashAlgorithm); - if (terminationDate == null) { - return true; - } - - // Reject if usage date is past termination date - return terminationDate.after(usageDate); - } - - public boolean isAcceptable(int algorithmId, @Nonnull Date usageDate) { - try { - HashAlgorithm algorithm = HashAlgorithm.requireFromId(algorithmId); - return isAcceptable(algorithm, usageDate); - } catch (NoSuchElementException e) { - // Unknown algorithm is not acceptable - return false; - } - } - - /** - * The default signature hash algorithm policy of PGPainless. - * Note that this policy is only used for non-revocation signatures. - * For revocation signatures {@link #defaultRevocationSignatureHashAlgorithmPolicy()} is used instead. - * - * @return default signature hash algorithm policy - * @deprecated not expressive - will be removed in an upcoming release - */ - @Deprecated - public static HashAlgorithmPolicy defaultSignatureAlgorithmPolicy() { - return smartSignatureHashAlgorithmPolicy(); - } - - /** - * {@link HashAlgorithmPolicy} which takes the date of the algorithm usage into consideration. - * If the policy has a termination date for a given algorithm, and the usage date is after that termination - * date, the algorithm is rejected. - * - * This policy is inspired by Sequoia-PGP's collision resistant algorithm policy. - * - * @see Sequoia-PGP's Collision Resistant Algorithm Policy - * - * @return smart signature algorithm policy - */ - public static HashAlgorithmPolicy smartSignatureHashAlgorithmPolicy() { - Map algorithmDateMap = new HashMap<>(); - - algorithmDateMap.put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")); - algorithmDateMap.put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")); - algorithmDateMap.put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")); - algorithmDateMap.put(HashAlgorithm.SHA224, null); - algorithmDateMap.put(HashAlgorithm.SHA256, null); - algorithmDateMap.put(HashAlgorithm.SHA384, null); - algorithmDateMap.put(HashAlgorithm.SHA512, null); - - return new HashAlgorithmPolicy(HashAlgorithm.SHA512, algorithmDateMap); - } - - /** - * {@link HashAlgorithmPolicy} which only accepts signatures made using algorithms which are acceptable - * according to 2022 standards. - * - * Particularly this policy only accepts algorithms from the SHA2 family. - * - * @return static signature algorithm policy - */ - public static HashAlgorithmPolicy static2022SignatureHashAlgorithmPolicy() { - return new HashAlgorithmPolicy(HashAlgorithm.SHA512, Arrays.asList( - HashAlgorithm.SHA224, - HashAlgorithm.SHA256, - HashAlgorithm.SHA384, - HashAlgorithm.SHA512 - )); - } - - /** - * The default revocation signature hash algorithm policy of PGPainless. - * - * @return default revocation signature hash algorithm policy - * @deprecated not expressive - will be removed in an upcoming release - */ - @Deprecated - public static HashAlgorithmPolicy defaultRevocationSignatureHashAlgorithmPolicy() { - return smartSignatureHashAlgorithmPolicy(); - } - - /** - * Hash algorithm policy for revocation signatures, which accepts SHA1 and SHA2 algorithms, as well as RIPEMD160. - * - * @return static revocation signature hash algorithm policy - */ - public static HashAlgorithmPolicy static2022RevocationSignatureHashAlgorithmPolicy() { - return new HashAlgorithmPolicy(HashAlgorithm.SHA512, Arrays.asList( - HashAlgorithm.RIPEMD160, - HashAlgorithm.SHA1, - HashAlgorithm.SHA224, - HashAlgorithm.SHA256, - HashAlgorithm.SHA384, - HashAlgorithm.SHA512 - )); - } - } - - public static final class CompressionAlgorithmPolicy { - - private final CompressionAlgorithm defaultCompressionAlgorithm; - private final List acceptableCompressionAlgorithms; - - public CompressionAlgorithmPolicy(CompressionAlgorithm defaultCompressionAlgorithm, - List acceptableCompressionAlgorithms) { - this.defaultCompressionAlgorithm = defaultCompressionAlgorithm; - this.acceptableCompressionAlgorithms = Collections.unmodifiableList(acceptableCompressionAlgorithms); - } - - public CompressionAlgorithm defaultCompressionAlgorithm() { - return defaultCompressionAlgorithm; - } - - public boolean isAcceptable(int compressionAlgorithmTag) { - try { - CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.requireFromId(compressionAlgorithmTag); - return isAcceptable(compressionAlgorithm); - } catch (NoSuchElementException e) { - // Unknown algorithm is not acceptable - return false; - } - } - - public boolean isAcceptable(CompressionAlgorithm compressionAlgorithm) { - return acceptableCompressionAlgorithms.contains(compressionAlgorithm); - } - - /** - * Default {@link CompressionAlgorithmPolicy} of PGPainless. - * The default compression algorithm policy accepts any compression algorithm. - * - * @return default algorithm policy - * @deprecated not expressive - might be removed in a future release - */ - @Deprecated - public static CompressionAlgorithmPolicy defaultCompressionAlgorithmPolicy() { - return anyCompressionAlgorithmPolicy(); - } - - /** - * Policy that accepts any known compression algorithm and offers {@link CompressionAlgorithm#ZIP} as - * default algorithm. - * - * @return compression algorithm policy - */ - public static CompressionAlgorithmPolicy anyCompressionAlgorithmPolicy() { - return new CompressionAlgorithmPolicy(CompressionAlgorithm.ZIP, Arrays.asList( - CompressionAlgorithm.UNCOMPRESSED, - CompressionAlgorithm.ZIP, - CompressionAlgorithm.BZIP2, - CompressionAlgorithm.ZLIB - )); - } - } - - public static final class PublicKeyAlgorithmPolicy { - - private final Map algorithmStrengths = new EnumMap<>(PublicKeyAlgorithm.class); - - public PublicKeyAlgorithmPolicy(Map minimalAlgorithmBitStrengths) { - this.algorithmStrengths.putAll(minimalAlgorithmBitStrengths); - } - - public boolean isAcceptable(int algorithmId, int bitStrength) { - try { - PublicKeyAlgorithm algorithm = PublicKeyAlgorithm.requireFromId(algorithmId); - return isAcceptable(algorithm, bitStrength); - } catch (NoSuchElementException e) { - // Unknown algorithm is not acceptable - return false; - } - } - - public boolean isAcceptable(PublicKeyAlgorithm algorithm, int bitStrength) { - if (!algorithmStrengths.containsKey(algorithm)) { - return false; - } - - int minStrength = algorithmStrengths.get(algorithm); - return bitStrength >= minStrength; - } - - /** - * Return PGPainless' default public key algorithm policy. - * This policy is based upon recommendations made by the German Federal Office for Information Security (BSI). - * - * @return default algorithm policy - * @deprecated not expressive - might be removed in a future release - */ - @Deprecated - public static PublicKeyAlgorithmPolicy defaultPublicKeyAlgorithmPolicy() { - return bsi2021PublicKeyAlgorithmPolicy(); - } - - /** - * This policy is based upon recommendations made by the German Federal Office for Information Security (BSI). - * - * Basically this policy requires keys based on elliptic curves to have a bit strength of at least 250, - * and keys based on prime number factorization / discrete logarithm problems to have a strength of at least 2000 bits. - * - * @see BSI - Technical Guideline - Cryptographic Mechanisms: Recommendations and Key Lengths (2021-01) - * @see BlueKrypt | Cryptographic Key Length Recommendation - * - * @return default algorithm policy - */ - public static PublicKeyAlgorithmPolicy bsi2021PublicKeyAlgorithmPolicy() { - Map minimalBitStrengths = new EnumMap<>(PublicKeyAlgorithm.class); - // §5.4.1 - minimalBitStrengths.put(PublicKeyAlgorithm.RSA_GENERAL, 2000); - minimalBitStrengths.put(PublicKeyAlgorithm.RSA_SIGN, 2000); - minimalBitStrengths.put(PublicKeyAlgorithm.RSA_ENCRYPT, 2000); - // Note: ElGamal is not mentioned in the BSI document. - // We assume that the requirements are similar to other DH algorithms - minimalBitStrengths.put(PublicKeyAlgorithm.ELGAMAL_ENCRYPT, 2000); - minimalBitStrengths.put(PublicKeyAlgorithm.ELGAMAL_GENERAL, 2000); - // §5.4.2 - minimalBitStrengths.put(PublicKeyAlgorithm.DSA, 2000); - // §5.4.3 - minimalBitStrengths.put(PublicKeyAlgorithm.ECDSA, 250); - // Note: EdDSA is not mentioned in the BSI document. - // We assume that the requirements are similar to other EC algorithms. - minimalBitStrengths.put(PublicKeyAlgorithm.EDDSA, 250); - // §7.2.1 - minimalBitStrengths.put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000); - // §7.2.2 - minimalBitStrengths.put(PublicKeyAlgorithm.ECDH, 250); - - return new PublicKeyAlgorithmPolicy(minimalBitStrengths); - } - } - - /** - * Return the {@link NotationRegistry} of PGPainless. - * The notation registry is used to decide, whether a Notation is known or not. - * Background: Critical unknown notations render signatures invalid. - * - * @return Notation registry - */ - public NotationRegistry getNotationRegistry() { - return notationRegistry; - } - - /** - * Return the current {@link AlgorithmSuite} which defines preferred algorithms used during key generation. - * @return current algorithm suite - */ - public @Nonnull AlgorithmSuite getKeyGenerationAlgorithmSuite() { - return keyGenerationAlgorithmSuite; - } - - /** - * Set a custom {@link AlgorithmSuite} which defines preferred algorithms used during key generation. - * - * @param algorithmSuite custom algorithm suite - */ - public void setKeyGenerationAlgorithmSuite(@Nonnull AlgorithmSuite algorithmSuite) { - this.keyGenerationAlgorithmSuite = algorithmSuite; - } - - /** - * Return the level of validation PGPainless shall do on {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets. - * By default, this value is {@link SignerUserIdValidationLevel#DISABLED}. - * - * @return the level of validation - */ - public SignerUserIdValidationLevel getSignerUserIdValidationLevel() { - return signerUserIdValidationLevel; - } - - /** - * Specify, how {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets on signatures shall be validated. - * - * @param signerUserIdValidationLevel level of verification PGPainless shall do on - * {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets. - * @return policy instance - */ - public Policy setSignerUserIdValidationLevel(SignerUserIdValidationLevel signerUserIdValidationLevel) { - if (signerUserIdValidationLevel == null) { - throw new NullPointerException("SignerUserIdValidationLevel cannot be null."); - } - this.signerUserIdValidationLevel = signerUserIdValidationLevel; - return this; - } - - /** - * Enable or disable validation of public key parameters when unlocking private keys. - * Disabled by default. - * When enabled, PGPainless will validate, whether public key parameters have been tampered with. - * This is a countermeasure against possible attacks described in the paper - * "Victory by KO: Attacking OpenPGP Using Key Overwriting" by Lara Bruseghini, Daniel Huigens, and Kenneth G. Paterson. - * Since these attacks are only possible in very special conditions (attacker has access to the encrypted private key), - * and the countermeasures are very costly, they are disabled by default, but can be enabled using this method. - * - * @see KOpenPGP.com - * @param enable boolean - * @return this - */ - public Policy setEnableKeyParameterValidation(boolean enable) { - this.enableKeyParameterValidation = enable; - return this; - } - - /** - * Return true, if countermeasures against the KOpenPGP attacks are enabled, false otherwise. - * - * @return true if countermeasures are enabled, false otherwise. - */ - public boolean isEnableKeyParameterValidation() { - return enableKeyParameterValidation; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy2.kt b/pgpainless-core/src/main/java/org/pgpainless/policy/Policy2.kt deleted file mode 100644 index e17b9653..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy2.kt +++ /dev/null @@ -1,132 +0,0 @@ -package org.pgpainless.policy - -import org.pgpainless.algorithm.CompressionAlgorithm -import org.pgpainless.algorithm.HashAlgorithm -import org.pgpainless.util.DateUtil -import java.util.* - -class Policy2 { - - /** - * Create a HashAlgorithmPolicy which accepts all [HashAlgorithms][HashAlgorithm] from the - * given map, if the queried usage date is BEFORE the respective termination date. - * A termination date value of
null
means no termination, resulting in the algorithm being - * acceptable, regardless of usage date. - * - * @param defaultHashAlgorithm default hash algorithm - * @param algorithmTerminationDates map of acceptable algorithms and their termination dates - */ - class HashAlgorithmPolicy( - val defaultHashAlgorithm: HashAlgorithm, - val acceptableHashAlgorithmsAndTerminationDates: Map) { - - /** - * Create a [HashAlgorithmPolicy] which accepts all [HashAlgorithms][HashAlgorithm] listed in - * the given list, regardless of usage date. - * - * @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation fails) - * @param acceptableHashAlgorithms list of acceptable hash algorithms - */ - constructor(defaultHashAlgorithm: HashAlgorithm, acceptableHashAlgorithms: List) : - this(defaultHashAlgorithm, acceptableHashAlgorithms.associateWith { null }) - - fun isAcceptable(hashAlgorithm: HashAlgorithm) = isAcceptable(hashAlgorithm, Date()) - - /** - * Return true, if the given algorithm is acceptable for the given usage date. - * - * @param hashAlgorithm algorithm - * @param referenceTime usage date (e.g. signature creation time) - * - * @return acceptance - */ - fun isAcceptable(hashAlgorithm: HashAlgorithm, referenceTime: Date): Boolean { - if (!acceptableHashAlgorithmsAndTerminationDates.containsKey(hashAlgorithm)) - return false - val terminationDate = acceptableHashAlgorithmsAndTerminationDates[hashAlgorithm] - if (terminationDate == null) { - return true - } - return terminationDate > referenceTime - } - - fun isAcceptable(algorithmId: Int, referenceTime: Date): Boolean { - HashAlgorithm.fromId(algorithmId).let { - if (it == null) { - return false - } - return isAcceptable(it, referenceTime) - } - } - - fun isAcceptable(algorithmId: Int) = isAcceptable(algorithmId, Date()) - - companion object { - @JvmStatic - val smartSignatureHashAlgorithmPolicy = HashAlgorithmPolicy(HashAlgorithm.SHA512, buildMap { - put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")) - put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) - put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) - put(HashAlgorithm.SHA224, null) - put(HashAlgorithm.SHA256, null) - put(HashAlgorithm.SHA384, null) - put(HashAlgorithm.SHA512, null) - put(HashAlgorithm.SHA3_256, null) - put(HashAlgorithm.SHA3_512, null) - }) - - /** - * [HashAlgorithmPolicy] which only accepts signatures made using algorithms which are acceptable - * according to 2022 standards. - * - * Particularly this policy only accepts algorithms from the SHA2 and SHA3 families. - * - * @return static signature algorithm policy - */ - @JvmStatic - val static2022SignatureHashAlgorithmPolicy = - HashAlgorithmPolicy(HashAlgorithm.SHA512, listOf( - HashAlgorithm.SHA224, - HashAlgorithm.SHA256, - HashAlgorithm.SHA384, - HashAlgorithm.SHA512, - HashAlgorithm.SHA3_256, - HashAlgorithm.SHA3_512)) - - /** - * Hash algorithm policy for revocation signatures, which accepts SHA1 and SHA2 algorithms, as well as RIPEMD160. - * - * @return static revocation signature hash algorithm policy - */ - @JvmStatic - val static2022RevocationSignatureHashAlgorithmPolicy = - HashAlgorithmPolicy(HashAlgorithm.SHA512, listOf( - HashAlgorithm.RIPEMD160, - HashAlgorithm.SHA1, - HashAlgorithm.SHA224, - HashAlgorithm.SHA256, - HashAlgorithm.SHA384, - HashAlgorithm.SHA512, - HashAlgorithm.SHA3_256, - HashAlgorithm.SHA3_512)) - } - } - - class CompressionAlgorithmPolicy( - val defaultCompressionAlgorithm: CompressionAlgorithm, - val acceptableCompressionAlgorithms: List) { - - fun isAcceptable(algorithm: CompressionAlgorithm) = acceptableCompressionAlgorithms.contains(algorithm) - fun isAcceptable(algorithmId: Int) = - } - - companion object { - private var INSTANCE: Policy2? = null - fun getInstance(): Policy2 { - if (INSTANCE == null) { - INSTANCE = Policy2() - } - return INSTANCE!! - } - } -} \ No newline at end of file diff --git a/pgpainless-core/src/main/java/org/pgpainless/policy/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/policy/package-info.java deleted file mode 100644 index dd248151..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/policy/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Policy regarding used algorithms. - */ -package org.pgpainless.policy; diff --git a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt similarity index 98% rename from pgpainless-core/src/main/java/org/pgpainless/PGPainless.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt index a52b38ae..6aa9799e 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless import org.bouncycastle.openpgp.PGPKeyRing diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/AEADAlgorithm.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/AlgorithmSuite.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CertificationType.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CertificationType.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CompressionAlgorithm.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/CompressionAlgorithm.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CompressionAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/DocumentSignatureType.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/DocumentSignatureType.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/DocumentSignatureType.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/EncryptionPurpose.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/EncryptionPurpose.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/EncryptionPurpose.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/EncryptionPurpose.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Feature.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/Feature.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Feature.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/HashAlgorithm.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/HashAlgorithm.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/HashAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/KeyFlag.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/KeyFlag.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/KeyFlag.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/OpenPgpPacket.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/OpenPgpPacket.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/PublicKeyAlgorithm.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/PublicKeyAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationState.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationState.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationState.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationStateType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationStateType.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/RevocationStateType.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationStateType.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureSubpacket.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureSubpacket.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureSubpacket.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureSubpacket.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureType.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureType.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/StreamEncoding.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/StreamEncoding.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/StreamEncoding.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthenticity.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthenticity.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthenticity.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthority.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthority.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/authentication/CertificateAuthority.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthority.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt similarity index 98% rename from pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt index 9c7409de..c5a2a24f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.key import org.bouncycastle.openpgp.PGPKeyRing diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV4Fingerprint.kt similarity index 94% rename from pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV4Fingerprint.kt index 72c31f4e..fe0c4364 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV4Fingerprint.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.key import org.bouncycastle.openpgp.PGPKeyRing diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV5Fingerprint.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV5Fingerprint.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV5Fingerprint.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV6Fingerprint.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV6Fingerprint.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/_64DigitFingerprint.kt similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.kt rename to pgpainless-core/src/main/kotlin/org/pgpainless/key/_64DigitFingerprint.kt diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt new file mode 100644 index 00000000..b7c9f39e --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt @@ -0,0 +1,359 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.policy + +import org.pgpainless.algorithm.* +import org.pgpainless.util.DateUtil +import org.pgpainless.util.NotationRegistry +import java.util.* + +class Policy( + var signatureHashAlgorithmPolicy: HashAlgorithmPolicy, + var revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + var symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, + var symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, + var compressionAlgorithmPolicy: CompressionAlgorithmPolicy, + var publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy, + var notationRegistry: NotationRegistry) { + + constructor(): this( + HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(), + HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(), + SymmetricKeyAlgorithmPolicy.symmetricKeyEncryptionPolicy2022(), + SymmetricKeyAlgorithmPolicy.symmetricKeyDecryptionPolicy2022(), + CompressionAlgorithmPolicy.anyCompressionAlgorithmPolicy(), + PublicKeyAlgorithmPolicy.bsi2021PublicKeyAlgorithmPolicy(), + NotationRegistry()) + + var keyGenerationAlgorithmSuite = AlgorithmSuite.defaultAlgorithmSuite + var signerUserIdValidationLevel = SignerUserIdValidationLevel.DISABLED + var enableKeyParameterValidation = false + + fun isEnableKeyParameterValidation() = enableKeyParameterValidation + + + /** + * Create a HashAlgorithmPolicy which accepts all [HashAlgorithms][HashAlgorithm] from the + * given map, if the queried usage date is BEFORE the respective termination date. + * A termination date value of
null
means no termination, resulting in the algorithm being + * acceptable, regardless of usage date. + * + * @param defaultHashAlgorithm default hash algorithm + * @param algorithmTerminationDates map of acceptable algorithms and their termination dates + */ + class HashAlgorithmPolicy( + val defaultHashAlgorithm: HashAlgorithm, + val acceptableHashAlgorithmsAndTerminationDates: Map) { + + /** + * Create a [HashAlgorithmPolicy] which accepts all [HashAlgorithms][HashAlgorithm] listed in + * the given list, regardless of usage date. + * + * @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation fails) + * @param acceptableHashAlgorithms list of acceptable hash algorithms + */ + constructor(defaultHashAlgorithm: HashAlgorithm, acceptableHashAlgorithms: List) : + this(defaultHashAlgorithm, acceptableHashAlgorithms.associateWith { null }) + + fun isAcceptable(hashAlgorithm: HashAlgorithm) = isAcceptable(hashAlgorithm, Date()) + + /** + * Return true, if the given algorithm is acceptable for the given usage date. + * + * @param hashAlgorithm algorithm + * @param referenceTime usage date (e.g. signature creation time) + * + * @return acceptance + */ + fun isAcceptable(hashAlgorithm: HashAlgorithm, referenceTime: Date): Boolean { + if (!acceptableHashAlgorithmsAndTerminationDates.containsKey(hashAlgorithm)) + return false + val terminationDate = acceptableHashAlgorithmsAndTerminationDates[hashAlgorithm] ?: return true + return terminationDate > referenceTime + } + + fun isAcceptable(algorithmId: Int) = isAcceptable(algorithmId, Date()) + + fun isAcceptable(algorithmId: Int, referenceTime: Date): Boolean { + val algorithm = HashAlgorithm.fromId(algorithmId) ?: return false + return isAcceptable(algorithm, referenceTime) + } + + fun defaultHashAlgorithm() = defaultHashAlgorithm + + companion object { + @JvmStatic + fun smartSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( + HashAlgorithm.SHA512, buildMap { + put(HashAlgorithm.SHA3_512, null) + put(HashAlgorithm.SHA3_256, null) + put(HashAlgorithm.SHA512, null) + put(HashAlgorithm.SHA384, null) + put(HashAlgorithm.SHA256, null) + put(HashAlgorithm.SHA224, null) + put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) + put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) + put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")) + }) + + /** + * [HashAlgorithmPolicy] which only accepts signatures made using algorithms which are acceptable + * according to 2022 standards. + * + * Particularly this policy only accepts algorithms from the SHA2 and SHA3 families. + * + * @return static signature algorithm policy + */ + @JvmStatic + fun static2022SignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( + HashAlgorithm.SHA512, + listOf( + HashAlgorithm.SHA3_512, + HashAlgorithm.SHA3_256, + HashAlgorithm.SHA512, + HashAlgorithm.SHA384, + HashAlgorithm.SHA256, + HashAlgorithm.SHA224) + ) + + /** + * Hash algorithm policy for revocation signatures, which accepts SHA1 and SHA2 algorithms, as well as RIPEMD160. + * + * @return static revocation signature hash algorithm policy + */ + @JvmStatic + fun static2022RevocationSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( + HashAlgorithm.SHA512, + listOf( + HashAlgorithm.SHA3_512, + HashAlgorithm.SHA3_256, + HashAlgorithm.SHA512, + HashAlgorithm.SHA384, + HashAlgorithm.SHA256, + HashAlgorithm.SHA224, + HashAlgorithm.SHA1, + HashAlgorithm.RIPEMD160 + ) + ) + } + } + + class SymmetricKeyAlgorithmPolicy( + val defaultSymmetricKeyAlgorithm: SymmetricKeyAlgorithm, + val acceptableSymmetricKeyAlgorithms: List) { + + fun isAcceptable(algorithm: SymmetricKeyAlgorithm) = acceptableSymmetricKeyAlgorithms.contains(algorithm) + fun isAcceptable(algorithmId: Int): Boolean { + val algorithm = SymmetricKeyAlgorithm.fromId(algorithmId) ?: return false + return isAcceptable(algorithm) + } + + fun selectBest(options: List): SymmetricKeyAlgorithm? { + for (acceptable in acceptableSymmetricKeyAlgorithms) { + if (options.contains(acceptable)) { + return acceptable + } + } + return null + } + + companion object { + + /** + * The default symmetric encryption algorithm policy of PGPainless. + * + * @return default symmetric encryption algorithm policy + * @deprecated not expressive - will be removed in a future release + */ + @JvmStatic + @Deprecated( + "Not expressive - will be removed in a future release", + ReplaceWith("symmetricKeyEncryptionPolicy2022")) + fun defaultSymmetricKeyEncryptionAlgorithmPolicy() = symmetricKeyEncryptionPolicy2022() + + /** + * Policy for symmetric encryption algorithms in the context of message production (encryption). + * This suite contains algorithms that are deemed safe to use in 2022. + * + * @return 2022 symmetric key encryption algorithm policy + */ + @JvmStatic + fun symmetricKeyEncryptionPolicy2022() = SymmetricKeyAlgorithmPolicy( + SymmetricKeyAlgorithm.AES_128, + // Reject: Unencrypted, IDEA, TripleDES, CAST5, Blowfish + listOf( + SymmetricKeyAlgorithm.AES_256, + SymmetricKeyAlgorithm.AES_192, + SymmetricKeyAlgorithm.AES_128, + SymmetricKeyAlgorithm.TWOFISH, + SymmetricKeyAlgorithm.CAMELLIA_256, + SymmetricKeyAlgorithm.CAMELLIA_192, + SymmetricKeyAlgorithm.CAMELLIA_128 + )) + + /** + * The default symmetric decryption algorithm policy of PGPainless. + * + * @return default symmetric decryption algorithm policy + * @deprecated not expressive - will be removed in a future update + */ + @JvmStatic + @Deprecated("not expressive - will be removed in a future update", + ReplaceWith("symmetricKeyDecryptionPolicy2022()")) + fun defaultSymmetricKeyDecryptionAlgorithmPolicy() = symmetricKeyDecryptionPolicy2022() + + /** + * Policy for symmetric key encryption algorithms in the context of message consumption (decryption). + * This suite contains algorithms that are deemed safe to use in 2022. + * + * @return 2022 symmetric key decryption algorithm policy + */ + @JvmStatic + fun symmetricKeyDecryptionPolicy2022() = SymmetricKeyAlgorithmPolicy( + SymmetricKeyAlgorithm.AES_128, + // Reject: Unencrypted, IDEA, TripleDES, Blowfish + listOf( + SymmetricKeyAlgorithm.AES_256, + SymmetricKeyAlgorithm.AES_192, + SymmetricKeyAlgorithm.AES_128, + SymmetricKeyAlgorithm.TWOFISH, + SymmetricKeyAlgorithm.CAMELLIA_256, + SymmetricKeyAlgorithm.CAMELLIA_192, + SymmetricKeyAlgorithm.CAMELLIA_128, + SymmetricKeyAlgorithm.CAST5 + )) + } + } + + class CompressionAlgorithmPolicy( + val defaultCompressionAlgorithm: CompressionAlgorithm, + val acceptableCompressionAlgorithms: List) { + + fun isAcceptable(algorithm: CompressionAlgorithm) = acceptableCompressionAlgorithms.contains(algorithm) + fun isAcceptable(algorithmId: Int): Boolean { + val algorithm = CompressionAlgorithm.fromId(algorithmId) ?: return false + return isAcceptable(algorithm) + } + + fun defaultCompressionAlgorithm() = defaultCompressionAlgorithm + + companion object { + + /** + * Default {@link CompressionAlgorithmPolicy} of PGPainless. + * The default compression algorithm policy accepts any compression algorithm. + * + * @return default algorithm policy + * @deprecated not expressive - might be removed in a future release + */ + @JvmStatic + @Deprecated("not expressive - might be removed in a future release", + ReplaceWith("anyCompressionAlgorithmPolicy()")) + fun defaultCompressionAlgorithmPolicy() = anyCompressionAlgorithmPolicy() + + /** + * Policy that accepts any known compression algorithm and offers [CompressionAlgorithm.ZIP] as + * default algorithm. + * + * @return compression algorithm policy + */ + @JvmStatic + fun anyCompressionAlgorithmPolicy() = CompressionAlgorithmPolicy( + CompressionAlgorithm.ZIP, + listOf(CompressionAlgorithm.UNCOMPRESSED, + CompressionAlgorithm.ZIP, + CompressionAlgorithm.BZIP2, + CompressionAlgorithm.ZLIB)) + } + } + + class PublicKeyAlgorithmPolicy(private val algorithmStrengths: Map) { + + fun isAcceptable(algorithm: PublicKeyAlgorithm, bitStrength: Int): Boolean { + return bitStrength >= (algorithmStrengths[algorithm] ?: return false) + } + + fun isAcceptable(algorithmId: Int, bitStrength: Int): Boolean { + val algorithm = PublicKeyAlgorithm.fromId(algorithmId) ?: return false + return isAcceptable(algorithm, bitStrength) + } + + companion object { + + /** + * Return PGPainless' default public key algorithm policy. + * This policy is based upon recommendations made by the German Federal Office for Information Security (BSI). + * + * @return default algorithm policy + * @deprecated not expressive - might be removed in a future release + */ + @JvmStatic + @Deprecated("not expressive - might be removed in a future release", + ReplaceWith("bsi2021PublicKeyAlgorithmPolicy()")) + fun defaultPublicKeyAlgorithmPolicy() = bsi2021PublicKeyAlgorithmPolicy() + + /** + * This policy is based upon recommendations made by the German Federal Office for Information Security (BSI). + * + * Basically this policy requires keys based on elliptic curves to have a bit strength of at least 250, + * and keys based on prime number factorization / discrete logarithm problems to have a strength of at least 2000 bits. + * + * @see BSI - Technical Guideline - Cryptographic Mechanisms: Recommendations and Key Lengths (2021-01) + * @see BlueKrypt | Cryptographic Key Length Recommendation + * + * @return default algorithm policy + */ + @JvmStatic + fun bsi2021PublicKeyAlgorithmPolicy() = PublicKeyAlgorithmPolicy(buildMap { + // §5.4.1 + put(PublicKeyAlgorithm.RSA_GENERAL, 2000) + put(PublicKeyAlgorithm.RSA_SIGN, 2000) + put(PublicKeyAlgorithm.RSA_ENCRYPT, 2000) + // Note: ElGamal is not mentioned in the BSI document. + // We assume that the requirements are similar to other DH algorithms + put(PublicKeyAlgorithm.ELGAMAL_ENCRYPT, 2000) + put(PublicKeyAlgorithm.ELGAMAL_GENERAL, 2000) + // §5.4.2 + put(PublicKeyAlgorithm.DSA, 2000) + // §5.4.3 + put(PublicKeyAlgorithm.ECDSA, 250) + // Note: EdDSA is not mentioned in the BSI document. + // We assume that the requirements are similar to other EC algorithms. + put(PublicKeyAlgorithm.EDDSA, 250) + // §7.2.1 + put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000) + // §7.2.2 + put(PublicKeyAlgorithm.ECDH, 250) + }) + } + } + + enum class SignerUserIdValidationLevel { + /** + * PGPainless will verify {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets in signatures strictly. + * This means, that signatures with Signer's User-ID subpackets containing a value that does not match the signer key's + * user-id exactly, will be rejected. + * E.g. Signer's user-id "alice@pgpainless.org", User-ID: "Alice <alice@pgpainless.org>" does not + * match exactly and is therefore rejected. + */ + STRICT, + + /** + * PGPainless will ignore {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets on signature. + */ + DISABLED + } + + companion object { + + @Volatile + private var INSTANCE: Policy? = null + + @JvmStatic + fun getInstance() = INSTANCE ?: synchronized(this) { + INSTANCE ?: Policy().also { INSTANCE = it } + } + } +} \ No newline at end of file From 39c5d12096cf27258ba7bd31092d821fe09b6f4a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Aug 2023 13:28:38 +0200 Subject: [PATCH 033/155] Use IntRange for Trustworthiness range check --- .../kotlin/org/pgpainless/algorithm/Trustworthiness.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt index b1a1b7dd..695632a2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt @@ -62,11 +62,13 @@ class Trustworthiness(amount: Int, depth: Int) { fun canIntroduce(other: Trustworthiness) = canIntroduce(other.depth) companion object { - const val THRESHOLD_FULLY_CONVINCED = 120 // greater or equal is fully trusted const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced const val NOT_TRUSTED = 0 // 0 is not trusted + @JvmStatic + private val validRange = 0..255 + /** * This means that we are fully convinced of the trustworthiness of the key. * @@ -94,7 +96,7 @@ class Trustworthiness(amount: Int, depth: Int) { @JvmStatic private fun capAmount(amount: Int): Int { - if (amount !in 0..255) { + if (amount !in validRange) { throw IllegalArgumentException("Trust amount MUST be a value between 0 and 255") } return amount @@ -102,7 +104,7 @@ class Trustworthiness(amount: Int, depth: Int) { @JvmStatic private fun capDepth(depth: Int): Int { - if (depth !in 0..255) { + if (depth !in validRange) { throw IllegalArgumentException("Trust depth MUST be a value between 0 and 255") } return depth From de73b3b00be4e0e3ddc67c5255b8de333f685cbb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Oct 2023 12:29:27 +0200 Subject: [PATCH 034/155] Kotlin conversion: CertifyCertificate --- .../key/certification/CertifyCertificate.java | 298 ------------------ .../key/certification/package-info.java | 8 - .../key/certification/CertifyCertificate.kt | 228 ++++++++++++++ 3 files changed, 228 insertions(+), 306 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/certification/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java b/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java deleted file mode 100644 index 5b6c6fe2..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java +++ /dev/null @@ -1,298 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.certification; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.CertificationType; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.Trustworthiness; -import org.pgpainless.exception.KeyException; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.util.KeyRingUtils; -import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder; -import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder; -import org.pgpainless.signature.subpackets.CertificationSubpackets; -import org.pgpainless.util.DateUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Date; - -/** - * API for creating certifications and delegations (Signatures) on keys. - * This API can be used to sign another persons OpenPGP key. - * - * A certification over a user-id is thereby used to attest, that the user believes that the user-id really belongs - * to the owner of the certificate. - * A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer. - */ -public class CertifyCertificate { - - /** - * Create a certification over a User-Id. - * By default, this method will use {@link CertificationType#GENERIC} to create the signature. - * If you need to create another type of certification, use - * {@link #userIdOnCertificate(String, PGPPublicKeyRing, CertificationType)} instead. - * - * @param userId user-id to certify - * @param certificate certificate - * @return API - */ - public CertificationOnUserId userIdOnCertificate(@Nonnull String userId, - @Nonnull PGPPublicKeyRing certificate) { - return userIdOnCertificate(userId, certificate, CertificationType.GENERIC); - } - - /** - * Create a certification of the given {@link CertificationType} over a User-Id. - * - * @param userid user-id to certify - * @param certificate certificate - * @param certificationType type of signature - * @return API - */ - public CertificationOnUserId userIdOnCertificate(@Nonnull String userid, - @Nonnull PGPPublicKeyRing certificate, - @Nonnull CertificationType certificationType) { - return new CertificationOnUserId(userid, certificate, certificationType); - } - - /** - * Create a delegation (direct key signature) over a certificate. - * This can be used to mark a certificate as a trusted introducer - * (see {@link #certificate(PGPPublicKeyRing, Trustworthiness)}). - * - * @param certificate certificate - * @return API - */ - public DelegationOnCertificate certificate(@Nonnull PGPPublicKeyRing certificate) { - return certificate(certificate, null); - } - - /** - * Create a delegation (direct key signature) containing a {@link org.bouncycastle.bcpg.sig.TrustSignature} packet - * over a certificate. - * This can be used to mark a certificate as a trusted introducer. - * - * @param certificate certificate - * @param trustworthiness trustworthiness of the certificate - * @return API - */ - public DelegationOnCertificate certificate(@Nonnull PGPPublicKeyRing certificate, - @Nullable Trustworthiness trustworthiness) { - return new DelegationOnCertificate(certificate, trustworthiness); - } - - public static class CertificationOnUserId { - - private final PGPPublicKeyRing certificate; - private final String userId; - private final CertificationType certificationType; - - CertificationOnUserId(@Nonnull String userId, - @Nonnull PGPPublicKeyRing certificate, - @Nonnull CertificationType certificationType) { - this.userId = userId; - this.certificate = certificate; - this.certificationType = certificationType; - } - - /** - * Create the certification using the given key. - * - * @param certificationKey key used to create the certification - * @param protector protector to unlock the certification key - * @return API - * @throws PGPException in case of an OpenPGP related error - */ - public CertificationOnUserIdWithSubpackets withKey(@Nonnull PGPSecretKeyRing certificationKey, - @Nonnull SecretKeyRingProtector protector) - throws PGPException { - PGPSecretKey secretKey = getCertifyingSecretKey(certificationKey); - - ThirdPartyCertificationSignatureBuilder sigBuilder = new ThirdPartyCertificationSignatureBuilder( - certificationType.asSignatureType(), secretKey, protector); - - return new CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder); - } - } - - public static class CertificationOnUserIdWithSubpackets { - - private final PGPPublicKeyRing certificate; - private final String userId; - private final ThirdPartyCertificationSignatureBuilder sigBuilder; - - CertificationOnUserIdWithSubpackets(@Nonnull PGPPublicKeyRing certificate, - @Nonnull String userId, - @Nonnull ThirdPartyCertificationSignatureBuilder sigBuilder) { - this.certificate = certificate; - this.userId = userId; - this.sigBuilder = sigBuilder; - } - - /** - * Apply the given signature subpackets and build the certification. - * - * @param subpacketCallback callback to modify the signatures subpackets - * @return result - * @throws PGPException in case of an OpenPGP related error - */ - public CertificationResult buildWithSubpackets(@Nonnull CertificationSubpackets.Callback subpacketCallback) - throws PGPException { - sigBuilder.applyCallback(subpacketCallback); - return build(); - } - - /** - * Build the certification signature. - * - * @return result - * @throws PGPException in case of an OpenPGP related error - */ - public CertificationResult build() throws PGPException { - PGPSignature signature = sigBuilder.build(certificate, userId); - PGPPublicKeyRing certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature); - return new CertificationResult(certifiedCertificate, signature); - } - } - - public static class DelegationOnCertificate { - - private final PGPPublicKeyRing certificate; - private final Trustworthiness trustworthiness; - - DelegationOnCertificate(@Nonnull PGPPublicKeyRing certificate, - @Nullable Trustworthiness trustworthiness) { - this.certificate = certificate; - this.trustworthiness = trustworthiness; - } - - /** - * Build the delegation using the given certification key. - * - * @param certificationKey key to create the certification with - * @param protector protector to unlock the certification key - * @return API - * @throws PGPException in case of an OpenPGP related error - */ - public DelegationOnCertificateWithSubpackets withKey(@Nonnull PGPSecretKeyRing certificationKey, - @Nonnull SecretKeyRingProtector protector) - throws PGPException { - PGPSecretKey secretKey = getCertifyingSecretKey(certificationKey); - - ThirdPartyDirectKeySignatureBuilder sigBuilder = new ThirdPartyDirectKeySignatureBuilder(secretKey, protector); - if (trustworthiness != null) { - sigBuilder.getHashedSubpackets().setTrust(true, trustworthiness.getDepth(), trustworthiness.getAmount()); - } - return new DelegationOnCertificateWithSubpackets(certificate, sigBuilder); - } - } - - public static class DelegationOnCertificateWithSubpackets { - - private final PGPPublicKeyRing certificate; - private final ThirdPartyDirectKeySignatureBuilder sigBuilder; - - DelegationOnCertificateWithSubpackets(@Nonnull PGPPublicKeyRing certificate, - @Nonnull ThirdPartyDirectKeySignatureBuilder sigBuilder) { - this.certificate = certificate; - this.sigBuilder = sigBuilder; - } - - /** - * Apply the given signature subpackets and build the delegation signature. - * - * @param subpacketsCallback callback to modify the signatures subpackets - * @return result - * @throws PGPException in case of an OpenPGP related error - */ - public CertificationResult buildWithSubpackets(@Nonnull CertificationSubpackets.Callback subpacketsCallback) - throws PGPException { - sigBuilder.applyCallback(subpacketsCallback); - return build(); - } - - /** - * Build the delegation signature. - * - * @return result - * @throws PGPException in case of an OpenPGP related error - */ - public CertificationResult build() throws PGPException { - PGPPublicKey delegatedKey = certificate.getPublicKey(); - PGPSignature delegation = sigBuilder.build(delegatedKey); - PGPPublicKeyRing delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation); - return new CertificationResult(delegatedCertificate, delegation); - } - } - - public static class CertificationResult { - - private final PGPPublicKeyRing certificate; - private final PGPSignature certification; - - CertificationResult(@Nonnull PGPPublicKeyRing certificate, @Nonnull PGPSignature certification) { - this.certificate = certificate; - this.certification = certification; - } - - /** - * Return the signature. - * - * @return signature - */ - @Nonnull - public PGPSignature getCertification() { - return certification; - } - - /** - * Return the certificate, which now contains the signature. - * - * @return certificate + signature - */ - @Nonnull - public PGPPublicKeyRing getCertifiedCertificate() { - return certificate; - } - } - - private static PGPSecretKey getCertifyingSecretKey(PGPSecretKeyRing certificationKey) { - Date now = DateUtil.now(); - KeyRingInfo info = PGPainless.inspectKeyRing(certificationKey, now); - - // We only support certification-capable primary keys - OpenPgpFingerprint fingerprint = info.getFingerprint(); - PGPPublicKey certificationPubKey = info.getPublicKey(fingerprint); - assert (certificationPubKey != null); - if (!info.isKeyValidlyBound(certificationPubKey.getKeyID())) { - throw new KeyException.RevokedKeyException(fingerprint); - } - - if (!info.isUsableForThirdPartyCertification()) { - throw new KeyException.UnacceptableThirdPartyCertificationKeyException(fingerprint); - } - - Date expirationDate = info.getExpirationDateForUse(KeyFlag.CERTIFY_OTHER); - if (expirationDate != null && expirationDate.before(now)) { - throw new KeyException.ExpiredKeyException(fingerprint, expirationDate); - } - - PGPSecretKey secretKey = certificationKey.getSecretKey(certificationPubKey.getKeyID()); - if (secretKey == null) { - throw new KeyException.MissingSecretKeyException(fingerprint, certificationPubKey.getKeyID()); - } - return secretKey; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/certification/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/certification/package-info.java deleted file mode 100644 index db2f4857..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/certification/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * API for key certifications. - */ -package org.pgpainless.key.certification; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt new file mode 100644 index 00000000..a924e0f3 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.certification + +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.CertificationType +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.algorithm.Trustworthiness +import org.pgpainless.exception.KeyException +import org.pgpainless.exception.KeyException.ExpiredKeyException +import org.pgpainless.exception.KeyException.MissingSecretKeyException +import org.pgpainless.exception.KeyException.RevokedKeyException +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.util.KeyRingUtils +import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder +import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder +import org.pgpainless.signature.subpackets.CertificationSubpackets +import java.util.* + +/** + * API for creating certifications and delegations (Signatures) on keys. + * This API can be used to sign another persons OpenPGP key. + * + * A certification over a user-id is thereby used to attest, that the user believes that the user-id really belongs + * to the owner of the certificate. + * A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer. + */ +class CertifyCertificate { + + /** + * Create a certification over a User-Id. + * By default, this method will use [CertificationType.GENERIC] to create the signature. + * + * @param userId user-id to certify + * @param certificate certificate + * @return API + */ + fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing): CertificationOnUserId = + userIdOnCertificate(userId, certificate, CertificationType.GENERIC) + + /** + * Create a certification of the given [CertificationType] over a User-Id. + * + * @param userid user-id to certify + * @param certificate certificate + * @param certificationType type of signature + * @return API + */ + fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing, certificationType: CertificationType) = + CertificationOnUserId(userId, certificate, certificationType) + + /** + * Create a delegation (direct key signature) over a certificate. + * This can be used to mark a certificate as a trusted introducer + * (see [certificate] method with [Trustworthiness] argument). + * + * @param certificate certificate + * @return API + */ + fun certificate(certificate: PGPPublicKeyRing): DelegationOnCertificate = + certificate(certificate, null) + + /** + * Create a delegation (direct key signature) containing a [org.bouncycastle.bcpg.sig.TrustSignature] packet + * over a certificate. + * This can be used to mark a certificate as a trusted introducer. + * + * @param certificate certificate + * @param trustworthiness trustworthiness of the certificate + * @return API + */ + fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) = + DelegationOnCertificate(certificate, trustworthiness) + + class CertificationOnUserId( + val userId: String, + val certificate: PGPPublicKeyRing, + val certificationType: CertificationType) { + + /** + * Create the certification using the given key. + * + * @param certificationKey key used to create the certification + * @param protector protector to unlock the certification key + * @return API + * @throws PGPException in case of an OpenPGP related error + */ + fun withKey(certificationKey: PGPSecretKeyRing, + protector: SecretKeyRingProtector): CertificationOnUserIdWithSubpackets { + + val secretKey = getCertifyingSecretKey(certificationKey) + val sigBuilder = ThirdPartyCertificationSignatureBuilder( + certificationType.asSignatureType(), secretKey, protector) + + return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder) + } + } + + class CertificationOnUserIdWithSubpackets( + val certificate: PGPPublicKeyRing, + val userId: String, + val sigBuilder: ThirdPartyCertificationSignatureBuilder + ) { + + /** + * Apply the given signature subpackets and build the certification. + * + * @param subpacketCallback callback to modify the signatures subpackets + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + fun buildWithSubpackets(subpacketCallback: CertificationSubpackets.Callback): CertificationResult { + sigBuilder.applyCallback(subpacketCallback) + return build() + } + + /** + * Build the certification signature. + * + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + fun build(): CertificationResult { + val signature = sigBuilder.build(certificate, userId) + val certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature) + return CertificationResult(certifiedCertificate, signature) + } + } + + class DelegationOnCertificate( + val certificate: PGPPublicKeyRing, + val trustworthiness: Trustworthiness?) { + + /** + * Build the delegation using the given certification key. + * + * @param certificationKey key to create the certification with + * @param protector protector to unlock the certification key + * @return API + * @throws PGPException in case of an OpenPGP related error + */ + fun withKey(certificationKey: PGPSecretKeyRing, + protector: SecretKeyRingProtector): DelegationOnCertificateWithSubpackets { + val secretKey = getCertifyingSecretKey(certificationKey) + val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector) + if (trustworthiness != null) { + sigBuilder.hashedSubpackets.setTrust(true, trustworthiness.depth, trustworthiness.amount) + } + return DelegationOnCertificateWithSubpackets(certificate, sigBuilder) + } + } + + class DelegationOnCertificateWithSubpackets( + val certificate: PGPPublicKeyRing, + val sigBuilder: ThirdPartyDirectKeySignatureBuilder) { + + /** + * Apply the given signature subpackets and build the delegation signature. + * + * @param subpacketsCallback callback to modify the signatures subpackets + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + fun buildWithSubpackets(subpacketsCallback: CertificationSubpackets.Callback): CertificationResult { + sigBuilder.applyCallback(subpacketsCallback) + return build() + } + + /** + * Build the delegation signature. + * + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + fun build(): CertificationResult { + val delegatedKey = certificate.publicKey + val delegation = sigBuilder.build(delegatedKey) + val delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation) + return CertificationResult(delegatedCertificate, delegation) + } + } + + /** + * Result of a certification operation. + * + * @param certifiedCertificate certificate which now contains the newly created signature + * @param certification the newly created signature + */ + data class CertificationResult( + val certifiedCertificate: PGPPublicKeyRing, + val certification: PGPSignature) + + companion object { + @JvmStatic + private fun getCertifyingSecretKey(certificationKey: PGPSecretKeyRing): PGPSecretKey { + val now = Date() + val info = PGPainless.inspectKeyRing(certificationKey, now) + + val fingerprint = info.fingerprint + val certificationPubKey = info.getPublicKey(fingerprint) + requireNotNull(certificationPubKey) { + "Primary key cannot be null." + } + if (!info.isKeyValidlyBound(certificationPubKey.keyID)) { + throw RevokedKeyException(fingerprint) + } + + if (!info.isUsableForThirdPartyCertification) { + throw KeyException.UnacceptableThirdPartyCertificationKeyException(fingerprint) + } + + val expirationDate = info.getExpirationDateForUse(KeyFlag.CERTIFY_OTHER) + if (expirationDate != null && expirationDate < now) { + throw ExpiredKeyException(fingerprint, expirationDate) + } + + return certificationKey.getSecretKey(certificationPubKey.keyID) + ?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID) + } + } +} \ No newline at end of file From a1a090028d9c5f9ff1ca16b087c511752df0c85c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 24 Aug 2023 16:09:15 +0200 Subject: [PATCH 035/155] Kotlin conversion: PDA --- .../syntax_check/PDA.java | 156 ------------------ .../syntax_check/PDA.kt | 120 ++++++++++++++ 2 files changed, 120 insertions(+), 156 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/PDA.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/PDA.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/PDA.java deleted file mode 100644 index 7d7cf973..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/PDA.java +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.syntax_check; - -import org.pgpainless.exception.MalformedOpenPgpMessageException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Stack; - -import static org.pgpainless.decryption_verification.syntax_check.StackSymbol.msg; -import static org.pgpainless.decryption_verification.syntax_check.StackSymbol.terminus; - -/** - * Pushdown Automaton for validating context-free languages. - * In PGPainless, this class is used to validate OpenPGP message packet sequences against the allowed syntax. - * - * @see OpenPGP Message Syntax - */ -public class PDA { - - private static final Logger LOGGER = LoggerFactory.getLogger(PDA.class); - - // right now we implement what rfc4880 specifies. - // TODO: Consider implementing what we proposed here: - // https://mailarchive.ietf.org/arch/msg/openpgp/uepOF6XpSegMO4c59tt9e5H1i4g/ - private final Syntax syntax; - private final Stack stack = new Stack<>(); - private final List inputs = new ArrayList<>(); // Track inputs for debugging / error reporting - private State state; - - /** - * Default constructor which initializes the PDA to work with the {@link OpenPgpMessageSyntax}. - */ - public PDA() { - this(new OpenPgpMessageSyntax(), State.OpenPgpMessage, terminus, msg); - } - - /** - * Construct a PDA with a custom {@link Syntax}, initial {@link State} and initial {@link StackSymbol StackSymbols}. - * - * @param syntax syntax - * @param initialState initial state - * @param initialStack zero or more initial stack items (get pushed onto the stack in order of appearance) - */ - public PDA(@Nonnull Syntax syntax, @Nonnull State initialState, @Nonnull StackSymbol... initialStack) { - this.syntax = syntax; - this.state = initialState; - for (StackSymbol symbol : initialStack) { - pushStack(symbol); - } - } - - /** - * Process the next {@link InputSymbol}. - * This will either leave the PDA in the next state, or throw a {@link MalformedOpenPgpMessageException} if the - * input symbol is rejected. - * - * @param input input symbol - * @throws MalformedOpenPgpMessageException if the input symbol is rejected - */ - public void next(@Nonnull InputSymbol input) - throws MalformedOpenPgpMessageException { - StackSymbol stackSymbol = popStack(); - try { - Transition transition = syntax.transition(state, input, stackSymbol); - state = transition.getNewState(); - for (StackSymbol item : transition.getPushedItems()) { - pushStack(item); - } - inputs.add(input); - } catch (MalformedOpenPgpMessageException e) { - MalformedOpenPgpMessageException wrapped = new MalformedOpenPgpMessageException( - "Malformed message: After reading packet sequence " + Arrays.toString(inputs.toArray()) + - ", token '" + input + "' is not allowed." + - "\nNo transition from state '" + state + "' with stack " + Arrays.toString(stack.toArray()) + - (stackSymbol != null ? "||'" + stackSymbol + "'." : "."), e); - LOGGER.debug("Invalid input '" + input + "'", wrapped); - throw wrapped; - } - } - - /** - * Return the current state of the PDA. - * - * @return state - */ - public @Nonnull State getState() { - return state; - } - - /** - * Peek at the stack, returning the topmost stack item without changing the stack. - * - * @return topmost stack item, or null if stack is empty - */ - public @Nullable StackSymbol peekStack() { - if (stack.isEmpty()) { - return null; - } - return stack.peek(); - } - - /** - * Return true, if the PDA is in a valid state (the OpenPGP message is valid). - * - * @return true if valid, false otherwise - */ - public boolean isValid() { - return getState() == State.Valid && stack.isEmpty(); - } - - /** - * Throw a {@link MalformedOpenPgpMessageException} if the pda is not in a valid state right now. - * - * @throws MalformedOpenPgpMessageException if the pda is not in an acceptable state - */ - public void assertValid() throws MalformedOpenPgpMessageException { - if (!isValid()) { - throw new MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: " + toString()); - } - } - - /** - * Pop an item from the stack. - * - * @return stack item - */ - private StackSymbol popStack() { - if (stack.isEmpty()) { - return null; - } - return stack.pop(); - } - - /** - * Push an item onto the stack. - * - * @param item item - */ - private void pushStack(StackSymbol item) { - stack.push(item); - } - - @Override - public String toString() { - return "State: " + state + " Stack: " + stack; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt new file mode 100644 index 00000000..ae79909f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.syntax_check + +import org.pgpainless.exception.MalformedOpenPgpMessageException +import org.slf4j.LoggerFactory + +/** + * Pushdown Automaton for validating context-free languages. + * In PGPainless, this class is used to validate OpenPGP message packet sequences against the allowed syntax. + * + * See [OpenPGP Message Syntax](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) + */ +class PDA constructor( + private val syntax: Syntax, + private val stack: ArrayDeque, + private val inputs: MutableList, + private var state: State +) { + + /** + * Construct a PDA with a custom [Syntax], initial [State] and initial [StackSymbols][StackSymbol]. + * + * @param syntax syntax + * @param initialState initial state + * @param initialStack zero or more initial stack items (get pushed onto the stack in order of appearance) + */ + constructor(syntax: Syntax, initialState: State, vararg initialStack: StackSymbol): this ( + syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState) + + /** + * Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax]. + */ + constructor(): this(OpenPgpMessageSyntax(), State.OpenPgpMessage, StackSymbol.terminus, StackSymbol.msg) + + /** + * Process the next [InputSymbol]. + * This will either leave the PDA in the next state, or throw a [MalformedOpenPgpMessageException] if the + * input symbol is rejected. + * + * @param input input symbol + * @throws MalformedOpenPgpMessageException if the input symbol is rejected + */ + fun next(input: InputSymbol) { + val stackSymbol = popStack() + try { + val transition = syntax.transition(state, input, stackSymbol) + state = transition.newState + for (item in transition.pushedItems) { + pushStack(item) + } + inputs.add(input) + } catch (e: MalformedOpenPgpMessageException) { + val stackFormat = if (stackSymbol != null) { + "${stack.joinToString()}||$stackSymbol" + } else { + stack.joinToString() + } + val wrapped = MalformedOpenPgpMessageException( + "Malformed message: After reading packet sequence ${inputs.joinToString()}, token '$input' is not allowed.\n" + + "No transition from state '$state' with stack $stackFormat", e) + LOGGER.debug("Invalid input '$input'", wrapped) + throw wrapped + } + } + + /** + * Peek at the stack, returning the topmost stack item without changing the stack. + * + * @return topmost stack item, or null if stack is empty + */ + fun peekStack(): StackSymbol? = stack.firstOrNull() + + /** + * Return true, if the PDA is in a valid state (the OpenPGP message is valid). + * + * @return true if valid, false otherwise + */ + fun isValid(): Boolean = state == State.Valid && stack.isEmpty() + + /** + * Throw a [MalformedOpenPgpMessageException] if the pda is not in a valid state right now. + * + * @throws MalformedOpenPgpMessageException if the pda is not in an acceptable state + */ + fun assertValid() { + if (!isValid()) { + throw MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: ${toString()}") + } + } + + /** + * Pop an item from the stack. + * + * @return stack item + */ + private fun popStack(): StackSymbol? { + return stack.removeFirstOrNull() + } + + /** + * Push an item onto the stack. + * + * @param item item + */ + private fun pushStack(item: StackSymbol) { + stack.addFirst(item) + } + + override fun toString(): String { + return "State: $state Stack: ${stack.joinToString()}" + } + + companion object { + @JvmStatic + private val LOGGER = LoggerFactory.getLogger(PDA::class.java) + } +} \ No newline at end of file From 603f07d014dc7574ec26a1f1e06b113a55141dbe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 24 Aug 2023 16:33:06 +0200 Subject: [PATCH 036/155] Kotlin conversion: Syntax checking --- .../syntax_check/OpenPgpMessageSyntax.java | 142 ------------------ .../syntax_check/Syntax.java | 33 ---- .../syntax_check/Transition.java | 48 ------ .../syntax_check/package-info.java | 8 - .../syntax_check/InputSymbol.kt} | 24 ++- .../syntax_check/OpenPgpMessageSyntax.kt | 88 +++++++++++ .../syntax_check/StackSymbol.kt} | 8 +- .../syntax_check/State.kt} | 8 +- .../syntax_check/Syntax.kt | 30 ++++ .../syntax_check/Transition.kt | 22 +++ 10 files changed, 157 insertions(+), 254 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Syntax.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Transition.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/package-info.java rename pgpainless-core/src/main/{java/org/pgpainless/decryption_verification/syntax_check/InputSymbol.java => kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt} (56%) create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt rename pgpainless-core/src/main/{java/org/pgpainless/decryption_verification/syntax_check/StackSymbol.java => kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt} (65%) rename pgpainless-core/src/main/{java/org/pgpainless/decryption_verification/syntax_check/State.java => kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt} (55%) create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Syntax.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Transition.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java deleted file mode 100644 index 9d20e0a8..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.syntax_check; - -import org.pgpainless.exception.MalformedOpenPgpMessageException; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * This class describes the syntax for OpenPGP messages as specified by rfc4880. - * - * @see - * rfc4880 - §11.3. OpenPGP Messages - * @see - * Blog post about theoretic background and translation of grammar to PDA syntax - * @see - * Blog post about practically implementing the PDA for packet syntax validation - */ -public class OpenPgpMessageSyntax implements Syntax { - - @Override - public @Nonnull Transition transition(@Nonnull State from, @Nonnull InputSymbol input, @Nullable StackSymbol stackItem) - throws MalformedOpenPgpMessageException { - switch (from) { - case OpenPgpMessage: - return fromOpenPgpMessage(input, stackItem); - case LiteralMessage: - return fromLiteralMessage(input, stackItem); - case CompressedMessage: - return fromCompressedMessage(input, stackItem); - case EncryptedMessage: - return fromEncryptedMessage(input, stackItem); - case Valid: - return fromValid(input, stackItem); - } - - throw new MalformedOpenPgpMessageException(from, input, stackItem); - } - - @Nonnull - Transition fromOpenPgpMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem) - throws MalformedOpenPgpMessageException { - if (stackItem != StackSymbol.msg) { - throw new MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem); - } - - switch (input) { - case LiteralData: - return new Transition(State.LiteralMessage); - - case Signature: - return new Transition(State.OpenPgpMessage, StackSymbol.msg); - - case OnePassSignature: - return new Transition(State.OpenPgpMessage, StackSymbol.ops, StackSymbol.msg); - - case CompressedData: - return new Transition(State.CompressedMessage); - - case EncryptedData: - return new Transition(State.EncryptedMessage); - - case EndOfSequence: - default: - throw new MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem); - } - } - - @Nonnull - Transition fromLiteralMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem) - throws MalformedOpenPgpMessageException { - switch (input) { - case Signature: - if (stackItem == StackSymbol.ops) { - return new Transition(State.LiteralMessage); - } - break; - - case EndOfSequence: - if (stackItem == StackSymbol.terminus) { - return new Transition(State.Valid); - } - break; - } - - throw new MalformedOpenPgpMessageException(State.LiteralMessage, input, stackItem); - } - - @Nonnull - Transition fromCompressedMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem) - throws MalformedOpenPgpMessageException { - switch (input) { - case Signature: - if (stackItem == StackSymbol.ops) { - return new Transition(State.CompressedMessage); - } - break; - - case EndOfSequence: - if (stackItem == StackSymbol.terminus) { - return new Transition(State.Valid); - } - break; - } - - throw new MalformedOpenPgpMessageException(State.CompressedMessage, input, stackItem); - } - - @Nonnull - Transition fromEncryptedMessage(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem) - throws MalformedOpenPgpMessageException { - switch (input) { - case Signature: - if (stackItem == StackSymbol.ops) { - return new Transition(State.EncryptedMessage); - } - break; - - case EndOfSequence: - if (stackItem == StackSymbol.terminus) { - return new Transition(State.Valid); - } - break; - } - - throw new MalformedOpenPgpMessageException(State.EncryptedMessage, input, stackItem); - } - - @Nonnull - Transition fromValid(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem) - throws MalformedOpenPgpMessageException { - if (input == InputSymbol.EndOfSequence) { - // allow subsequent read() calls. - return new Transition(State.Valid); - } - // There is no applicable transition rule out of Valid - throw new MalformedOpenPgpMessageException(State.Valid, input, stackItem); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Syntax.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Syntax.java deleted file mode 100644 index 2f3d0a57..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Syntax.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.syntax_check; - -import org.pgpainless.exception.MalformedOpenPgpMessageException; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * This interface can be used to define a custom syntax for the {@link PDA}. - */ -public interface Syntax { - - /** - * Describe a transition rule from {@link State}
from
for {@link InputSymbol}
input
- * with {@link StackSymbol}
stackItem
from the top of the {@link PDA PDAs} stack. - * The resulting {@link Transition} contains the new {@link State}, as well as a list of - * {@link StackSymbol StackSymbols} that get pushed onto the stack by the transition rule. - * If there is no applicable rule, a {@link MalformedOpenPgpMessageException} is thrown, since in this case - * the {@link InputSymbol} must be considered illegal. - * - * @param from current state of the PDA - * @param input input symbol - * @param stackItem item that got popped from the top of the stack - * @return applicable transition rule containing the new state and pushed stack symbols - * @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input symbol is illegal) - */ - @Nonnull Transition transition(@Nonnull State from, @Nonnull InputSymbol input, @Nullable StackSymbol stackItem) - throws MalformedOpenPgpMessageException; -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Transition.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Transition.java deleted file mode 100644 index ab0db5ef..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/Transition.java +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.syntax_check; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Result of applying a transition rule. - * Transition rules can be described by implementing the {@link Syntax} interface. - */ -public class Transition { - - private final List pushedItems = new ArrayList<>(); - private final State newState; - - public Transition(@Nonnull State newState, @Nonnull StackSymbol... pushedItems) { - this.newState = newState; - this.pushedItems.addAll(Arrays.asList(pushedItems)); - } - - /** - * Return the new {@link State} that is reached by applying the transition. - * - * @return new state - */ - @Nonnull - public State getNewState() { - return newState; - } - - /** - * Return a list of {@link StackSymbol StackSymbols} that are pushed onto the stack - * by applying the transition. - * The list contains items in the order in which they are pushed onto the stack. - * The list may be empty. - * - * @return list of items to be pushed onto the stack - */ - @Nonnull - public List getPushedItems() { - return new ArrayList<>(pushedItems); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/package-info.java deleted file mode 100644 index 4df6af5a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Pushdown Automaton to verify validity of packet sequences according to the OpenPGP Message format. - */ -package org.pgpainless.decryption_verification.syntax_check; diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/InputSymbol.java b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt similarity index 56% rename from pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/InputSymbol.java rename to pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt index 854c3305..f189c89f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/InputSymbol.java +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt @@ -1,36 +1,30 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.decryption_verification.syntax_check; +package org.pgpainless.decryption_verification.syntax_check -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPOnePassSignatureList; -import org.bouncycastle.openpgp.PGPSignatureList; - -public enum InputSymbol { +enum class InputSymbol { /** - * A {@link PGPLiteralData} packet. + * A [PGPLiteralData] packet. */ LiteralData, /** - * A {@link PGPSignatureList} object. + * A [PGPSignatureList] object. */ Signature, /** - * A {@link PGPOnePassSignatureList} object. + * A [PGPOnePassSignatureList] object. */ OnePassSignature, /** - * A {@link PGPCompressedData} packet. + * A [PGPCompressedData] packet. * The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify * its nested packet sequence. */ CompressedData, /** - * A {@link PGPEncryptedDataList} object. + * A [PGPEncryptedDataList] object. * This object combines multiple ESKs and the corresponding Symmetrically Encrypted * (possibly Integrity Protected) Data packet. */ @@ -42,4 +36,4 @@ public enum InputSymbol { * (e.g. the end of a Compressed Data packet). */ EndOfSequence -} +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt new file mode 100644 index 00000000..c25476b2 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.syntax_check + +import org.pgpainless.exception.MalformedOpenPgpMessageException + +/** + * This class describes the syntax for OpenPGP messages as specified by rfc4880. + * + * See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) + * See [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/) + * See [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/) + */ +class OpenPgpMessageSyntax : Syntax { + + override fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition { + return when (from) { + State.OpenPgpMessage -> fromOpenPgpMessage(input, stackItem) + State.LiteralMessage -> fromLiteralMessage(input, stackItem) + State.CompressedMessage -> fromCompressedMessage(input, stackItem) + State.EncryptedMessage -> fromEncryptedMessage(input, stackItem) + State.Valid -> fromValid(input, stackItem) + else -> throw MalformedOpenPgpMessageException(from, input, stackItem) + } + } + + fun fromOpenPgpMessage(input: InputSymbol, stackItem: StackSymbol?): Transition { + if (stackItem !== StackSymbol.msg) { + throw MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem) + } + return when (input) { + InputSymbol.LiteralData -> Transition(State.LiteralMessage) + InputSymbol.Signature -> Transition(State.OpenPgpMessage, StackSymbol.msg) + InputSymbol.OnePassSignature -> Transition(State.OpenPgpMessage, StackSymbol.ops, StackSymbol.msg) + InputSymbol.CompressedData -> Transition(State.CompressedMessage) + InputSymbol.EncryptedData -> Transition(State.EncryptedMessage) + InputSymbol.EndOfSequence -> throw MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem) + else -> throw MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem) + } + } + + @Throws(MalformedOpenPgpMessageException::class) + fun fromLiteralMessage(input: InputSymbol, stackItem: StackSymbol?): Transition { + if (input == InputSymbol.Signature && stackItem == StackSymbol.ops) { + return Transition(State.LiteralMessage) + } + if (input == InputSymbol.EndOfSequence && stackItem == StackSymbol.terminus) { + return Transition(State.Valid) + } + + throw MalformedOpenPgpMessageException(State.LiteralMessage, input, stackItem) + } + + @Throws(MalformedOpenPgpMessageException::class) + fun fromCompressedMessage(input: InputSymbol, stackItem: StackSymbol?): Transition { + if (input == InputSymbol.Signature && stackItem == StackSymbol.ops) { + return Transition(State.CompressedMessage) + } + if (input == InputSymbol.EndOfSequence && stackItem == StackSymbol.terminus) { + return Transition(State.Valid) + } + + throw MalformedOpenPgpMessageException(State.CompressedMessage, input, stackItem) + } + + @Throws(MalformedOpenPgpMessageException::class) + fun fromEncryptedMessage(input: InputSymbol, stackItem: StackSymbol?): Transition { + if (input == InputSymbol.Signature && stackItem == StackSymbol.ops) { + return Transition(State.EncryptedMessage) + } + if (input == InputSymbol.EndOfSequence && stackItem == StackSymbol.terminus) { + return Transition(State.Valid) + } + + throw MalformedOpenPgpMessageException(State.EncryptedMessage, input, stackItem) + } + + @Throws(MalformedOpenPgpMessageException::class) + fun fromValid(input: InputSymbol, stackItem: StackSymbol?): Transition { + if (input == InputSymbol.EndOfSequence) { + // allow subsequent read() calls. + return Transition(State.Valid) + } + throw MalformedOpenPgpMessageException(State.Valid, input, stackItem) + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/StackSymbol.java b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt similarity index 65% rename from pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/StackSymbol.java rename to pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt index 120458e5..009a86d4 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/StackSymbol.java +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.decryption_verification.syntax_check; +package org.pgpainless.decryption_verification.syntax_check -public enum StackSymbol { +enum class StackSymbol { /** * OpenPGP Message. */ @@ -17,4 +17,4 @@ public enum StackSymbol { * Special symbol representing the end of the message. */ terminus -} +} \ No newline at end of file diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/State.java b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt similarity index 55% rename from pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/State.java rename to pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt index 9dee9af1..5c3e4906 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/State.java +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt @@ -1,16 +1,16 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.decryption_verification.syntax_check; +package org.pgpainless.decryption_verification.syntax_check /** * Set of states of the automaton. */ -public enum State { +enum class State { OpenPgpMessage, LiteralMessage, CompressedMessage, EncryptedMessage, Valid -} +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Syntax.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Syntax.kt new file mode 100644 index 00000000..16f0445b --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Syntax.kt @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.syntax_check + +import org.pgpainless.exception.MalformedOpenPgpMessageException + +/** + * This interface can be used to define a custom syntax for the [PDA]. + */ +interface Syntax { + + /** + * Describe a transition rule from [State]
from
for [InputSymbol]
input
+ * with [StackSymbol]
stackItem
from the top of the [PDAs][PDA] stack. + * The resulting [Transition] contains the new [State], as well as a list of + * [StackSymbols][StackSymbol] that get pushed onto the stack by the transition rule. + * If there is no applicable rule, a [MalformedOpenPgpMessageException] is thrown, since in this case + * the [InputSymbol] must be considered illegal. + * + * @param from current state of the PDA + * @param input input symbol + * @param stackItem item that got popped from the top of the stack + * @return applicable transition rule containing the new state and pushed stack symbols + * @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input symbol is illegal) + */ + @Throws(MalformedOpenPgpMessageException::class) + fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Transition.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Transition.kt new file mode 100644 index 00000000..5bc6dfc6 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Transition.kt @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.syntax_check + +/** + * Result of applying a transition rule. + * Transition rules can be described by implementing the [Syntax] interface. + * + * @param newState new [State] that is reached by applying the transition. + * @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the transition. + * The list contains items in the order in which they are pushed onto the stack. + * The list may be empty. + */ +class Transition private constructor( + val pushedItems: List, + val newState: State +) { + + constructor(newState: State, vararg pushedItems: StackSymbol): this(pushedItems.toList(), newState) +} \ No newline at end of file From 1ab5377f708c5fa048e32de65a4c98c6d11a5aa6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 24 Aug 2023 16:35:40 +0200 Subject: [PATCH 037/155] Rename syntax checker enums to screaming snake case --- .../OpenPgpMessageInputStream.java | 18 ++-- .../syntax_check/InputSymbol.kt | 12 +-- .../syntax_check/OpenPgpMessageSyntax.kt | 64 +++++------ .../syntax_check/PDA.kt | 4 +- .../syntax_check/StackSymbol.kt | 6 +- .../syntax_check/State.kt | 10 +- .../syntax_check/PDATest.java | 102 +++++++++--------- 7 files changed, 108 insertions(+), 108 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java index b9ead7e7..66ad1edd 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java @@ -342,7 +342,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { private void processLiteralData() throws IOException { LOGGER.debug("Literal Data Packet at depth " + metadata.depth + " encountered"); - syntaxVerifier.next(InputSymbol.LiteralData); + syntaxVerifier.next(InputSymbol.LITERAL_DATA); PGPLiteralData literalData = packetInputStream.readLiteralData(); // Extract Metadata this.metadata.setChild(new MessageMetadata.LiteralData( @@ -354,7 +354,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { } private void processCompressedData() throws IOException, PGPException { - syntaxVerifier.next(InputSymbol.CompressedData); + syntaxVerifier.next(InputSymbol.COMPRESSED_DATA); signatures.enterNesting(); PGPCompressedData compressedData = packetInputStream.readCompressedData(); // Extract Metadata @@ -368,7 +368,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { } private void processOnePassSignature() throws PGPException, IOException { - syntaxVerifier.next(InputSymbol.OnePassSignature); + syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE); PGPOnePassSignature onePassSignature = packetInputStream.readOnePassSignature(); LOGGER.debug("One-Pass-Signature Packet by key " + KeyIdUtil.formatKeyId(onePassSignature.getKeyID()) + " at depth " + metadata.depth + " encountered"); @@ -377,8 +377,8 @@ public class OpenPgpMessageInputStream extends DecryptionStream { private void processSignature() throws PGPException, IOException { // true if Signature corresponds to OnePassSignature - boolean isSigForOPS = syntaxVerifier.peekStack() == StackSymbol.ops; - syntaxVerifier.next(InputSymbol.Signature); + boolean isSigForOPS = syntaxVerifier.peekStack() == StackSymbol.OPS; + syntaxVerifier.next(InputSymbol.SIGNATURE); PGPSignature signature; try { signature = packetInputStream.readSignature(); @@ -404,7 +404,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { private boolean processEncryptedData() throws IOException, PGPException { LOGGER.debug("Symmetrically Encrypted Data Packet at depth " + metadata.depth + " encountered"); - syntaxVerifier.next(InputSymbol.EncryptedData); + syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA); PGPEncryptedDataList encDataList = packetInputStream.readEncryptedDataList(); if (!encDataList.isIntegrityProtected()) { @@ -749,7 +749,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { throws IOException { if (nestedInputStream == null) { if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.EndOfSequence); + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); syntaxVerifier.assertValid(); } return -1; @@ -780,7 +780,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { super.close(); if (closed) { if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.EndOfSequence); + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); syntaxVerifier.assertValid(); } return; @@ -799,7 +799,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { } if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.EndOfSequence); + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); syntaxVerifier.assertValid(); packetInputStream.close(); } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt index f189c89f..133bfcb3 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt @@ -8,32 +8,32 @@ enum class InputSymbol { /** * A [PGPLiteralData] packet. */ - LiteralData, + LITERAL_DATA, /** * A [PGPSignatureList] object. */ - Signature, + SIGNATURE, /** * A [PGPOnePassSignatureList] object. */ - OnePassSignature, + ONE_PASS_SIGNATURE, /** * A [PGPCompressedData] packet. * The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify * its nested packet sequence. */ - CompressedData, + COMPRESSED_DATA, /** * A [PGPEncryptedDataList] object. * This object combines multiple ESKs and the corresponding Symmetrically Encrypted * (possibly Integrity Protected) Data packet. */ - EncryptedData, + ENCRYPTED_DATA, /** * Marks the end of a (sub-) sequence. * This input is given if the end of an OpenPGP message is reached. * This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents * (e.g. the end of a Compressed Data packet). */ - EndOfSequence + END_OF_SEQUENCE } \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt index c25476b2..56bd9a77 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt @@ -17,72 +17,72 @@ class OpenPgpMessageSyntax : Syntax { override fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition { return when (from) { - State.OpenPgpMessage -> fromOpenPgpMessage(input, stackItem) - State.LiteralMessage -> fromLiteralMessage(input, stackItem) - State.CompressedMessage -> fromCompressedMessage(input, stackItem) - State.EncryptedMessage -> fromEncryptedMessage(input, stackItem) - State.Valid -> fromValid(input, stackItem) + State.OPENPGP_MESSAGE -> fromOpenPgpMessage(input, stackItem) + State.LITERAL_MESSAGE -> fromLiteralMessage(input, stackItem) + State.COMPRESSED_MESSAGE -> fromCompressedMessage(input, stackItem) + State.ENCRYPTED_MESSAGE -> fromEncryptedMessage(input, stackItem) + State.VALID -> fromValid(input, stackItem) else -> throw MalformedOpenPgpMessageException(from, input, stackItem) } } fun fromOpenPgpMessage(input: InputSymbol, stackItem: StackSymbol?): Transition { - if (stackItem !== StackSymbol.msg) { - throw MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem) + if (stackItem !== StackSymbol.MSG) { + throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem) } return when (input) { - InputSymbol.LiteralData -> Transition(State.LiteralMessage) - InputSymbol.Signature -> Transition(State.OpenPgpMessage, StackSymbol.msg) - InputSymbol.OnePassSignature -> Transition(State.OpenPgpMessage, StackSymbol.ops, StackSymbol.msg) - InputSymbol.CompressedData -> Transition(State.CompressedMessage) - InputSymbol.EncryptedData -> Transition(State.EncryptedMessage) - InputSymbol.EndOfSequence -> throw MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem) - else -> throw MalformedOpenPgpMessageException(State.OpenPgpMessage, input, stackItem) + InputSymbol.LITERAL_DATA -> Transition(State.LITERAL_MESSAGE) + InputSymbol.SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.MSG) + InputSymbol.ONE_PASS_SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG) + InputSymbol.COMPRESSED_DATA -> Transition(State.COMPRESSED_MESSAGE) + InputSymbol.ENCRYPTED_DATA -> Transition(State.ENCRYPTED_MESSAGE) + InputSymbol.END_OF_SEQUENCE -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem) + else -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem) } } @Throws(MalformedOpenPgpMessageException::class) fun fromLiteralMessage(input: InputSymbol, stackItem: StackSymbol?): Transition { - if (input == InputSymbol.Signature && stackItem == StackSymbol.ops) { - return Transition(State.LiteralMessage) + if (input == InputSymbol.SIGNATURE && stackItem == StackSymbol.OPS) { + return Transition(State.LITERAL_MESSAGE) } - if (input == InputSymbol.EndOfSequence && stackItem == StackSymbol.terminus) { - return Transition(State.Valid) + if (input == InputSymbol.END_OF_SEQUENCE && stackItem == StackSymbol.TERMINUS) { + return Transition(State.VALID) } - throw MalformedOpenPgpMessageException(State.LiteralMessage, input, stackItem) + throw MalformedOpenPgpMessageException(State.LITERAL_MESSAGE, input, stackItem) } @Throws(MalformedOpenPgpMessageException::class) fun fromCompressedMessage(input: InputSymbol, stackItem: StackSymbol?): Transition { - if (input == InputSymbol.Signature && stackItem == StackSymbol.ops) { - return Transition(State.CompressedMessage) + if (input == InputSymbol.SIGNATURE && stackItem == StackSymbol.OPS) { + return Transition(State.COMPRESSED_MESSAGE) } - if (input == InputSymbol.EndOfSequence && stackItem == StackSymbol.terminus) { - return Transition(State.Valid) + if (input == InputSymbol.END_OF_SEQUENCE && stackItem == StackSymbol.TERMINUS) { + return Transition(State.VALID) } - throw MalformedOpenPgpMessageException(State.CompressedMessage, input, stackItem) + throw MalformedOpenPgpMessageException(State.COMPRESSED_MESSAGE, input, stackItem) } @Throws(MalformedOpenPgpMessageException::class) fun fromEncryptedMessage(input: InputSymbol, stackItem: StackSymbol?): Transition { - if (input == InputSymbol.Signature && stackItem == StackSymbol.ops) { - return Transition(State.EncryptedMessage) + if (input == InputSymbol.SIGNATURE && stackItem == StackSymbol.OPS) { + return Transition(State.ENCRYPTED_MESSAGE) } - if (input == InputSymbol.EndOfSequence && stackItem == StackSymbol.terminus) { - return Transition(State.Valid) + if (input == InputSymbol.END_OF_SEQUENCE && stackItem == StackSymbol.TERMINUS) { + return Transition(State.VALID) } - throw MalformedOpenPgpMessageException(State.EncryptedMessage, input, stackItem) + throw MalformedOpenPgpMessageException(State.ENCRYPTED_MESSAGE, input, stackItem) } @Throws(MalformedOpenPgpMessageException::class) fun fromValid(input: InputSymbol, stackItem: StackSymbol?): Transition { - if (input == InputSymbol.EndOfSequence) { + if (input == InputSymbol.END_OF_SEQUENCE) { // allow subsequent read() calls. - return Transition(State.Valid) + return Transition(State.VALID) } - throw MalformedOpenPgpMessageException(State.Valid, input, stackItem) + throw MalformedOpenPgpMessageException(State.VALID, input, stackItem) } } \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt index ae79909f..b1949917 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt @@ -33,7 +33,7 @@ class PDA constructor( /** * Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax]. */ - constructor(): this(OpenPgpMessageSyntax(), State.OpenPgpMessage, StackSymbol.terminus, StackSymbol.msg) + constructor(): this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG) /** * Process the next [InputSymbol]. @@ -78,7 +78,7 @@ class PDA constructor( * * @return true if valid, false otherwise */ - fun isValid(): Boolean = state == State.Valid && stack.isEmpty() + fun isValid(): Boolean = state == State.VALID && stack.isEmpty() /** * Throw a [MalformedOpenPgpMessageException] if the pda is not in a valid state right now. diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt index 009a86d4..960e5eba 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt @@ -8,13 +8,13 @@ enum class StackSymbol { /** * OpenPGP Message. */ - msg, + MSG, /** * OnePassSignature (in case of BC this represents a OnePassSignatureList). */ - ops, + OPS, /** * Special symbol representing the end of the message. */ - terminus + TERMINUS } \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt index 5c3e4906..8e1f682c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt @@ -8,9 +8,9 @@ package org.pgpainless.decryption_verification.syntax_check * Set of states of the automaton. */ enum class State { - OpenPgpMessage, - LiteralMessage, - CompressedMessage, - EncryptedMessage, - Valid + OPENPGP_MESSAGE, + LITERAL_MESSAGE, + COMPRESSED_MESSAGE, + ENCRYPTED_MESSAGE, + VALID } \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/syntax_check/PDATest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/syntax_check/PDATest.java index d0486a3e..10f8dceb 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/syntax_check/PDATest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/syntax_check/PDATest.java @@ -21,8 +21,8 @@ public class PDATest { @Test public void testSimpleLiteralMessageIsValid() throws MalformedOpenPgpMessageException { PDA check = new PDA(); - check.next(InputSymbol.LiteralData); - check.next(InputSymbol.EndOfSequence); + check.next(InputSymbol.LITERAL_DATA); + check.next(InputSymbol.END_OF_SEQUENCE); assertTrue(check.isValid()); } @@ -35,10 +35,10 @@ public class PDATest { @Test public void testSimpleOpsSignedMesssageIsValid() throws MalformedOpenPgpMessageException { PDA check = new PDA(); - check.next(InputSymbol.OnePassSignature); - check.next(InputSymbol.LiteralData); - check.next(InputSymbol.Signature); - check.next(InputSymbol.EndOfSequence); + check.next(InputSymbol.ONE_PASS_SIGNATURE); + check.next(InputSymbol.LITERAL_DATA); + check.next(InputSymbol.SIGNATURE); + check.next(InputSymbol.END_OF_SEQUENCE); assertTrue(check.isValid()); } @@ -52,9 +52,9 @@ public class PDATest { @Test public void testSimplePrependSignedMessageIsValid() throws MalformedOpenPgpMessageException { PDA check = new PDA(); - check.next(InputSymbol.Signature); - check.next(InputSymbol.LiteralData); - check.next(InputSymbol.EndOfSequence); + check.next(InputSymbol.SIGNATURE); + check.next(InputSymbol.LITERAL_DATA); + check.next(InputSymbol.END_OF_SEQUENCE); assertTrue(check.isValid()); } @@ -68,11 +68,11 @@ public class PDATest { @Test public void testOPSSignedCompressedMessageIsValid() throws MalformedOpenPgpMessageException { PDA check = new PDA(); - check.next(InputSymbol.OnePassSignature); - check.next(InputSymbol.CompressedData); + check.next(InputSymbol.ONE_PASS_SIGNATURE); + check.next(InputSymbol.COMPRESSED_DATA); // Here would be a nested PDA for the LiteralData packet - check.next(InputSymbol.Signature); - check.next(InputSymbol.EndOfSequence); + check.next(InputSymbol.SIGNATURE); + check.next(InputSymbol.END_OF_SEQUENCE); assertTrue(check.isValid()); } @@ -80,105 +80,105 @@ public class PDATest { @Test public void testOPSSignedEncryptedMessageIsValid() { PDA check = new PDA(); - check.next(InputSymbol.OnePassSignature); - check.next(InputSymbol.EncryptedData); - check.next(InputSymbol.Signature); - check.next(InputSymbol.EndOfSequence); + check.next(InputSymbol.ONE_PASS_SIGNATURE); + check.next(InputSymbol.ENCRYPTED_DATA); + check.next(InputSymbol.SIGNATURE); + check.next(InputSymbol.END_OF_SEQUENCE); assertTrue(check.isValid()); } @Test public void anyInputAfterEOSIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.LiteralData); - check.next(InputSymbol.EndOfSequence); + check.next(InputSymbol.LITERAL_DATA); + check.next(InputSymbol.END_OF_SEQUENCE); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.Signature)); + () -> check.next(InputSymbol.SIGNATURE)); } @Test public void testEncryptedMessageWithAppendedStandaloneSigIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.EncryptedData); + check.next(InputSymbol.ENCRYPTED_DATA); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.Signature)); + () -> check.next(InputSymbol.SIGNATURE)); } @Test public void testOPSSignedEncryptedMessageWithMissingSigIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.OnePassSignature); - check.next(InputSymbol.EncryptedData); + check.next(InputSymbol.ONE_PASS_SIGNATURE); + check.next(InputSymbol.ENCRYPTED_DATA); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.EndOfSequence)); + () -> check.next(InputSymbol.END_OF_SEQUENCE)); } @Test public void testTwoLiteralDataIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.LiteralData); + check.next(InputSymbol.LITERAL_DATA); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.LiteralData)); + () -> check.next(InputSymbol.LITERAL_DATA)); } @Test public void testTrailingSigIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.LiteralData); + check.next(InputSymbol.LITERAL_DATA); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.Signature)); + () -> check.next(InputSymbol.SIGNATURE)); } @Test public void testOPSAloneIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.OnePassSignature); + check.next(InputSymbol.ONE_PASS_SIGNATURE); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.EndOfSequence)); + () -> check.next(InputSymbol.END_OF_SEQUENCE)); } @Test public void testOPSLitWithMissingSigIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.OnePassSignature); - check.next(InputSymbol.LiteralData); + check.next(InputSymbol.ONE_PASS_SIGNATURE); + check.next(InputSymbol.LITERAL_DATA); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.EndOfSequence)); + () -> check.next(InputSymbol.END_OF_SEQUENCE)); } @Test public void testCompressedMessageWithStandalongAppendedSigIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.CompressedData); + check.next(InputSymbol.COMPRESSED_DATA); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.Signature)); + () -> check.next(InputSymbol.SIGNATURE)); } @Test public void testOPSCompressedDataWithMissingSigIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.OnePassSignature); - check.next(InputSymbol.CompressedData); + check.next(InputSymbol.ONE_PASS_SIGNATURE); + check.next(InputSymbol.COMPRESSED_DATA); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.EndOfSequence)); + () -> check.next(InputSymbol.END_OF_SEQUENCE)); } @Test public void testCompressedMessageFollowedByTrailingLiteralDataIsNotValid() { PDA check = new PDA(); - check.next(InputSymbol.CompressedData); + check.next(InputSymbol.COMPRESSED_DATA); assertThrows(MalformedOpenPgpMessageException.class, - () -> check.next(InputSymbol.LiteralData)); + () -> check.next(InputSymbol.LITERAL_DATA)); } @Test public void testOPSWithPrependedSigIsValid() { PDA check = new PDA(); - check.next(InputSymbol.Signature); - check.next(InputSymbol.OnePassSignature); - check.next(InputSymbol.LiteralData); - check.next(InputSymbol.Signature); - check.next(InputSymbol.EndOfSequence); + check.next(InputSymbol.SIGNATURE); + check.next(InputSymbol.ONE_PASS_SIGNATURE); + check.next(InputSymbol.LITERAL_DATA); + check.next(InputSymbol.SIGNATURE); + check.next(InputSymbol.END_OF_SEQUENCE); assertTrue(check.isValid()); } @@ -186,11 +186,11 @@ public class PDATest { @Test public void testPrependedSigInsideOPSSignedMessageIsValid() { PDA check = new PDA(); - check.next(InputSymbol.OnePassSignature); - check.next(InputSymbol.Signature); - check.next(InputSymbol.LiteralData); - check.next(InputSymbol.Signature); - check.next(InputSymbol.EndOfSequence); + check.next(InputSymbol.ONE_PASS_SIGNATURE); + check.next(InputSymbol.SIGNATURE); + check.next(InputSymbol.LITERAL_DATA); + check.next(InputSymbol.SIGNATURE); + check.next(InputSymbol.END_OF_SEQUENCE); assertTrue(check.isValid()); } From fca5c88d0956d17621e90a81661a03314df61e87 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Aug 2023 13:37:23 +0200 Subject: [PATCH 038/155] Kotlin conversion: OpenPgpMessageInputStream --- .../OpenPgpMessageInputStream.java | 1139 ----------------- .../OpenPgpMessageInputStream.kt | 903 +++++++++++++ 2 files changed, 903 insertions(+), 1139 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java deleted file mode 100644 index 66ad1edd..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java +++ /dev/null @@ -1,1139 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.Stack; -import javax.annotation.Nonnull; - -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.bcpg.BCPGInputStream; -import org.bouncycastle.bcpg.S2K; -import org.bouncycastle.bcpg.UnsupportedPacketVersionException; -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPEncryptedData; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPOnePassSignature; -import org.bouncycastle.openpgp.PGPPBEEncryptedData; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSessionKeyEncryptedData; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; -import org.bouncycastle.util.io.TeeInputStream; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.OpenPgpPacket; -import org.pgpainless.algorithm.StreamEncoding; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil; -import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy; -import org.pgpainless.decryption_verification.syntax_check.InputSymbol; -import org.pgpainless.decryption_verification.syntax_check.PDA; -import org.pgpainless.decryption_verification.syntax_check.StackSymbol; -import org.pgpainless.exception.MalformedOpenPgpMessageException; -import org.pgpainless.exception.MessageNotIntegrityProtectedException; -import org.pgpainless.exception.MissingDecryptionMethodException; -import org.pgpainless.exception.MissingPassphraseException; -import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.exception.UnacceptableAlgorithmException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnlockSecretKey; -import org.pgpainless.key.util.KeyIdUtil; -import org.pgpainless.key.util.KeyRingUtils; -import org.pgpainless.policy.Policy; -import org.pgpainless.signature.SignatureUtils; -import org.pgpainless.signature.consumer.CertificateValidator; -import org.pgpainless.signature.consumer.OnePassSignatureCheck; -import org.pgpainless.signature.consumer.SignatureCheck; -import org.pgpainless.signature.consumer.SignatureValidator; -import org.pgpainless.util.ArmoredInputStreamFactory; -import org.pgpainless.util.Passphrase; -import org.pgpainless.util.SessionKey; -import org.pgpainless.util.Tuple; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OpenPgpMessageInputStream extends DecryptionStream { - - private static final Logger LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream.class); - - // Options to consume the data - protected final ConsumerOptions options; - - private final Policy policy; - // Pushdown Automaton to verify validity of OpenPGP packet sequence in an OpenPGP message - protected final PDA syntaxVerifier = new PDA(); - // InputStream of OpenPGP packets - protected TeeBCPGInputStream packetInputStream; - // InputStream of a data packet containing nested data - protected InputStream nestedInputStream; - - private boolean closed = false; - - private final Signatures signatures; - private final MessageMetadata.Layer metadata; - - /** - * Create an {@link OpenPgpMessageInputStream} suitable for decryption and verification of - * OpenPGP messages and signatures. - * This constructor will use the global PGPainless {@link Policy}. - * - * @param inputStream underlying input stream - * @param options options for consuming the stream - * @return input stream that consumes OpenPGP messages - * - * @throws IOException in case of an IO error - * @throws PGPException in case of an OpenPGP error - */ - public static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options) - throws IOException, PGPException { - return create(inputStream, options, PGPainless.getPolicy()); - } - - /** - * Create an {@link OpenPgpMessageInputStream} suitable for decryption and verification of - * OpenPGP messages and signatures. - * This factory method takes a custom {@link Policy} instead of using the global policy object. - * - * @param inputStream underlying input stream containing the OpenPGP message - * @param options options for consuming the message - * @param policy policy for acceptable algorithms etc. - * @return input stream that consumes OpenPGP messages - * - * @throws PGPException in case of an OpenPGP error - * @throws IOException in case of an IO error - */ - public static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options, - @Nonnull Policy policy) - throws PGPException, IOException { - return create(inputStream, options, new MessageMetadata.Message(), policy); - } - - protected static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options, - @Nonnull MessageMetadata.Layer metadata, - @Nonnull Policy policy) - throws IOException, PGPException { - OpenPgpInputStream openPgpIn = new OpenPgpInputStream(inputStream); - openPgpIn.reset(); - - if (openPgpIn.isNonOpenPgp() || options.isForceNonOpenPgpData()) { - return new OpenPgpMessageInputStream(Type.non_openpgp, - openPgpIn, options, metadata, policy); - } - - if (openPgpIn.isBinaryOpenPgp()) { - // Simply consume OpenPGP message - return new OpenPgpMessageInputStream(Type.standard, - openPgpIn, options, metadata, policy); - } - - if (openPgpIn.isAsciiArmored()) { - ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(openPgpIn); - if (armorIn.isClearText()) { - ((MessageMetadata.Message) metadata).cleartextSigned = true; - return new OpenPgpMessageInputStream(Type.cleartext_signed, - armorIn, options, metadata, policy); - } else { - // Simply consume dearmored OpenPGP message - return new OpenPgpMessageInputStream(Type.standard, - armorIn, options, metadata, policy); - } - } else { - throw new AssertionError("Cannot deduce type of data."); - } - } - - protected OpenPgpMessageInputStream(@Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options, - @Nonnull MessageMetadata.Layer metadata, - @Nonnull Policy policy) - throws PGPException, IOException { - super(); - - this.policy = policy; - this.options = options; - this.metadata = metadata; - this.signatures = new Signatures(options); - - // Add detached signatures only on the outermost OpenPgpMessageInputStream - if (metadata instanceof MessageMetadata.Message) { - this.signatures.addDetachedSignatures(options.getDetachedSignatures()); - } - - // tee out packet bytes for signature verification - packetInputStream = new TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures); - - // *omnomnom* - consumePackets(); - } - - enum Type { - standard, - cleartext_signed, - non_openpgp - } - - protected OpenPgpMessageInputStream(@Nonnull Type type, - @Nonnull InputStream inputStream, - @Nonnull ConsumerOptions options, - @Nonnull MessageMetadata.Layer metadata, - @Nonnull Policy policy) throws PGPException, IOException { - super(); - this.policy = policy; - this.options = options; - this.metadata = metadata; - this.signatures = new Signatures(options); - - if (metadata instanceof MessageMetadata.Message) { - this.signatures.addDetachedSignatures(options.getDetachedSignatures()); - } - - switch (type) { - - // Binary OpenPGP Message - case standard: - // tee out packet bytes for signature verification - packetInputStream = new TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), this.signatures); - - // *omnomnom* - consumePackets(); - break; - - // Cleartext Signature Framework (probably signed message) - case cleartext_signed: - MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy(); - PGPSignatureList detachedSignatures = ClearsignedMessageUtil - .detachSignaturesFromInbandClearsignedMessage( - inputStream, multiPassStrategy.getMessageOutputStream()); - - for (PGPSignature signature : detachedSignatures) { - signatures.addDetachedSignature(signature); - } - - options.forceNonOpenPgpData(); - nestedInputStream = new TeeInputStream(multiPassStrategy.getMessageInputStream(), this.signatures); - break; - - // Non-OpenPGP Data (e.g. detached signature verification) - case non_openpgp: - packetInputStream = null; - nestedInputStream = new TeeInputStream(inputStream, this.signatures); - break; - } - } - - /** - * Consume OpenPGP packets from the current {@link BCPGInputStream}. - * Once an OpenPGP packet with nested data (Literal Data, Compressed Data, Encrypted Data) is reached, - * set
nestedInputStream
to the nested stream and breaks the loop. - * The nested stream is either a simple {@link InputStream} (in case of Literal Data), or another - * {@link OpenPgpMessageInputStream} in case of Compressed and Encrypted Data. - * Once the nested data is processed, this method is called again to consume the remainder - * of packets following the nested data packet. - * - * @throws IOException in case of an IO error - * @throws PGPException in case of an OpenPGP error - * @throws MissingDecryptionMethodException if there is an encrypted data packet which cannot be decrypted - * due to missing decryption methods (no key, no password, no sessionkey) - * @throws MalformedOpenPgpMessageException if the message is made of an invalid packet sequence which - * does not follow the packet syntax of RFC4880. - */ - private void consumePackets() - throws IOException, PGPException { - OpenPgpPacket nextPacket; - if (packetInputStream == null) { - return; - } - - loop: // we break this when we enter nested packets and later resume - while ((nextPacket = packetInputStream.nextPacketTag()) != null) { - signatures.nextPacket(nextPacket); - switch (nextPacket) { - - // Literal Data - the literal data content is the new input stream - case LIT: - processLiteralData(); - break loop; - - // Compressed Data - the content contains another OpenPGP message - case COMP: - processCompressedData(); - break loop; - - // One Pass Signature - case OPS: - processOnePassSignature(); - break; - - // Signature - either prepended to the message, or corresponding to a One Pass Signature - case SIG: - processSignature(); - break; - - // Encrypted Data (ESKs and SED/SEIPD are parsed the same by BC) - case PKESK: - case SKESK: - case SED: - case SEIPD: - if (processEncryptedData()) { - // Successfully decrypted, enter nested content - break loop; - } - - throw new MissingDecryptionMethodException("No working decryption method found."); - - // Marker Packets need to be skipped and ignored - case MARKER: - LOGGER.debug("Skipping Marker Packet"); - packetInputStream.readMarker(); - break; - - // Key Packets are illegal in this context - case SK: - case PK: - case SSK: - case PSK: - case TRUST: - case UID: - case UATTR: - throw new MalformedOpenPgpMessageException("Illegal Packet in Stream: " + nextPacket); - - // MDC packet is usually processed by PGPEncryptedDataList, so it is very likely we encounter this - // packet out of order - case MDC: - throw new MalformedOpenPgpMessageException("Unexpected Packet in Stream: " + nextPacket); - - // Experimental Packets are not supported - case EXP_1: - case EXP_2: - case EXP_3: - case EXP_4: - throw new MalformedOpenPgpMessageException("Unsupported Packet in Stream: " + nextPacket); - } - } - } - - private void processLiteralData() throws IOException { - LOGGER.debug("Literal Data Packet at depth " + metadata.depth + " encountered"); - syntaxVerifier.next(InputSymbol.LITERAL_DATA); - PGPLiteralData literalData = packetInputStream.readLiteralData(); - // Extract Metadata - this.metadata.setChild(new MessageMetadata.LiteralData( - literalData.getFileName(), - literalData.getModificationTime(), - StreamEncoding.requireFromCode(literalData.getFormat()))); - - nestedInputStream = literalData.getDataStream(); - } - - private void processCompressedData() throws IOException, PGPException { - syntaxVerifier.next(InputSymbol.COMPRESSED_DATA); - signatures.enterNesting(); - PGPCompressedData compressedData = packetInputStream.readCompressedData(); - // Extract Metadata - MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData( - CompressionAlgorithm.requireFromId(compressedData.getAlgorithm()), - metadata.depth + 1); - - LOGGER.debug("Compressed Data Packet (" + compressionLayer.algorithm + ") at depth " + metadata.depth + " encountered"); - InputStream decompressed = compressedData.getDataStream(); - nestedInputStream = new OpenPgpMessageInputStream(decompressed, options, compressionLayer, policy); - } - - private void processOnePassSignature() throws PGPException, IOException { - syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE); - PGPOnePassSignature onePassSignature = packetInputStream.readOnePassSignature(); - LOGGER.debug("One-Pass-Signature Packet by key " + KeyIdUtil.formatKeyId(onePassSignature.getKeyID()) + - " at depth " + metadata.depth + " encountered"); - signatures.addOnePassSignature(onePassSignature); - } - - private void processSignature() throws PGPException, IOException { - // true if Signature corresponds to OnePassSignature - boolean isSigForOPS = syntaxVerifier.peekStack() == StackSymbol.OPS; - syntaxVerifier.next(InputSymbol.SIGNATURE); - PGPSignature signature; - try { - signature = packetInputStream.readSignature(); - } catch (UnsupportedPacketVersionException e) { - LOGGER.debug("Unsupported Signature at depth " + metadata.depth + " encountered.", e); - return; - } - - long keyId = SignatureUtils.determineIssuerKeyId(signature); - if (isSigForOPS) { - LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key " + - KeyIdUtil.formatKeyId(keyId) + - " at depth " + metadata.depth + " encountered"); - signatures.leaveNesting(); // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with - signatures.addCorrespondingOnePassSignature(signature, metadata, policy); - } else { - LOGGER.debug("Prepended Signature Packet by key " + - KeyIdUtil.formatKeyId(keyId) + - " at depth " + metadata.depth + " encountered"); - signatures.addPrependedSignature(signature); - } - } - - private boolean processEncryptedData() throws IOException, PGPException { - LOGGER.debug("Symmetrically Encrypted Data Packet at depth " + metadata.depth + " encountered"); - syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA); - PGPEncryptedDataList encDataList = packetInputStream.readEncryptedDataList(); - - if (!encDataList.isIntegrityProtected()) { - LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected."); - if (!options.isIgnoreMDCErrors()) { - throw new MessageNotIntegrityProtectedException(); - } - } - - SortedESKs esks = new SortedESKs(encDataList); - LOGGER.debug("Symmetrically Encrypted Integrity-Protected Data has " + - esks.skesks.size() + " SKESK(s) and " + - (esks.pkesks.size() + esks.anonPkesks.size()) + " PKESK(s) from which " + - esks.anonPkesks.size() + " PKESK(s) have an anonymous recipient"); - - // Try custom decryptor factories - for (SubkeyIdentifier subkeyIdentifier : options.getCustomDecryptorFactories().keySet()) { - LOGGER.debug("Attempt decryption with custom decryptor factory with key " + subkeyIdentifier); - PublicKeyDataDecryptorFactory decryptorFactory = options.getCustomDecryptorFactories().get(subkeyIdentifier); - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - // find matching PKESK - if (pkesk.getKeyID() != subkeyIdentifier.getSubkeyId()) { - continue; - } - - // attempt decryption - if (decryptPKESKAndStream(esks, subkeyIdentifier, decryptorFactory, pkesk)) { - return true; - } - } - } - - // Try provided session key - if (options.getSessionKey() != null) { - LOGGER.debug("Attempt decryption with provided session key"); - SessionKey sessionKey = options.getSessionKey(); - throwIfUnacceptable(sessionKey.getAlgorithm()); - - SessionKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance() - .getSessionKeyDataDecryptorFactory(sessionKey); - MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( - sessionKey.getAlgorithm(), metadata.depth + 1); - - PGPSessionKeyEncryptedData sessionKeyEncryptedData = encDataList.extractSessionKeyEncryptedData(); - try { - InputStream decrypted = sessionKeyEncryptedData.getDataStream(decryptorFactory); - encryptedData.sessionKey = sessionKey; - IntegrityProtectedInputStream integrityProtected = new IntegrityProtectedInputStream(decrypted, sessionKeyEncryptedData, options); - nestedInputStream = new OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy); - LOGGER.debug("Successfully decrypted data with provided session key"); - return true; - } catch (PGPException e) { - // Session key mismatch? - LOGGER.debug("Decryption using provided session key failed. Mismatched session key and message?", e); - } - } - - // Try passwords - for (Passphrase passphrase : options.getDecryptionPassphrases()) { - for (PGPPBEEncryptedData skesk : esks.skesks) { - LOGGER.debug("Attempt decryption with provided passphrase"); - SymmetricKeyAlgorithm encapsulationAlgorithm = SymmetricKeyAlgorithm.requireFromId(skesk.getAlgorithm()); - try { - throwIfUnacceptable(encapsulationAlgorithm); - } catch (UnacceptableAlgorithmException e) { - LOGGER.debug("Skipping SKESK with unacceptable encapsulation algorithm", e); - continue; - } - - PBEDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance() - .getPBEDataDecryptorFactory(passphrase); - if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) { - return true; - } - } - } - - List> postponedDueToMissingPassphrase = new ArrayList<>(); - - // Try (known) secret keys - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - long keyId = pkesk.getKeyID(); - LOGGER.debug("Encountered PKESK for recipient " + KeyIdUtil.formatKeyId(keyId)); - PGPSecretKeyRing decryptionKeys = getDecryptionKey(keyId); - if (decryptionKeys == null) { - LOGGER.debug("Skipping PKESK because no matching key " + KeyIdUtil.formatKeyId(keyId) + " was provided"); - continue; - } - PGPSecretKey secretKey = decryptionKeys.getSecretKey(keyId); - SubkeyIdentifier decryptionKeyId = new SubkeyIdentifier(decryptionKeys, secretKey.getKeyID()); - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { - continue; - } - LOGGER.debug("Attempt decryption using secret key " + decryptionKeyId); - - SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKeys); - // Postpone keys with missing passphrase - if (!protector.hasPassphraseFor(keyId)) { - LOGGER.debug("Missing passphrase for key " + decryptionKeyId + ". Postponing decryption until all other keys were tried"); - postponedDueToMissingPassphrase.add(new Tuple<>(secretKey, pkesk)); - continue; - } - - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector); - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { - return true; - } - } - - // try anonymous secret keys - for (PGPPublicKeyEncryptedData pkesk : esks.anonPkesks) { - for (Tuple decryptionKeyCandidate : findPotentialDecryptionKeys(pkesk)) { - PGPSecretKeyRing decryptionKeys = decryptionKeyCandidate.getA(); - PGPSecretKey secretKey = decryptionKeyCandidate.getB(); - SubkeyIdentifier decryptionKeyId = new SubkeyIdentifier(decryptionKeys, secretKey.getKeyID()); - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { - continue; - } - LOGGER.debug("Attempt decryption of anonymous PKESK with key " + decryptionKeyId); - SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKeyCandidate.getA()); - if (!protector.hasPassphraseFor(secretKey.getKeyID())) { - LOGGER.debug("Missing passphrase for key " + decryptionKeyId + ". Postponing decryption until all other keys were tried."); - postponedDueToMissingPassphrase.add(new Tuple<>(secretKey, pkesk)); - continue; - } - - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector); - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { - return true; - } - } - } - - if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) { - // Non-interactive mode: Throw an exception with all locked decryption keys - Set keyIds = new HashSet<>(); - for (Tuple k : postponedDueToMissingPassphrase) { - PGPSecretKey key = k.getA(); - PGPSecretKeyRing keys = getDecryptionKey(key.getKeyID()); - assert (keys != null); - keyIds.add(new SubkeyIdentifier(keys, key.getKeyID())); - } - if (!keyIds.isEmpty()) { - throw new MissingPassphraseException(keyIds); - } - } else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) { - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - // Interactive mode: Fire protector callbacks to get passphrases interactively - for (Tuple missingPassphrases : postponedDueToMissingPassphrase) { - PGPSecretKey secretKey = missingPassphrases.getA(); - long keyId = secretKey.getKeyID(); - PGPSecretKeyRing decryptionKey = getDecryptionKey(keyId); - assert (decryptionKey != null); - SubkeyIdentifier decryptionKeyId = new SubkeyIdentifier(decryptionKey, keyId); - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { - continue; - } - - LOGGER.debug("Attempt decryption with key " + decryptionKeyId + " while interactively requesting its passphrase"); - SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKey); - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector); - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { - return true; - } - } - } - } else { - throw new IllegalStateException("Invalid PostponedKeysStrategy set in consumer options."); - } - - // we did not yet succeed in decrypting any session key :/ - - LOGGER.debug("Failed to decrypt encrypted data packet"); - return false; - } - - private boolean decryptWithPrivateKey(SortedESKs esks, - PGPPrivateKey privateKey, - SubkeyIdentifier decryptionKeyId, - PGPPublicKeyEncryptedData pkesk) - throws PGPException, IOException { - PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance() - .getPublicKeyDataDecryptorFactory(privateKey); - return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk); - } - - private static boolean hasUnsupportedS2KSpecifier(PGPSecretKey secretKey, SubkeyIdentifier decryptionKeyId) { - S2K s2K = secretKey.getS2K(); - if (s2K != null) { - int s2kType = s2K.getType(); - if (s2kType >= 100 && s2kType <= 110) { - LOGGER.debug("Skipping PKESK because key " + decryptionKeyId + " has unsupported private S2K specifier " + s2kType); - return true; - } - } - return false; - } - - private boolean decryptSKESKAndStream(SortedESKs esks, - PGPPBEEncryptedData symEsk, - PBEDataDecryptorFactory decryptorFactory) - throws IOException, UnacceptableAlgorithmException { - try { - InputStream decrypted = symEsk.getDataStream(decryptorFactory); - SessionKey sessionKey = new SessionKey(symEsk.getSessionKey(decryptorFactory)); - throwIfUnacceptable(sessionKey.getAlgorithm()); - MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( - sessionKey.getAlgorithm(), metadata.depth + 1); - encryptedData.sessionKey = sessionKey; - encryptedData.recipients = new ArrayList<>(); - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - encryptedData.recipients.add(pkesk.getKeyID()); - } - LOGGER.debug("Successfully decrypted data with passphrase"); - IntegrityProtectedInputStream integrityProtected = new IntegrityProtectedInputStream(decrypted, symEsk, options); - nestedInputStream = new OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy); - return true; - } catch (UnacceptableAlgorithmException e) { - throw e; - } catch (PGPException e) { - LOGGER.debug("Decryption of encrypted data packet using password failed. Password mismatch?", e); - } - return false; - } - - private boolean decryptPKESKAndStream(SortedESKs esks, - SubkeyIdentifier decryptionKeyId, - PublicKeyDataDecryptorFactory decryptorFactory, - PGPPublicKeyEncryptedData asymEsk) - throws IOException, UnacceptableAlgorithmException { - try { - InputStream decrypted = asymEsk.getDataStream(decryptorFactory); - SessionKey sessionKey = new SessionKey(asymEsk.getSessionKey(decryptorFactory)); - throwIfUnacceptable(sessionKey.getAlgorithm()); - - MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( - SymmetricKeyAlgorithm.requireFromId(asymEsk.getSymmetricAlgorithm(decryptorFactory)), - metadata.depth + 1); - encryptedData.decryptionKey = decryptionKeyId; - encryptedData.sessionKey = sessionKey; - encryptedData.recipients = new ArrayList<>(); - for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { - encryptedData.recipients.add(pkesk.getKeyID()); - } - for (PGPPublicKeyEncryptedData pkesk : esks.anonPkesks) { - encryptedData.recipients.add(pkesk.getKeyID()); - } - - LOGGER.debug("Successfully decrypted data with key " + decryptionKeyId); - IntegrityProtectedInputStream integrityProtected = new IntegrityProtectedInputStream(decrypted, asymEsk, options); - nestedInputStream = new OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy); - return true; - } catch (UnacceptableAlgorithmException e) { - throw e; - } catch (PGPException e) { - LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e); - } - return false; - } - - private void throwIfUnacceptable(SymmetricKeyAlgorithm algorithm) - throws UnacceptableAlgorithmException { - if (!policy.getSymmetricKeyDecryptionAlgorithmPolicy().isAcceptable(algorithm)) { - throw new UnacceptableAlgorithmException("Symmetric-Key algorithm " + algorithm + " is not acceptable for message decryption."); - } - } - - private List> findPotentialDecryptionKeys(PGPPublicKeyEncryptedData pkesk) { - int algorithm = pkesk.getAlgorithm(); - List> decryptionKeyCandidates = new ArrayList<>(); - - for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) { - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); - for (PGPPublicKey publicKey : info.getDecryptionSubkeys()) { - if (publicKey.getAlgorithm() == algorithm && info.isSecretKeyAvailable(publicKey.getKeyID())) { - PGPSecretKey candidate = secretKeys.getSecretKey(publicKey.getKeyID()); - decryptionKeyCandidates.add(new Tuple<>(secretKeys, candidate)); - } - } - } - return decryptionKeyCandidates; - } - - private PGPSecretKeyRing getDecryptionKey(long keyID) { - for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) { - PGPSecretKey decryptionKey = secretKeys.getSecretKey(keyID); - if (decryptionKey == null) { - continue; - } - - KeyRingInfo info = new KeyRingInfo(secretKeys, policy, new Date()); - List encryptionKeys = info.getDecryptionSubkeys(); - for (PGPPublicKey key : encryptionKeys) { - if (key.getKeyID() == keyID) { - return secretKeys; - } - } - - LOGGER.debug("Subkey " + Long.toHexString(keyID) + " cannot be used for decryption."); - } - return null; - } - - @Override - public int read() throws IOException { - if (nestedInputStream == null) { - if (packetInputStream != null) { - syntaxVerifier.assertValid(); - } - return -1; - } - - int r; - try { - r = nestedInputStream.read(); - } catch (IOException e) { - r = -1; - } - boolean eos = r == -1; - if (!eos) { - byte b = (byte) r; - signatures.updateLiteral(b); - } else { - nestedInputStream.close(); - collectMetadata(); - nestedInputStream = null; - - if (packetInputStream != null) { - try { - consumePackets(); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } - signatures.finish(metadata, policy); - } - return r; - } - - @Override - public int read(@Nonnull byte[] b, int off, int len) - throws IOException { - if (nestedInputStream == null) { - if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); - syntaxVerifier.assertValid(); - } - return -1; - } - - int r = nestedInputStream.read(b, off, len); - if (r != -1) { - signatures.updateLiteral(b, off, r); - } else { - nestedInputStream.close(); - collectMetadata(); - nestedInputStream = null; - - if (packetInputStream != null) { - try { - consumePackets(); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } - signatures.finish(metadata, policy); - } - return r; - } - - @Override - public void close() throws IOException { - super.close(); - if (closed) { - if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); - syntaxVerifier.assertValid(); - } - return; - } - - if (nestedInputStream != null) { - nestedInputStream.close(); - collectMetadata(); - nestedInputStream = null; - } - - try { - consumePackets(); - } catch (PGPException e) { - throw new RuntimeException(e); - } - - if (packetInputStream != null) { - syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE); - syntaxVerifier.assertValid(); - packetInputStream.close(); - } - closed = true; - } - - private void collectMetadata() { - if (nestedInputStream instanceof OpenPgpMessageInputStream) { - OpenPgpMessageInputStream child = (OpenPgpMessageInputStream) nestedInputStream; - this.metadata.setChild((MessageMetadata.Nested) child.metadata); - } - } - - public MessageMetadata getMetadata() { - if (!closed) { - throw new IllegalStateException("Stream must be closed before access to metadata can be granted."); - } - - return new MessageMetadata((MessageMetadata.Message) metadata); - } - - private static class SortedESKs { - - private final List skesks = new ArrayList<>(); - private final List pkesks = new ArrayList<>(); - private final List anonPkesks = new ArrayList<>(); - - SortedESKs(PGPEncryptedDataList esks) { - for (PGPEncryptedData esk : esks) { - if (esk instanceof PGPPBEEncryptedData) { - skesks.add((PGPPBEEncryptedData) esk); - } - else if (esk instanceof PGPPublicKeyEncryptedData) { - PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk; - if (pkesk.getKeyID() != 0) { - pkesks.add(pkesk); - } else { - anonPkesks.add(pkesk); - } - } else { - throw new IllegalArgumentException("Unknown ESK class type."); - } - } - } - - public List all() { - List esks = new ArrayList<>(); - esks.addAll(skesks); - esks.addAll(pkesks); - esks.addAll(anonPkesks); - return esks; - } - } - - // In 'OPS LIT("Foo") SIG', OPS is only updated with "Foo" - // In 'OPS[1] OPS LIT("Foo") SIG SIG', OPS[1] (nested) is updated with OPS LIT("Foo") SIG. - // Therefore, we need to handle the innermost signature layer differently when updating with Literal data. - // Furthermore, For 'OPS COMP(LIT("Foo")) SIG', the signature is updated with "Foo". CHAOS!!! - private static final class Signatures extends OutputStream { - final ConsumerOptions options; - final List detachedSignatures; - final List prependedSignatures; - final List onePassSignatures; - final Stack> opsUpdateStack; - List literalOPS = new ArrayList<>(); - final List correspondingSignatures; - final List prependedSignaturesWithMissingCert = new ArrayList<>(); - final List inbandSignaturesWithMissingCert = new ArrayList<>(); - final List detachedSignaturesWithMissingCert = new ArrayList<>(); - boolean isLiteral = true; - - private Signatures(ConsumerOptions options) { - this.options = options; - this.detachedSignatures = new ArrayList<>(); - this.prependedSignatures = new ArrayList<>(); - this.onePassSignatures = new ArrayList<>(); - this.opsUpdateStack = new Stack<>(); - this.correspondingSignatures = new ArrayList<>(); - } - - void addDetachedSignatures(Collection signatures) { - for (PGPSignature signature : signatures) { - addDetachedSignature(signature); - } - } - - void addDetachedSignature(PGPSignature signature) { - SignatureCheck check = initializeSignature(signature); - long keyId = SignatureUtils.determineIssuerKeyId(signature); - if (check != null) { - detachedSignatures.add(check); - } else { - LOGGER.debug("No suitable certificate for verification of signature by key " + KeyIdUtil.formatKeyId(keyId) + " found."); - this.detachedSignaturesWithMissingCert.add(new SignatureVerification.Failure( - new SignatureVerification(signature, null), - new SignatureValidationException("Missing verification key") - )); - } - } - - void addPrependedSignature(PGPSignature signature) { - SignatureCheck check = initializeSignature(signature); - long keyId = SignatureUtils.determineIssuerKeyId(signature); - if (check != null) { - this.prependedSignatures.add(check); - } else { - LOGGER.debug("No suitable certificate for verification of signature by key " + KeyIdUtil.formatKeyId(keyId) + " found."); - this.prependedSignaturesWithMissingCert.add(new SignatureVerification.Failure( - new SignatureVerification(signature, null), - new SignatureValidationException("Missing verification key") - )); - } - } - - SignatureCheck initializeSignature(PGPSignature signature) { - long keyId = SignatureUtils.determineIssuerKeyId(signature); - PGPPublicKeyRing certificate = findCertificate(keyId); - if (certificate == null) { - return null; - } - - SubkeyIdentifier verifierKey = new SubkeyIdentifier(certificate, keyId); - initialize(signature, certificate, keyId); - return new SignatureCheck(signature, certificate, verifierKey); - } - - void addOnePassSignature(PGPOnePassSignature signature) { - PGPPublicKeyRing certificate = findCertificate(signature.getKeyID()); - - if (certificate != null) { - OnePassSignatureCheck ops = new OnePassSignatureCheck(signature, certificate); - initialize(signature, certificate); - onePassSignatures.add(ops); - - literalOPS.add(ops); - } - if (signature.isContaining()) { - enterNesting(); - } - } - - void addCorrespondingOnePassSignature(PGPSignature signature, MessageMetadata.Layer layer, Policy policy) { - boolean found = false; - long keyId = SignatureUtils.determineIssuerKeyId(signature); - for (int i = onePassSignatures.size() - 1; i >= 0; i--) { - OnePassSignatureCheck onePassSignature = onePassSignatures.get(i); - if (onePassSignature.getOnePassSignature().getKeyID() != keyId) { - continue; - } - found = true; - - if (onePassSignature.getSignature() != null) { - continue; - } - - onePassSignature.setSignature(signature); - SignatureVerification verification = new SignatureVerification(signature, - new SubkeyIdentifier(onePassSignature.getVerificationKeys(), onePassSignature.getOnePassSignature().getKeyID())); - - try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(signature); - CertificateValidator.validateCertificateAndVerifyOnePassSignature(onePassSignature, policy); - LOGGER.debug("Acceptable signature by key " + verification.getSigningKey()); - layer.addVerifiedOnePassSignature(verification); - } catch (SignatureValidationException e) { - LOGGER.debug("Rejected signature by key " + verification.getSigningKey(), e); - layer.addRejectedOnePassSignature(new SignatureVerification.Failure(verification, e)); - } - break; - } - - if (!found) { - LOGGER.debug("No suitable certificate for verification of signature by key " + KeyIdUtil.formatKeyId(keyId) + " found."); - inbandSignaturesWithMissingCert.add(new SignatureVerification.Failure( - new SignatureVerification(signature, null), - new SignatureValidationException("Missing verification key"))); - } - } - - void enterNesting() { - opsUpdateStack.push(literalOPS); - literalOPS = new ArrayList<>(); - } - - void leaveNesting() { - if (opsUpdateStack.isEmpty()) { - return; - } - opsUpdateStack.pop(); - } - - private static void initialize(@Nonnull PGPSignature signature, @Nonnull PGPPublicKeyRing certificate, long keyId) { - PGPContentVerifierBuilderProvider verifierProvider = ImplementationFactory.getInstance() - .getPGPContentVerifierBuilderProvider(); - try { - signature.init(verifierProvider, certificate.getPublicKey(keyId)); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } - - private static void initialize(@Nonnull PGPOnePassSignature ops, @Nonnull PGPPublicKeyRing certificate) { - PGPContentVerifierBuilderProvider verifierProvider = ImplementationFactory.getInstance() - .getPGPContentVerifierBuilderProvider(); - try { - ops.init(verifierProvider, certificate.getPublicKey(ops.getKeyID())); - } catch (PGPException e) { - throw new RuntimeException(e); - } - } - - private PGPPublicKeyRing findCertificate(long keyId) { - PGPPublicKeyRing cert = options.getCertificateSource().getCertificate(keyId); - if (cert != null) { - return cert; - } - - if (options.getMissingCertificateCallback() != null) { - return options.getMissingCertificateCallback().onMissingPublicKeyEncountered(keyId); - } - return null; // TODO: Missing cert for sig - } - - public void updateLiteral(byte b) { - for (OnePassSignatureCheck ops : literalOPS) { - ops.getOnePassSignature().update(b); - } - - for (SignatureCheck detached : detachedSignatures) { - detached.getSignature().update(b); - } - - for (SignatureCheck prepended : prependedSignatures) { - prepended.getSignature().update(b); - } - } - - public void updateLiteral(byte[] b, int off, int len) { - for (OnePassSignatureCheck ops : literalOPS) { - ops.getOnePassSignature().update(b, off, len); - } - - for (SignatureCheck detached : detachedSignatures) { - detached.getSignature().update(b, off, len); - } - - for (SignatureCheck prepended : prependedSignatures) { - prepended.getSignature().update(b, off, len); - } - } - - public void updatePacket(byte b) { - for (int i = opsUpdateStack.size() - 1; i >= 0; i--) { - List nestedOPSs = opsUpdateStack.get(i); - for (OnePassSignatureCheck ops : nestedOPSs) { - ops.getOnePassSignature().update(b); - } - } - } - - public void updatePacket(byte[] buf, int off, int len) { - for (int i = opsUpdateStack.size() - 1; i >= 0; i--) { - List nestedOPSs = opsUpdateStack.get(i); - for (OnePassSignatureCheck ops : nestedOPSs) { - ops.getOnePassSignature().update(buf, off, len); - } - } - } - - public void finish(MessageMetadata.Layer layer, Policy policy) { - for (SignatureCheck detached : detachedSignatures) { - SignatureVerification verification = new SignatureVerification(detached.getSignature(), detached.getSigningKeyIdentifier()); - try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(detached.getSignature()); - CertificateValidator.validateCertificateAndVerifyInitializedSignature( - detached.getSignature(), KeyRingUtils.publicKeys(detached.getSigningKeyRing()), policy); - LOGGER.debug("Acceptable signature by key " + verification.getSigningKey()); - layer.addVerifiedDetachedSignature(verification); - } catch (SignatureValidationException e) { - LOGGER.debug("Rejected signature by key " + verification.getSigningKey(), e); - layer.addRejectedDetachedSignature(new SignatureVerification.Failure(verification, e)); - } - } - - for (SignatureCheck prepended : prependedSignatures) { - SignatureVerification verification = new SignatureVerification(prepended.getSignature(), prepended.getSigningKeyIdentifier()); - try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(prepended.getSignature()); - CertificateValidator.validateCertificateAndVerifyInitializedSignature( - prepended.getSignature(), KeyRingUtils.publicKeys(prepended.getSigningKeyRing()), policy); - LOGGER.debug("Acceptable signature by key " + verification.getSigningKey()); - layer.addVerifiedPrependedSignature(verification); - } catch (SignatureValidationException e) { - LOGGER.debug("Rejected signature by key " + verification.getSigningKey(), e); - layer.addRejectedPrependedSignature(new SignatureVerification.Failure(verification, e)); - } - } - - for (SignatureVerification.Failure rejected : inbandSignaturesWithMissingCert) { - layer.addRejectedOnePassSignature(rejected); - } - - for (SignatureVerification.Failure rejected : prependedSignaturesWithMissingCert) { - layer.addRejectedPrependedSignature(rejected); - } - - for (SignatureVerification.Failure rejected : detachedSignaturesWithMissingCert) { - layer.addRejectedDetachedSignature(rejected); - } - } - - @Override - public void write(int b) { - updatePacket((byte) b); - } - - @Override - public void write(@Nonnull byte[] b, int off, int len) { - updatePacket(b, off, len); - } - - public void nextPacket(OpenPgpPacket nextPacket) { - if (nextPacket == OpenPgpPacket.LIT) { - isLiteral = true; - if (literalOPS.isEmpty() && !opsUpdateStack.isEmpty()) { - literalOPS = opsUpdateStack.pop(); - } - } else { - isLiteral = false; - } - } - - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt new file mode 100644 index 00000000..25b29100 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -0,0 +1,903 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.bcpg.BCPGInputStream +import org.bouncycastle.bcpg.UnsupportedPacketVersionException +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory +import org.bouncycastle.util.io.TeeInputStream +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.* +import org.pgpainless.decryption_verification.MessageMetadata.* +import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil +import org.pgpainless.decryption_verification.syntax_check.InputSymbol +import org.pgpainless.decryption_verification.syntax_check.PDA +import org.pgpainless.decryption_verification.syntax_check.StackSymbol +import org.pgpainless.exception.* +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.key.protection.UnlockSecretKey +import org.pgpainless.key.util.KeyIdUtil +import org.pgpainless.key.util.KeyRingUtils +import org.pgpainless.policy.Policy +import org.pgpainless.signature.SignatureUtils +import org.pgpainless.signature.consumer.CertificateValidator +import org.pgpainless.signature.consumer.OnePassSignatureCheck +import org.pgpainless.signature.consumer.SignatureCheck +import org.pgpainless.signature.consumer.SignatureValidator +import org.pgpainless.util.ArmoredInputStreamFactory +import org.pgpainless.util.SessionKey +import org.slf4j.LoggerFactory +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +class OpenPgpMessageInputStream( + type: Type, + inputStream: InputStream, + private val options: ConsumerOptions, + private val metadata: Layer, + private val policy: Policy) : DecryptionStream() { + + private val signatures: Signatures = Signatures(options) + private var packetInputStream: TeeBCPGInputStream? = null + private var nestedInputStream: InputStream? = null + private val syntaxVerifier = PDA() + private var closed = false + + init { + + // Add detached signatures only on the outermost OpenPgpMessageInputStream + if (metadata is Message) { + signatures.addDetachedSignatures(options.detachedSignatures) + } + + when(type) { + Type.standard -> { + + // tee out packet bytes for signature verification + packetInputStream = TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures) + + // *omnomnom* + consumePackets() + } + + Type.cleartext_signed -> { + val multiPassStrategy = options.multiPassStrategy + val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage( + inputStream, multiPassStrategy.messageOutputStream) + + for (signature in detachedSignatures) { + signatures.addDetachedSignature(signature) + } + + options.forceNonOpenPgpData() + nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures) + } + + Type.non_openpgp -> { + packetInputStream = null + nestedInputStream = TeeInputStream(inputStream, this.signatures) + } + } + } + + enum class Type { + standard, cleartext_signed, non_openpgp + } + + constructor(inputStream: InputStream, options: ConsumerOptions, metadata: Layer, policy: Policy): + this(Type.standard, inputStream, options, metadata, policy) + + private fun consumePackets() { + val pIn = packetInputStream ?: return + + var packet: OpenPgpPacket? + + // Comsume packets, potentially stepping into nested layers + layer@ while (run { + packet = pIn.nextPacketTag() + packet + } != null) { + + signatures.nextPacket(packet!!) + // Consume packets in a layer + when(packet) { + + OpenPgpPacket.LIT -> { + processLiteralData() + break@layer // nest down + } + + OpenPgpPacket.COMP -> { + processCompressedData() + break@layer // nest down + } + + OpenPgpPacket.OPS -> { + processOnePassSignature() // OPS is on the same layer, no nest down + } + + OpenPgpPacket.SIG -> { + processSignature() // SIG is on the same layer, no nest down + } + + OpenPgpPacket.PKESK, OpenPgpPacket.SKESK, OpenPgpPacket.SED, OpenPgpPacket.SEIPD -> { + if (processEncryptedData()) { + break@layer + } + throw MissingDecryptionMethodException("No working decryption method found.") + } + + OpenPgpPacket.MARKER -> { + LOGGER.debug("Skipping Marker Packet") + pIn.readMarker() + } + + OpenPgpPacket.SK, OpenPgpPacket.PK, OpenPgpPacket.SSK, OpenPgpPacket.PSK, OpenPgpPacket.TRUST, OpenPgpPacket.UID, OpenPgpPacket.UATTR -> + throw MalformedOpenPgpMessageException("Illegal Packet in Stream: $packet") + + OpenPgpPacket.EXP_1, OpenPgpPacket.EXP_2, OpenPgpPacket.EXP_3, OpenPgpPacket.EXP_4 -> + throw MalformedOpenPgpMessageException("Unsupported Packet in Stream: $packet") + + else -> + throw MalformedOpenPgpMessageException("Unexpected Packet in Stream: $packet") + } + } + } + + private fun processLiteralData() { + LOGGER.debug("Literal Data Packet at depth ${metadata.depth} encountered.") + syntaxVerifier.next(InputSymbol.LITERAL_DATA) + val literalData = packetInputStream!!.readLiteralData() + + // Extract Metadata + metadata.setChild(LiteralData( + literalData.fileName, literalData.modificationTime, + StreamEncoding.requireFromCode(literalData.format))) + + nestedInputStream = literalData.inputStream + } + + private fun processCompressedData() { + syntaxVerifier.next(InputSymbol.COMPRESSED_DATA) + signatures.enterNesting() + val compressedData = packetInputStream!!.readCompressedData() + + // Extract Metadata + val compressionLayer = CompressedData( + CompressionAlgorithm.requireFromId(compressedData.algorithm), + metadata.depth + 1) + + LOGGER.debug("Compressed Data Packet (${compressionLayer.algorithm}) at depth ${metadata.depth} encountered.") + nestedInputStream = OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy) + } + + private fun processOnePassSignature() { + syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE) + val ops = packetInputStream!!.readOnePassSignature() + LOGGER.debug("One-Pass-Signature Packet by key ${KeyIdUtil.formatKeyId(ops.keyID)} at depth ${metadata.depth} encountered.") + signatures.addOnePassSignature(ops) + } + + private fun processSignature() { + // true if signature corresponds to OPS + val isSigForOps = syntaxVerifier.peekStack() == StackSymbol.OPS + syntaxVerifier.next(InputSymbol.SIGNATURE) + val signature = try { + packetInputStream!!.readSignature() + } catch (e : UnsupportedPacketVersionException) { + LOGGER.debug("Unsupported Signature at depth ${metadata.depth} encountered.", e) + return + } + + val keyId = SignatureUtils.determineIssuerKeyId(signature) + if (isSigForOps) { + LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${metadata.depth} encountered.") + signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with + signatures.addCorrespondingOnePassSignature(signature, metadata, policy) + } else { + LOGGER.debug("Prepended Signature Packet by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${metadata.depth} encountered.") + signatures.addPrependedSignature(signature) + } + } + + private fun processEncryptedData(): Boolean { + LOGGER.debug("Symmetrically Encrypted Data Packet at depth ${metadata.depth} encountered.") + syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA) + val encDataList = packetInputStream!!.readEncryptedDataList() + if (!encDataList.isIntegrityProtected) { + LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.") + if (!options.isIgnoreMDCErrors) { + throw MessageNotIntegrityProtectedException() + } + } + + val esks = SortedESKs(encDataList) + LOGGER.debug("Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" + + " ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" + + " have an anonymous recipient.") + + // try custom decryptor factories + for ((key, decryptorFactory) in options.customDecryptorFactories) { + LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.") + esks.pkesks.filter { + // find matching PKESK + it.keyID == key.subkeyId + }.forEach { + // attempt decryption + if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) { + return true + } + } + } + + // try provided session key + if (options.sessionKey != null) { + val sk = options.sessionKey!! + LOGGER.debug("Attempt decryption with provided session key.") + throwIfUnacceptable(sk.algorithm) + + val decryptorFactory = ImplementationFactory.getInstance() + .getSessionKeyDataDecryptorFactory(sk) + val layer = EncryptedData(sk.algorithm, metadata.depth + 1) + val skEncData = encDataList.extractSessionKeyEncryptedData() + try { + val decrypted = skEncData.getDataStream(decryptorFactory) + layer.sessionKey = sk + val integrityProtected = IntegrityProtectedInputStream(decrypted, skEncData, options) + nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, layer, policy) + LOGGER.debug("Successfully decrypted data using provided session key") + return true + } catch (e : PGPException) { + // Session key mismatch? + LOGGER.debug("Decryption using provided session key failed. Mismatched session key and message?", e) + } + } + + // try passwords + for (passphrase in options.decryptionPassphrases) { + for (skesk in esks.skesks) { + LOGGER.debug("Attempt decryption with provided passphrase") + val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm) + if (!isAcceptable(algorithm)) { + LOGGER.debug("Skipping SKESK with unacceptable encapsulation algorithm $algorithm") + continue + } + + val decryptorFactory = ImplementationFactory.getInstance() + .getPBEDataDecryptorFactory(passphrase) + if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) { + return true + } + } + } + + val postponedDueToMissingPassphrase = mutableListOf>() + + // try (known) secret keys + for (pkesk in esks.pkesks) { + val keyId = pkesk.keyID + LOGGER.debug("Encountered PKESK for recipient ${KeyIdUtil.formatKeyId(keyId)}") + val decryptionKeys = getDecryptionKey(keyId) + if (decryptionKeys == null) { + LOGGER.debug("Skipping PKESK because no matching key ${KeyIdUtil.formatKeyId(keyId)} was provided.") + continue + } + val secretKey = decryptionKeys.getSecretKey(keyId) + val decryptionKeyId = SubkeyIdentifier(decryptionKeys, keyId) + if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + continue + } + + LOGGER.debug("Attempt decryption using secret key $decryptionKeyId") + val protector = options.getSecretKeyProtector(decryptionKeys) + if (!protector.hasPassphraseFor(keyId)) { + LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") + postponedDueToMissingPassphrase.add(secretKey to pkesk) + continue + } + + val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + return true + } + } + + // try anonymous secret keys + for (pkesk in esks.anonPkesks) { + for ((decryptionKeys, secretKey) in findPotentialDecryptionKeys(pkesk)) { + val decryptionKeyId = SubkeyIdentifier(decryptionKeys, secretKey.keyID) + if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + continue + } + + LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeyId.") + val protector = options.getSecretKeyProtector(decryptionKeys) + + if (!protector.hasPassphraseFor(secretKey.keyID)) { + LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") + postponedDueToMissingPassphrase.add(secretKey to pkesk) + continue + } + + val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + return true + } + } + } + + if (options.missingKeyPassphraseStrategy == MissingKeyPassphraseStrategy.THROW_EXCEPTION) { + // Non-interactive mode: Throw an exception with all locked decryption keys + postponedDueToMissingPassphrase.map { + SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) + }.also { + if (it.isNotEmpty()) + throw MissingPassphraseException(it.toSet()) + } + } else if (options.missingKeyPassphraseStrategy == MissingKeyPassphraseStrategy.INTERACTIVE) { + for ((secretKey, pkesk) in postponedDueToMissingPassphrase) { + val keyId = secretKey.keyID + val decryptionKeys = getDecryptionKey(keyId)!! + val decryptionKeyId = SubkeyIdentifier(decryptionKeys, keyId) + if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + continue + } + + LOGGER.debug("Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.") + val protector = options.getSecretKeyProtector(decryptionKeys) + val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + return true + } + } + } else { + throw IllegalStateException("Invalid PostponedKeysStrategy set in consumer options.") + } + + // We did not yet succeed in decrypting any session key :/ + LOGGER.debug("Failed to decrypt encrypted data packet.") + return false + } + + private fun decryptWithPrivateKey(esks: SortedESKs, + privateKey: PGPPrivateKey, + decryptionKeyId: SubkeyIdentifier, + pkesk: PGPPublicKeyEncryptedData): Boolean { + val decryptorFactory = ImplementationFactory.getInstance() + .getPublicKeyDataDecryptorFactory(privateKey) + return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk) + } + + private fun hasUnsupportedS2KSpecifier(secretKey: PGPSecretKey, decryptionKeyId: SubkeyIdentifier): Boolean { + val s2k = secretKey.s2K + if (s2k != null) { + if (s2k.type in 100..110) { + LOGGER.debug("Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}") + return true + } + } + return false + } + + private fun decryptSKESKAndStream(esks: SortedESKs, + skesk: PGPPBEEncryptedData, + decryptorFactory: PBEDataDecryptorFactory): Boolean { + try { + val decrypted = skesk.getDataStream(decryptorFactory) + val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory)) + throwIfUnacceptable(sessionKey.algorithm) + val encryptedData = EncryptedData(sessionKey.algorithm, metadata.depth + 1) + encryptedData.sessionKey = sessionKey + encryptedData.recipients = esks.pkesks.map { it.keyID } + LOGGER.debug("Successfully decrypted data with passphrase") + val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options) + nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) + return true + } catch (e : UnacceptableAlgorithmException) { + throw e + } catch (e : PGPException) { + LOGGER.debug("Decryption of encrypted data packet using password failed. Password mismatch?", e) + } + return false + } + + private fun decryptPKESKAndStream(esks: SortedESKs, + decryptionKeyId: SubkeyIdentifier, + decryptorFactory: PublicKeyDataDecryptorFactory, + pkesk: PGPPublicKeyEncryptedData): Boolean { + try { + val decrypted = pkesk.getDataStream(decryptorFactory) + val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory)) + throwIfUnacceptable(sessionKey.algorithm) + + val encryptedData = EncryptedData( + SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)), + metadata.depth + 1) + encryptedData.decryptionKey = decryptionKeyId + encryptedData.sessionKey = sessionKey + encryptedData.recipients = esks.pkesks.plus(esks.anonPkesks).map { it.keyID } + LOGGER.debug("Successfully decrypted data with key $decryptionKeyId") + val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options) + nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) + return true + } catch (e : UnacceptableAlgorithmException) { + throw e + } catch (e : PGPException) { + LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e) + } + return false + } + + override fun read(): Int { + if (nestedInputStream == null) { + if (packetInputStream != null) { + syntaxVerifier.assertValid() + } + return -1 + } + + val r: Int = try { + nestedInputStream!!.read() + } catch (e: IOException) { + -1 + } + if (r != -1) { + signatures.updateLiteral(r.toByte()) + } else { + nestedInputStream!!.close() + collectMetadata() + nestedInputStream = null + if (packetInputStream != null) { + try { + consumePackets() + } catch (e: PGPException) { + throw RuntimeException(e) + } + } + signatures.finish(metadata, policy) + } + return r + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + if (nestedInputStream == null) { + if (packetInputStream != null) { + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE) + syntaxVerifier.assertValid() + } + return -1 + } + val r = nestedInputStream!!.read(b, off, len) + if (r != -1) { + signatures.updateLiteral(b, off, r) + } else { + nestedInputStream!!.close() + collectMetadata() + nestedInputStream = null + if (packetInputStream != null) { + try { + consumePackets() + } catch (e: PGPException) { + throw RuntimeException(e) + } + } + signatures.finish(metadata, policy) + } + return r + } + + override fun close() { + super.close() + if (closed) { + if (packetInputStream != null) { + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE) + syntaxVerifier.assertValid() + } + return + } + if (nestedInputStream != null) { + nestedInputStream!!.close() + collectMetadata() + nestedInputStream = null + } + try { + consumePackets() + } catch (e: PGPException) { + throw RuntimeException(e) + } + if (packetInputStream != null) { + syntaxVerifier.next(InputSymbol.END_OF_SEQUENCE) + syntaxVerifier.assertValid() + packetInputStream!!.close() + } + closed = true + } + + private fun collectMetadata() { + if (nestedInputStream is OpenPgpMessageInputStream) { + val child = nestedInputStream as OpenPgpMessageInputStream + metadata.setChild(child.metadata as Nested) + } + } + + override fun getMetadata(): MessageMetadata { + check(closed) { "Stream must be closed before access to metadata can be granted." } + + return MessageMetadata((metadata as Message)) + } + + private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.decryptionKeys.firstOrNull { + it.any { + k -> k.keyID == keyId + }.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { + k -> k.keyID == keyId + }) + } + + private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List> { + val algorithm = pkesk.algorithm + val candidates = mutableListOf>() + options.decryptionKeys.forEach { + val info = PGPainless.inspectKeyRing(it) + for (key in info.decryptionSubkeys) { + if (key.algorithm == algorithm && info.isSecretKeyAvailable(key.keyID)) { + candidates.add(it to it.getSecretKey(key.keyID)) + } + } + } + return candidates + } + + private fun isAcceptable(algorithm: SymmetricKeyAlgorithm): Boolean = + policy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm) + + private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) { + if (!isAcceptable(algorithm)) { + throw UnacceptableAlgorithmException("Symmetric-Key algorithm $algorithm is not acceptable for message decryption.") + } + } + + private class SortedESKs(esks: PGPEncryptedDataList) { + val skesks: List + val pkesks: List + val anonPkesks: List + + init { + skesks = mutableListOf() + pkesks = mutableListOf() + anonPkesks = mutableListOf() + for (esk in esks) { + if (esk is PGPPBEEncryptedData) { + skesks.add(esk) + } else if (esk is PGPPublicKeyEncryptedData) { + if (esk.keyID != 0L) { + pkesks.add(esk) + } else { + anonPkesks.add(esk) + } + } else { + throw IllegalArgumentException("Unknown ESK class type ${esk.javaClass}") + } + } + } + + val all: List + get() = skesks.plus(pkesks).plus(anonPkesks) + } + + private class Signatures( + val options: ConsumerOptions + ) : OutputStream() { + val detachedSignatures = mutableListOf() + val prependedSignatures = mutableListOf() + val onePassSignatures = mutableListOf() + val opsUpdateStack = ArrayDeque>() + var literalOPS = mutableListOf() + val correspondingSignatures = mutableListOf() + val prependedSignaturesWithMissingCert = mutableListOf() + val inbandSignaturesWithMissingCert = mutableListOf() + val detachedSignaturesWithMissingCert = mutableListOf() + var isLiteral = true + + fun addDetachedSignatures(signatures: Collection) { + for (signature in signatures) { + addDetachedSignature(signature) + } + } + + fun addDetachedSignature(signature: PGPSignature) { + val check = initializeSignature(signature) + val keyId = SignatureUtils.determineIssuerKeyId(signature) + if (check != null) { + detachedSignatures.add(check) + } else { + LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + detachedSignaturesWithMissingCert.add(SignatureVerification.Failure( + SignatureVerification(signature, null), + SignatureValidationException("Missing verification key."))) + } + } + + fun addPrependedSignature(signature: PGPSignature) { + val check = initializeSignature(signature) + val keyId = SignatureUtils.determineIssuerKeyId(signature) + if (check != null) { + prependedSignatures.add(check) + } else { + LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + prependedSignaturesWithMissingCert.add(SignatureVerification.Failure( + SignatureVerification(signature, null), + SignatureValidationException("Missing verification key") + )) + } + } + + fun initializeSignature(signature: PGPSignature): SignatureCheck? { + val keyId = SignatureUtils.determineIssuerKeyId(signature) + val certificate = findCertificate(keyId) ?: return null + + val verifierKey = SubkeyIdentifier(certificate, keyId) + initialize(signature, certificate, keyId) + return SignatureCheck(signature, certificate, verifierKey) + } + + fun addOnePassSignature(signature: PGPOnePassSignature) { + val certificate = findCertificate(signature.keyID) + + if (certificate != null) { + val ops = OnePassSignatureCheck(signature, certificate) + initialize(signature, certificate) + onePassSignatures.add(ops) + literalOPS.add(ops) + } + if (signature.isContaining) { + enterNesting() + } + } + + fun addCorrespondingOnePassSignature(signature: PGPSignature, layer: Layer, policy: Policy) { + var found = false + val keyId = SignatureUtils.determineIssuerKeyId(signature) + for ((i, check) in onePassSignatures.withIndex().reversed()) { + if (check.onePassSignature.keyID != keyId) { + continue + } + found = true + + if (check.signature != null) { + continue + } + + check.signature = signature + val verification = SignatureVerification(signature, + SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID)) + + try { + SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + .verify(signature) + CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy) + LOGGER.debug("Acceptable signature by key ${verification.signingKey}") + layer.addVerifiedOnePassSignature(verification) + } catch (e: SignatureValidationException) { + LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) + layer.addRejectedOnePassSignature(SignatureVerification.Failure(verification, e)) + } + break + } + + if (!found) { + LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + inbandSignaturesWithMissingCert.add(SignatureVerification.Failure( + SignatureVerification(signature, null), + SignatureValidationException("Missing verification key.") + )) + } + } + + fun enterNesting() { + opsUpdateStack.addFirst(literalOPS) + literalOPS = mutableListOf() + } + + fun leaveNesting() { + if (opsUpdateStack.isEmpty()) { + return + } + opsUpdateStack.removeFirst() + } + + fun findCertificate(keyId: Long): PGPPublicKeyRing? { + val cert = options.certificateSource.getCertificate(keyId) + if (cert != null) { + return cert + } + + if (options.missingCertificateCallback != null) { + return options.missingCertificateCallback!!.onMissingPublicKeyEncountered(keyId) + } + return null // TODO: Missing cert for sig + } + + fun updateLiteral(b: Byte) { + for (ops in literalOPS) { + ops.onePassSignature.update(b) + } + + for (detached in detachedSignatures) { + detached.signature.update(b) + } + + for (prepended in prependedSignatures) { + prepended.signature.update(b) + } + } + + fun updateLiteral(buf: ByteArray, off: Int, len: Int) { + for (ops in literalOPS) { + ops.onePassSignature.update(buf, off, len) + } + + for (detached in detachedSignatures) { + detached.signature.update(buf, off, len) + } + + for (prepended in prependedSignatures) { + prepended.signature.update(buf, off, len) + } + } + + fun updatePacket(b: Byte) { + for (nestedOPSs in opsUpdateStack.reversed()) { + for (ops in nestedOPSs) { + ops.onePassSignature.update(b) + } + } + } + + fun updatePacket(buf: ByteArray, off: Int, len: Int) { + for (nestedOPSs in opsUpdateStack.reversed()) { + for (ops in nestedOPSs) { + ops.onePassSignature.update(buf, off, len) + } + } + } + + fun finish(layer: Layer, policy: Policy) { + for (detached in detachedSignatures) { + val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier) + try { + SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + .verify(detached.signature) + CertificateValidator.validateCertificateAndVerifyInitializedSignature( + detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy) + LOGGER.debug("Acceptable signature by key ${verification.signingKey}") + layer.addVerifiedDetachedSignature(verification) + } catch (e : SignatureValidationException) { + LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) + layer.addRejectedDetachedSignature(SignatureVerification.Failure(verification, e)) + } + } + + for (prepended in prependedSignatures) { + val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier) + try { + SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + .verify(prepended.signature) + CertificateValidator.validateCertificateAndVerifyInitializedSignature( + prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy) + LOGGER.debug("Acceptable signature by key ${verification.signingKey}") + layer.addVerifiedPrependedSignature(verification) + } catch (e : SignatureValidationException) { + LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) + layer.addRejectedPrependedSignature(SignatureVerification.Failure(verification, e)) + } + } + + for (rejected in inbandSignaturesWithMissingCert) { + layer.addRejectedOnePassSignature(rejected) + } + + for (rejected in prependedSignaturesWithMissingCert) { + layer.addRejectedPrependedSignature(rejected) + } + + for (rejected in detachedSignaturesWithMissingCert) { + layer.addRejectedDetachedSignature(rejected) + } + } + + override fun write(b: Int) { + updatePacket(b.toByte()) + } + + override fun write(buf: ByteArray, off: Int, len: Int) { + updatePacket(buf, off, len) + } + + fun nextPacket(nextPacket: OpenPgpPacket) { + if (nextPacket == OpenPgpPacket.LIT) { + isLiteral = true + if (literalOPS.isEmpty() && opsUpdateStack.isNotEmpty()) { + literalOPS = opsUpdateStack.removeFirst() + } + } else { + isLiteral = false + } + } + + companion object { + @JvmStatic + private fun initialize(signature: PGPSignature, certificate: PGPPublicKeyRing, keyId: Long) { + val verifierProvider = ImplementationFactory.getInstance() + .pgpContentVerifierBuilderProvider + try { + signature.init(verifierProvider, certificate.getPublicKey(keyId)) + } catch (e : PGPException) { + throw RuntimeException(e) + } + } + + @JvmStatic + private fun initialize(ops: PGPOnePassSignature, certificate: PGPPublicKeyRing) { + val verifierProvider = ImplementationFactory.getInstance() + .pgpContentVerifierBuilderProvider + try { + ops.init(verifierProvider, certificate.getPublicKey(ops.keyID)) + } catch (e : PGPException) { + throw RuntimeException(e) + } + } + } + } + + companion object { + @JvmStatic + private val LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream::class.java) + + @JvmStatic + fun create(inputStream: InputStream, + options: ConsumerOptions) = create(inputStream, options, PGPainless.getPolicy()) + + @JvmStatic + fun create(inputStream: InputStream, + options: ConsumerOptions, + policy: Policy) = create(inputStream, options, Message(), policy) + + @JvmStatic + internal fun create(inputStream: InputStream, + options: ConsumerOptions, + metadata: Layer, + policy: Policy): OpenPgpMessageInputStream { + val openPgpIn = OpenPgpInputStream(inputStream) + openPgpIn.reset() + + if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData) { + return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy) + } + + if (openPgpIn.isBinaryOpenPgp) { + // Simply consume OpenPGP message + return OpenPgpMessageInputStream(Type.standard, openPgpIn, options, metadata, policy) + } + + return if (openPgpIn.isAsciiArmored) { + val armorIn = ArmoredInputStreamFactory.get(openPgpIn) + if (armorIn.isClearText) { + (metadata as Message).cleartextSigned = true + OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy) + } else { + // Simply consume dearmored OpenPGP message + OpenPgpMessageInputStream(Type.standard, armorIn, options, metadata, policy) + } + } else { + throw AssertionError("Cannot deduce type of data.") + } + } + } +} \ No newline at end of file From b33ee90845ddbb08e68adeb299cde1192f360e0d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 28 Aug 2023 16:17:47 +0200 Subject: [PATCH 039/155] Kotlin conversion: SignatureUtils --- .../pgpainless/signature/SignatureUtils.java | 342 ------------------ .../pgpainless/signature/SignatureUtils.kt | 263 ++++++++++++++ 2 files changed, 263 insertions(+), 342 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java deleted file mode 100644 index 663ef003..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java +++ /dev/null @@ -1,342 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import javax.annotation.Nonnull; - -import org.bouncycastle.bcpg.sig.IssuerKeyID; -import org.bouncycastle.bcpg.sig.KeyExpirationTime; -import org.bouncycastle.bcpg.sig.RevocationReason; -import org.bouncycastle.bcpg.sig.SignatureExpirationTime; -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.util.encoders.Hex; -import org.bouncycastle.util.io.Streams; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.util.RevocationAttributes; -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; -import org.pgpainless.util.ArmorUtils; - -/** - * Utility methods related to signatures. - */ -public final class SignatureUtils { - - public static final int MAX_ITERATIONS = 10000; - - private SignatureUtils() { - - } - - /** - * Extract and return the key expiration date value from the given signature. - * If the signature does not carry a {@link KeyExpirationTime} subpacket, return null. - * - * @param keyCreationDate creation date of the key - * @param signature signature - * @return key expiration date as given by the signature - */ - public static Date getKeyExpirationDate(Date keyCreationDate, PGPSignature signature) { - KeyExpirationTime keyExpirationTime = SignatureSubpacketsUtil.getKeyExpirationTime(signature); - long expiresInSecs = keyExpirationTime == null ? 0 : keyExpirationTime.getTime(); - return datePlusSeconds(keyCreationDate, expiresInSecs); - } - - /** - * Return the expiration date of the signature. - * If the signature has no expiration date, {@link #datePlusSeconds(Date, long)} will return null. - * - * @param signature signature - * @return expiration date of the signature, or null if it does not expire. - */ - public static Date getSignatureExpirationDate(PGPSignature signature) { - Date creationDate = signature.getCreationTime(); - SignatureExpirationTime signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTime(signature); - long expiresInSecs = signatureExpirationTime == null ? 0 : signatureExpirationTime.getTime(); - return datePlusSeconds(creationDate, expiresInSecs); - } - - /** - * Return a new date which represents the given date plus the given amount of seconds added. - * - * Since '0' is a special date value in the OpenPGP specification - * (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0. - * - * @param date date - * @param seconds number of seconds to be added - * @return date plus seconds or null if seconds is '0' - */ - public static Date datePlusSeconds(Date date, long seconds) { - if (seconds == 0) { - return null; - } - return new Date(date.getTime() + 1000 * seconds); - } - - /** - * Return true, if the expiration date of the {@link PGPSignature} lays in the past. - * If no expiration date is present in the signature, it is considered non-expired. - * - * @param signature signature - * @return true if expired, false otherwise - */ - public static boolean isSignatureExpired(PGPSignature signature) { - return isSignatureExpired(signature, new Date()); - } - - /** - * Return true, if the expiration date of the given {@link PGPSignature} is past the given comparison {@link Date}. - * If no expiration date is present in the signature, it is considered non-expiring. - * - * @param signature signature - * @param comparisonDate reference date - * @return true if sig is expired at reference date, false otherwise - */ - public static boolean isSignatureExpired(PGPSignature signature, Date comparisonDate) { - Date expirationDate = getSignatureExpirationDate(signature); - return expirationDate != null && comparisonDate.after(expirationDate); - } - - /** - * Return true if the provided signature is a hard revocation. - * Hard revocations are revocation signatures which either carry a revocation reason of - * {@link RevocationAttributes.Reason#KEY_COMPROMISED} or {@link RevocationAttributes.Reason#NO_REASON}, - * or no reason at all. - * - * @param signature signature - * @return true if signature is a hard revocation - */ - public static boolean isHardRevocation(PGPSignature signature) { - - SignatureType type = SignatureType.valueOf(signature.getSignatureType()); - if (type != SignatureType.KEY_REVOCATION && type != SignatureType.SUBKEY_REVOCATION && type != SignatureType.CERTIFICATION_REVOCATION) { - // Not a revocation - return false; - } - - RevocationReason reasonSubpacket = SignatureSubpacketsUtil.getRevocationReason(signature); - if (reasonSubpacket == null) { - // no reason -> hard revocation - return true; - } - return RevocationAttributes.Reason.isHardRevocation(reasonSubpacket.getRevocationReason()); - } - - /** - * Parse an ASCII encoded list of OpenPGP signatures into a {@link PGPSignatureList} - * and return it as a {@link List}. - * - * @param encodedSignatures ASCII armored signature list - * @return signature list - * - * @throws IOException if the signatures cannot be read - * @throws PGPException in case of a broken signature - */ - public static List readSignatures(String encodedSignatures) throws IOException, PGPException { - @SuppressWarnings("CharsetObjectCanBeUsed") - Charset utf8 = Charset.forName("UTF-8"); - byte[] bytes = encodedSignatures.getBytes(utf8); - return readSignatures(bytes); - } - - /** - * Read a single, or a list of {@link PGPSignature PGPSignatures} and return them as a {@link List}. - * - * @param encodedSignatures ASCII armored or binary signatures - * @return signatures - * @throws IOException if the signatures cannot be read - * @throws PGPException in case of an OpenPGP error - */ - public static List readSignatures(byte[] encodedSignatures) throws IOException, PGPException { - InputStream inputStream = new ByteArrayInputStream(encodedSignatures); - return readSignatures(inputStream); - } - - /** - * Read and return {@link PGPSignature PGPSignatures}. - * This method can deal with signatures that may be armored, compressed and may contain marker packets. - * - * @param inputStream input stream - * @return list of encountered signatures - * @throws IOException in case of a stream error - * @throws PGPException in case of an OpenPGP error - */ - public static List readSignatures(InputStream inputStream) throws IOException, PGPException { - return readSignatures(inputStream, MAX_ITERATIONS); - } - - /** - * Read and return {@link PGPSignature PGPSignatures}. - * This method can deal with signatures that may be binary, armored and may contain marker packets. - * - * @param inputStream input stream - * @param maxIterations number of loop iterations until reading is aborted - * @return list of encountered signatures - * @throws IOException in case of a stream error - */ - public static List readSignatures(InputStream inputStream, int maxIterations) throws IOException { - List signatures = new ArrayList<>(); - InputStream pgpIn = ArmorUtils.getDecoderStream(inputStream); - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(pgpIn); - - int i = 0; - Object nextObject; - while (i++ < maxIterations && (nextObject = objectFactory.nextObject()) != null) { - - // Since signatures are indistinguishable from randomness, there is no point in having them compressed, - // except for an attacker who is trying to exploit flaws in the decompression algorithm. - // Therefore, we ignore compressed data packets without attempting decompression. - if (nextObject instanceof PGPCompressedData) { - PGPCompressedData compressedData = (PGPCompressedData) nextObject; - // getInputStream() does not do decompression, contrary to getDataStream(). - Streams.drain(compressedData.getInputStream()); // Skip packet without decompressing - } - - if (nextObject instanceof PGPSignatureList) { - PGPSignatureList signatureList = (PGPSignatureList) nextObject; - for (PGPSignature s : signatureList) { - signatures.add(s); - } - } - - if (nextObject instanceof PGPSignature) { - signatures.add((PGPSignature) nextObject); - } - } - pgpIn.close(); - - return signatures; - } - - /** - * Determine the issuer key-id of a {@link PGPSignature}. - * This method first inspects the {@link IssuerKeyID} subpacket of the signature and returns the key-id if present. - * If not, it inspects the {@link org.bouncycastle.bcpg.sig.IssuerFingerprint} packet and retrieves the key-id from the fingerprint. - * - * Otherwise, it returns 0. - * @param signature signature - * @return signatures issuing key id - */ - public static long determineIssuerKeyId(PGPSignature signature) { - if (signature.getVersion() == 3) { - // V3 sigs do not contain subpackets - return signature.getKeyID(); - } - - IssuerKeyID issuerKeyId = SignatureSubpacketsUtil.getIssuerKeyId(signature); - OpenPgpFingerprint fingerprint = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature); - - if (issuerKeyId != null && issuerKeyId.getKeyID() != 0) { - return issuerKeyId.getKeyID(); - } - if (issuerKeyId == null && fingerprint != null) { - return fingerprint.getKeyId(); - } - return 0; - } - - /** - * Return the digest prefix of the signature as hex-encoded String. - * - * @param signature signature - * @return digest prefix - */ - public static String getSignatureDigestPrefix(PGPSignature signature) { - return Hex.toHexString(signature.getDigestPrefix()); - } - - public static boolean wasIssuedBy(byte[] fingerprint, PGPSignature signature) { - try { - OpenPgpFingerprint fp = OpenPgpFingerprint.parseFromBinary(fingerprint); - OpenPgpFingerprint issuerFp = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature); - if (issuerFp == null) { - return fp.getKeyId() == signature.getKeyID(); - } - return fp.equals(issuerFp); - } catch (IllegalArgumentException e) { - // Unknown fingerprint length - return false; - } - } - - /** - * Extract all signatures from the given
key
which were issued by
issuerKeyId
- * over
userId
. - * - * @param key public key - * @param userId user-id - * @param issuerKeyId issuer key-id - * @return (potentially empty) list of signatures - */ - public static @Nonnull List getSignaturesOverUserIdBy( - @Nonnull PGPPublicKey key, - @Nonnull String userId, - long issuerKeyId) { - List signaturesByKeyId = new ArrayList<>(); - Iterator userIdSignatures = key.getSignaturesForID(userId); - - // getSignaturesForID() is nullable for some reason -.- - if (userIdSignatures == null) { - return signaturesByKeyId; - } - - // filter for signatures by key-id - while (userIdSignatures.hasNext()) { - PGPSignature signature = userIdSignatures.next(); - if (signature.getKeyID() == issuerKeyId) { - signaturesByKeyId.add(signature); - } - } - - return Collections.unmodifiableList(signaturesByKeyId); - } - - public static @Nonnull List getDelegations(PGPPublicKeyRing key) { - List delegations = new ArrayList<>(); - PGPPublicKey primaryKey = key.getPublicKey(); - Iterator signatures = primaryKey.getKeySignatures(); - outerloop: while (signatures.hasNext()) { - PGPSignature signature = signatures.next(); - Iterator subkeys = key.getPublicKeys(); - while (subkeys.hasNext()) { - if (signature.getKeyID() == subkeys.next().getKeyID()) { - continue outerloop; - } - } - delegations.add(signature); - } - - return delegations; - } - - public static @Nonnull List get3rdPartyCertificationsFor(String userId, PGPPublicKeyRing key) { - PGPPublicKey primaryKey = key.getPublicKey(); - List certifications = new ArrayList<>(); - Iterator it = primaryKey.getSignaturesForID(userId); - while (it.hasNext()) { - PGPSignature sig = it.next(); - if (sig.getKeyID() != primaryKey.getKeyID()) { - certifications.add(sig); - } - } - return certifications; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt new file mode 100644 index 00000000..2f320bf0 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt @@ -0,0 +1,263 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature + +import org.bouncycastle.bcpg.sig.KeyExpirationTime +import org.bouncycastle.openpgp.* +import org.bouncycastle.util.encoders.Hex +import org.bouncycastle.util.io.Streams +import org.pgpainless.algorithm.SignatureType +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.util.RevocationAttributes.Reason +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil +import org.pgpainless.util.ArmorUtils +import java.io.InputStream +import java.util.* + +const val MAX_ITERATIONS = 10000 + +class SignatureUtils { + companion object { + + /** + * Extract and return the key expiration date value from the given signature. + * If the signature does not carry a {@link KeyExpirationTime} subpacket, return null. + * + * @param keyCreationDate creation date of the key + * @param signature signature + * @return key expiration date as given by the signature + */ + @JvmStatic + fun getKeyExpirationDate(keyCreationDate: Date, signature: PGPSignature): Date? { + val expirationPacket: KeyExpirationTime? = SignatureSubpacketsUtil.getKeyExpirationTime(signature) + val expiresInSeconds = expirationPacket?.time ?: 0 + return datePlusSeconds(keyCreationDate, expiresInSeconds) + } + + /** + * Return the expiration date of the signature. + * If the signature has no expiration date, {@link #datePlusSeconds(Date, long)} will return null. + * + * @param signature signature + * @return expiration date of the signature, or null if it does not expire. + */ + @JvmStatic + fun getSignatureExpirationDate(signature: PGPSignature): Date? { + val creationTime = signature.creationTime + val expirationTime = SignatureSubpacketsUtil.getSignatureExpirationTime(signature) + val expiresInSeconds = expirationTime?.time ?: 0 + return datePlusSeconds(creationTime, expiresInSeconds) + } + + /** + * Return a new date which represents the given date plus the given amount of seconds added. + * + * Since '0' is a special date value in the OpenPGP specification + * (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0. + * + * @param date date + * @param seconds number of seconds to be added + * @return date plus seconds or null if seconds is '0' + */ + @JvmStatic + fun datePlusSeconds(date: Date, seconds: Long): Date? { + if (seconds == 0L) { + return null + } + return Date(date.time + 1000 * seconds) + } + + /** + * Return true, if the expiration date of the {@link PGPSignature} lays in the past. + * If no expiration date is present in the signature, it is considered non-expired. + * + * @param signature signature + * @return true if expired, false otherwise + */ + @JvmStatic + fun isSignatureExpired(signature: PGPSignature): Boolean { + return isSignatureExpired(signature, Date()) + } + + /** + * Return true, if the expiration date of the given {@link PGPSignature} is past the given comparison {@link Date}. + * If no expiration date is present in the signature, it is considered non-expiring. + * + * @param signature signature + * @param referenceTime reference date + * @return true if sig is expired at reference date, false otherwise + */ + @JvmStatic + fun isSignatureExpired(signature: PGPSignature, referenceTime: Date): Boolean { + val expirationDate = getSignatureExpirationDate(signature) + return expirationDate != null && referenceTime >= expirationDate + } + + /** + * Return true if the provided signature is a hard revocation. + * Hard revocations are revocation signatures which either carry a revocation reason of + * {@link RevocationAttributes.Reason#KEY_COMPROMISED} or {@link RevocationAttributes.Reason#NO_REASON}, + * or no reason at all. + * + * @param signature signature + * @return true if signature is a hard revocation + */ + @JvmStatic + fun isHardRevocation(signature: PGPSignature): Boolean { + val type = SignatureType.requireFromCode(signature.signatureType) + if (type != SignatureType.KEY_REVOCATION && type != SignatureType.SUBKEY_REVOCATION && type != SignatureType.CERTIFICATION_REVOCATION) { + // Not a revocation + return false + } + + val reason = SignatureSubpacketsUtil.getRevocationReason(signature) ?: return true // no reason -> hard revocation + return Reason.isHardRevocation(reason.revocationReason) + } + + @JvmStatic + fun readSignatures(encodedSignatures: String): List { + return readSignatures(encodedSignatures.toByteArray()) + } + + @JvmStatic + fun readSignatures(encodedSignatures: ByteArray): List { + return readSignatures(encodedSignatures.inputStream()) + } + + @JvmStatic + fun readSignatures(inputStream: InputStream): List { + return readSignatures(inputStream, MAX_ITERATIONS) + } + + /** + * Read and return {@link PGPSignature PGPSignatures}. + * This method can deal with signatures that may be binary, armored and may contain marker packets. + * + * @param inputStream input stream + * @param maxIterations number of loop iterations until reading is aborted + * @return list of encountered signatures + */ + @JvmStatic + fun readSignatures(inputStream: InputStream, maxIterations: Int): List { + val signatures = mutableListOf() + val pgpIn = ArmorUtils.getDecoderStream(inputStream) + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(pgpIn) + + var i = 0 + var nextObject: Any? = null + while (i++ < maxIterations && objectFactory.nextObject().also { nextObject = it } != null) { + // Since signatures are indistinguishable from randomness, there is no point in having them compressed, + // except for an attacker who is trying to exploit flaws in the decompression algorithm. + // Therefore, we ignore compressed data packets without attempting decompression. + if (nextObject is PGPCompressedData) { + // getInputStream() does not do decompression, contrary to getDataStream(). + Streams.drain((nextObject as PGPCompressedData).inputStream) // Skip packet without decompressing + } + + if (nextObject is PGPSignatureList) { + signatures.addAll(nextObject as PGPSignatureList) + } + + if (nextObject is PGPSignature) { + signatures.add(nextObject as PGPSignature) + } + } + + pgpIn.close() + return signatures.toList() + } + + /** + * Determine the issuer key-id of a {@link PGPSignature}. + * This method first inspects the {@link IssuerKeyID} subpacket of the signature and returns the key-id if present. + * If not, it inspects the {@link org.bouncycastle.bcpg.sig.IssuerFingerprint} packet and retrieves the key-id from the fingerprint. + * + * Otherwise, it returns 0. + * @param signature signature + * @return signatures issuing key id + */ + @JvmStatic + fun determineIssuerKeyId(signature: PGPSignature): Long { + if (signature.version == 3) { + // V3 sigs do not contain subpackets + return signature.keyID + } + + val issuerKeyId = SignatureSubpacketsUtil.getIssuerKeyId(signature) + val issuerFingerprint = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature) + + if (issuerKeyId != null && issuerKeyId.keyID != 0L) { + return issuerKeyId.keyID + } + if (issuerKeyId == null && issuerFingerprint != null) { + return issuerFingerprint.keyId + } + return 0 + } + + /** + * Return the digest prefix of the signature as hex-encoded String. + * + * @param signature signature + * @return digest prefix + */ + @JvmStatic + fun getSignatureDigestPrefix(signature: PGPSignature): String { + return Hex.toHexString(signature.digestPrefix) + } + + @JvmStatic + fun wasIssuedBy(fingerprint: ByteArray, signature: PGPSignature): Boolean { + return try { + val pgpFingerprint = OpenPgpFingerprint.parseFromBinary(fingerprint) + wasIssuedBy(pgpFingerprint, signature) + } catch (e : IllegalArgumentException) { + // Unknown fingerprint length + false + } + } + + @JvmStatic + fun wasIssuedBy(fingerprint: OpenPgpFingerprint, signature: PGPSignature): Boolean { + val issuerFp = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature) + ?: return fingerprint.keyId == signature.keyID + return fingerprint == issuerFp + } + + /** + * Extract all signatures from the given
key
which were issued by
issuerKeyId
+ * over
userId
. + * + * @param key public key + * @param userId user-id + * @param issuer issuer key-id + * @return (potentially empty) list of signatures + */ + @JvmStatic + fun getSignaturesOverUserIdBy(key: PGPPublicKey, userId: String, issuer: Long): List { + return key.getSignaturesForID(userId) + ?.asSequence() + ?.filter { it.keyID == issuer } + ?.toList() ?: listOf() + } + + @JvmStatic + fun getDelegations(key: PGPPublicKeyRing): List { + return key.publicKey.keySignatures + .asSequence() + .filter { key.getPublicKey(it.keyID) == null } // Filter out back-sigs from subkeys + .toList() + } + + @JvmStatic + fun get3rdPartyCertificationsFor(key: PGPPublicKeyRing, userId: String): List { + return key.publicKey.getSignaturesForID(userId) + .asSequence() + .filter { it.keyID != key.publicKey.keyID } // Filter out self-sigs + .toList() + } + } +} \ No newline at end of file From 85b1ffe2e9113797c319d777e1eae4c9c706ab0e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 28 Aug 2023 16:18:41 +0200 Subject: [PATCH 040/155] Add PGPSignatureExtensions file --- .../extensions/PGPSignatureExtensions.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt new file mode 100644 index 00000000..657abba9 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle.extensions + +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.signature.SignatureUtils +import java.util.* + +fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? = + SignatureUtils.getKeyExpirationDate(keyCreationDate, this) + +fun PGPSignature.getSignatureExpirationDate(): Date? = + SignatureUtils.getSignatureExpirationDate(this) + +fun PGPSignature.isExpired(referenceTime: Date = Date()) = + SignatureUtils.isSignatureExpired(this, referenceTime) + +fun PGPSignature.getIssuerKeyId() = SignatureUtils.determineIssuerKeyId(this) + +fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint) = SignatureUtils.wasIssuedBy(fingerprint, this) From fa765fdb0d462acb563e63258535c7e305612a1c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 28 Aug 2023 16:26:52 +0200 Subject: [PATCH 041/155] Add documentation to PGPSignatureExtensions --- .../extensions/PGPSignatureExtensions.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt index 657abba9..ccbb8f45 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -9,15 +9,38 @@ import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.signature.SignatureUtils import java.util.* +/** + * Return the value of the KeyExpirationDate subpacket, or null, if the signature does not carry + * such a subpacket. + */ fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? = SignatureUtils.getKeyExpirationDate(keyCreationDate, this) +/** + * Return the value of the signature ExpirationTime subpacket, or null, if the signature + * does not carry such a subpacket. + */ fun PGPSignature.getSignatureExpirationDate(): Date? = SignatureUtils.getSignatureExpirationDate(this) +/** + * Return true, if the signature is expired at the given reference time. + */ fun PGPSignature.isExpired(referenceTime: Date = Date()) = SignatureUtils.isSignatureExpired(this, referenceTime) +/** + * Return the key-ID of the issuer, determined by examining the IssuerKeyId and IssuerFingerprint + * subpackets of the signature. + */ fun PGPSignature.getIssuerKeyId() = SignatureUtils.determineIssuerKeyId(this) +/** + * Return true, if the signature was likely issued by the key with the given fingerprint. + */ fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint) = SignatureUtils.wasIssuedBy(fingerprint, this) + +/** + * Return true, if this signature is a hard revocation. + */ +fun PGPSignature.isHardRevocation() = SignatureUtils.isHardRevocation(this) From 6dc08e74450c31d7f47e049c95139992e1632b0f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 28 Aug 2023 16:39:53 +0200 Subject: [PATCH 042/155] Improve SignatureUtils readability --- .../pgpainless/signature/SignatureUtils.kt | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt index 2f320bf0..b9b80f8f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt @@ -24,7 +24,7 @@ class SignatureUtils { /** * Extract and return the key expiration date value from the given signature. - * If the signature does not carry a {@link KeyExpirationTime} subpacket, return null. + * If the signature does not carry a [KeyExpirationTime] subpacket, return null. * * @param keyCreationDate creation date of the key * @param signature signature @@ -32,24 +32,24 @@ class SignatureUtils { */ @JvmStatic fun getKeyExpirationDate(keyCreationDate: Date, signature: PGPSignature): Date? { - val expirationPacket: KeyExpirationTime? = SignatureSubpacketsUtil.getKeyExpirationTime(signature) - val expiresInSeconds = expirationPacket?.time ?: 0 + val expirationPacket: KeyExpirationTime = SignatureSubpacketsUtil.getKeyExpirationTime(signature) ?: return null + val expiresInSeconds = expirationPacket.time return datePlusSeconds(keyCreationDate, expiresInSeconds) } /** * Return the expiration date of the signature. - * If the signature has no expiration date, {@link #datePlusSeconds(Date, long)} will return null. + * If the signature has no expiration date, [datePlusSeconds] will return null. * * @param signature signature * @return expiration date of the signature, or null if it does not expire. */ @JvmStatic fun getSignatureExpirationDate(signature: PGPSignature): Date? { - val creationTime = signature.creationTime - val expirationTime = SignatureSubpacketsUtil.getSignatureExpirationTime(signature) - val expiresInSeconds = expirationTime?.time ?: 0 - return datePlusSeconds(creationTime, expiresInSeconds) + val expirationTime = SignatureSubpacketsUtil.getSignatureExpirationTime(signature) ?: return null + + val expiresInSeconds = expirationTime.time + return datePlusSeconds(signature.creationTime, expiresInSeconds) } /** @@ -71,7 +71,7 @@ class SignatureUtils { } /** - * Return true, if the expiration date of the {@link PGPSignature} lays in the past. + * Return true, if the expiration date of the [PGPSignature] lays in the past. * If no expiration date is present in the signature, it is considered non-expired. * * @param signature signature @@ -83,7 +83,7 @@ class SignatureUtils { } /** - * Return true, if the expiration date of the given {@link PGPSignature} is past the given comparison {@link Date}. + * Return true, if the expiration date of the given [PGPSignature] is past the given comparison [Date]. * If no expiration date is present in the signature, it is considered non-expiring. * * @param signature signature @@ -92,15 +92,14 @@ class SignatureUtils { */ @JvmStatic fun isSignatureExpired(signature: PGPSignature, referenceTime: Date): Boolean { - val expirationDate = getSignatureExpirationDate(signature) - return expirationDate != null && referenceTime >= expirationDate + val expirationDate = getSignatureExpirationDate(signature) ?: return false + return referenceTime >= expirationDate } /** * Return true if the provided signature is a hard revocation. * Hard revocations are revocation signatures which either carry a revocation reason of - * {@link RevocationAttributes.Reason#KEY_COMPROMISED} or {@link RevocationAttributes.Reason#NO_REASON}, - * or no reason at all. + * [Reason.KEY_COMPROMISED] or [Reason.NO_REASON], or no reason at all. * * @param signature signature * @return true if signature is a hard revocation @@ -133,7 +132,7 @@ class SignatureUtils { } /** - * Read and return {@link PGPSignature PGPSignatures}. + * Read and return [PGPSignatures][PGPSignature]. * This method can deal with signatures that may be binary, armored and may contain marker packets. * * @param inputStream input stream @@ -171,9 +170,9 @@ class SignatureUtils { } /** - * Determine the issuer key-id of a {@link PGPSignature}. - * This method first inspects the {@link IssuerKeyID} subpacket of the signature and returns the key-id if present. - * If not, it inspects the {@link org.bouncycastle.bcpg.sig.IssuerFingerprint} packet and retrieves the key-id from the fingerprint. + * Determine the issuer key-id of a [PGPSignature]. + * This method first inspects the [org.bouncycastle.bcpg.sig.IssuerKeyID] subpacket of the signature and returns the key-id if present. + * If not, it inspects the [org.bouncycastle.bcpg.sig.IssuerFingerprint] packet and retrieves the key-id from the fingerprint. * * Otherwise, it returns 0. * @param signature signature @@ -238,10 +237,11 @@ class SignatureUtils { */ @JvmStatic fun getSignaturesOverUserIdBy(key: PGPPublicKey, userId: String, issuer: Long): List { - return key.getSignaturesForID(userId) - ?.asSequence() - ?.filter { it.keyID == issuer } - ?.toList() ?: listOf() + val signatures = key.getSignaturesForID(userId) ?: return listOf() + return signatures + .asSequence() + .filter { it.keyID == issuer } + .toList() } @JvmStatic From cc63095ab0197539fc2c654366ee7b5388f3dcbe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 28 Aug 2023 18:24:16 +0200 Subject: [PATCH 043/155] Kotlin conversion: SignatureSubpacketsUtil --- .../subpackets/SignatureSubpacketsUtil.java | 703 ------------------ .../subpackets/SignatureSubpacketsUtil.kt | 575 ++++++++++++++ 2 files changed, 575 insertions(+), 703 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java deleted file mode 100644 index 6d53ad1d..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java +++ /dev/null @@ -1,703 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.sig.Exportable; -import org.bouncycastle.bcpg.sig.Features; -import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint; -import org.bouncycastle.bcpg.sig.IssuerFingerprint; -import org.bouncycastle.bcpg.sig.IssuerKeyID; -import org.bouncycastle.bcpg.sig.KeyExpirationTime; -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.NotationData; -import org.bouncycastle.bcpg.sig.PreferredAlgorithms; -import org.bouncycastle.bcpg.sig.PrimaryUserID; -import org.bouncycastle.bcpg.sig.RegularExpression; -import org.bouncycastle.bcpg.sig.Revocable; -import org.bouncycastle.bcpg.sig.RevocationKey; -import org.bouncycastle.bcpg.sig.RevocationReason; -import org.bouncycastle.bcpg.sig.SignatureCreationTime; -import org.bouncycastle.bcpg.sig.SignatureExpirationTime; -import org.bouncycastle.bcpg.sig.SignatureTarget; -import org.bouncycastle.bcpg.sig.SignerUserID; -import org.bouncycastle.bcpg.sig.TrustSignature; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.Feature; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SignatureSubpacket; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.OpenPgpV4Fingerprint; -import org.pgpainless.key.generation.type.KeyType; -import org.pgpainless.signature.SignatureUtils; - -/** - * Utility class to access signature subpackets from signatures. - * - * Since rfc4880 is not always clear about where a signature subpacket can be located (hashed/unhashed area), - * this class makes some educated guesses as to where the subpacket may be found when necessary. - */ -public final class SignatureSubpacketsUtil { - - private SignatureSubpacketsUtil() { - - } - - /** - * Return the issuer-fingerprint subpacket of the signature. - * Since this packet is self-authenticating, we expect it to be in the unhashed area, - * however as it cannot hurt we search for it in the hashed area first. - * - * @param signature signature - * @return issuer fingerprint or null - */ - public static @Nullable IssuerFingerprint getIssuerFingerprint(PGPSignature signature) { - return hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint); - } - - /** - * Return the {@link IssuerFingerprint} subpacket of the signature into a {@link org.pgpainless.key.OpenPgpFingerprint}. - * If no v4 issuer fingerprint is present in the signature, return null. - * - * @param signature signature - * @return v4 fingerprint of the issuer, or null - */ - public static @Nullable OpenPgpFingerprint getIssuerFingerprintAsOpenPgpFingerprint(PGPSignature signature) { - IssuerFingerprint subpacket = getIssuerFingerprint(signature); - if (subpacket == null) { - return null; - } - - OpenPgpFingerprint fingerprint = null; - if (subpacket.getKeyVersion() == 4) { - fingerprint = new OpenPgpV4Fingerprint(subpacket.getFingerprint()); - } - - return fingerprint; - } - - /** - * Return the issuer key-id subpacket of the signature. - * Since this packet is self-authenticating, we expect it to be in the unhashed area, - * however as it cannot hurt we search for it in the hashed area first. - * - * @param signature signature - * @return issuer key-id or null - */ - public static @Nullable IssuerKeyID getIssuerKeyId(PGPSignature signature) { - return hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId); - } - - /** - * Inspect the given signature's {@link IssuerKeyID} packet to determine the issuer key-id. - * If no such packet is present, return null. - * - * @param signature signature - * @return issuer key-id as {@link Long} - */ - public static @Nullable Long getIssuerKeyIdAsLong(PGPSignature signature) { - IssuerKeyID keyID = getIssuerKeyId(signature); - if (keyID == null) { - return null; - } - return keyID.getKeyID(); - } - - /** - * Return the revocation reason subpacket of the signature. - * Since this packet is rather important for revocations, we only search for it in the - * hashed area of the signature. - * - * @param signature signature - * @return revocation reason - */ - public static @Nullable RevocationReason getRevocationReason(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.revocationReason); - } - - /** - * Return the signature creation time subpacket. - * Since this packet is rather important, we only search for it in the hashed area - * of the signature. - * - * @param signature signature - * @return signature creation time subpacket - */ - public static @Nullable SignatureCreationTime getSignatureCreationTime(PGPSignature signature) { - if (signature.getVersion() == 3) { - return new SignatureCreationTime(false, signature.getCreationTime()); - } - return hashed(signature, SignatureSubpacket.signatureCreationTime); - } - - /** - * Return the signature expiration time subpacket of the signature. - * Since this packet is rather important, we only search for it in the hashed area of the signature. - * - * @param signature signature - * @return signature expiration time - */ - public static @Nullable SignatureExpirationTime getSignatureExpirationTime(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.signatureExpirationTime); - } - - /** - * Return the signatures' expiration time as a date. - * The expiration date is computed by adding the expiration time to the signature creation date. - * If the signature has no expiration time subpacket, or the expiration time is set to '0', this message returns null. - * - * @param signature signature - * @return expiration time as date - */ - public static @Nullable Date getSignatureExpirationTimeAsDate(PGPSignature signature) { - SignatureExpirationTime subpacket = getSignatureExpirationTime(signature); - if (subpacket == null) { - return null; - } - return SignatureUtils.datePlusSeconds(signature.getCreationTime(), subpacket.getTime()); - } - - /** - * Return the key expiration time subpacket of this signature. - * We only look for it in the hashed area of the signature. - * - * @param signature signature - * @return key expiration time - */ - public static @Nullable KeyExpirationTime getKeyExpirationTime(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.keyExpirationTime); - } - - /** - * Return the signatures key-expiration time as a date. - * The expiration date is computed by adding the signatures' key-expiration time to the signing keys - * creation date. - * If the signature does not have a key-expiration time subpacket, or its value is '0', this method returns null. - * - * @param signature self-signature carrying the key-expiration time subpacket - * @param signingKey signature creation key - * @return key expiration time as date - */ - public static @Nullable Date getKeyExpirationTimeAsDate(PGPSignature signature, PGPPublicKey signingKey) { - if (signature.getKeyID() != signingKey.getKeyID()) { - throw new IllegalArgumentException("Provided key (" + Long.toHexString(signingKey.getKeyID()) + ") did not create the signature (" + Long.toHexString(signature.getKeyID()) + ")"); - } - KeyExpirationTime subpacket = getKeyExpirationTime(signature); - if (subpacket == null) { - return null; - } - - return SignatureUtils.datePlusSeconds(signingKey.getCreationTime(), subpacket.getTime()); - } - - /** - * Calculate the duration in seconds until the key expires after creation. - * - * @param expirationDate new expiration date - * @param creationDate key creation time - * @return lifetime of the key in seconds - */ - public static long getKeyLifetimeInSeconds(@Nullable Date expirationDate, @Nonnull Date creationDate) { - long secondsToExpire = 0; // 0 means "no expiration" - if (expirationDate != null) { - if (creationDate.after(expirationDate)) { - throw new IllegalArgumentException("Key MUST NOT expire before being created. " + - "(creation: " + creationDate + ", expiration: " + expirationDate + ")"); - } - secondsToExpire = (expirationDate.getTime() - creationDate.getTime()) / 1000; - } - return secondsToExpire; - } - - /** - * Return the revocable subpacket of this signature. - * We only look for it in the hashed area of the signature. - * - * @param signature signature - * @return revocable subpacket - */ - public static @Nullable Revocable getRevocable(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.revocable); - } - - /** - * Return the symmetric algorithm preferences from the signatures hashed area. - * - * @param signature signature - * @return symm. algo. prefs - */ - public static @Nullable PreferredAlgorithms getPreferredSymmetricAlgorithms(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms); - } - - /** - * Return the preferred {@link SymmetricKeyAlgorithm SymmetricKeyAlgorithms} as present in the signature. - * If no preference is given with regard to symmetric encryption algorithms, return an empty set. - * - * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}). - * @param signature signature - * @return ordered set of symmetric key algorithm preferences - */ - public static @Nonnull Set parsePreferredSymmetricKeyAlgorithms(PGPSignature signature) { - Set algorithms = new LinkedHashSet<>(); - PreferredAlgorithms preferences = getPreferredSymmetricAlgorithms(signature); - if (preferences != null) { - for (int code : preferences.getPreferences()) { - SymmetricKeyAlgorithm algorithm = SymmetricKeyAlgorithm.fromId(code); - if (algorithm != null) { - algorithms.add(algorithm); - } - } - } - return algorithms; - } - - /** - * Return the hash algorithm preferences from the signatures hashed area. - * - * @param signature signature - * @return hash algo prefs - */ - public static @Nullable PreferredAlgorithms getPreferredHashAlgorithms(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.preferredHashAlgorithms); - } - - /** - * Return the preferred {@link HashAlgorithm HashAlgorithms} as present in the signature. - * If no preference is given with regard to hash algorithms, return an empty set. - * - * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}). - * @param signature signature - * @return ordered set of hash algorithm preferences - */ - public static @Nonnull Set parsePreferredHashAlgorithms(PGPSignature signature) { - Set algorithms = new LinkedHashSet<>(); - PreferredAlgorithms preferences = getPreferredHashAlgorithms(signature); - if (preferences != null) { - for (int code : preferences.getPreferences()) { - HashAlgorithm algorithm = HashAlgorithm.fromId(code); - if (algorithm != null) { - algorithms.add(algorithm); - } - } - } - return algorithms; - } - - /** - * Return the compression algorithm preferences from the signatures hashed area. - * - * @param signature signature - * @return compression algo prefs - */ - public static @Nullable PreferredAlgorithms getPreferredCompressionAlgorithms(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms); - } - - /** - * Return the preferred {@link CompressionAlgorithm CompressionAlgorithms} as present in the signature. - * If no preference is given with regard to compression algorithms, return an empty set. - * - * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}). - * @param signature signature - * @return ordered set of compression algorithm preferences - */ - public static @Nonnull Set parsePreferredCompressionAlgorithms(PGPSignature signature) { - Set algorithms = new LinkedHashSet<>(); - PreferredAlgorithms preferences = getPreferredCompressionAlgorithms(signature); - if (preferences != null) { - for (int code : preferences.getPreferences()) { - CompressionAlgorithm algorithm = CompressionAlgorithm.fromId(code); - if (algorithm != null) { - algorithms.add(algorithm); - } - } - } - return algorithms; - } - - /** - * Return the primary user-id subpacket from the signatures hashed area. - * - * @param signature signature - * @return primary user id - */ - public static @Nullable PrimaryUserID getPrimaryUserId(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.primaryUserId); - } - - /** - * Return the key flags subpacket from the signatures hashed area. - * - * @param signature signature - * @return key flags - */ - public static @Nullable KeyFlags getKeyFlags(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.keyFlags); - } - - /** - * Return a list of key flags carried by the signature. - * If the signature is null, or has no {@link KeyFlags} subpacket, return null. - * - * @param signature signature - * @return list of key flags - */ - public static @Nullable List parseKeyFlags(@Nullable PGPSignature signature) { - if (signature == null) { - return null; - } - KeyFlags keyFlags = getKeyFlags(signature); - if (keyFlags == null) { - return null; - } - return KeyFlag.fromBitmask(keyFlags.getFlags()); - } - - /** - * Return the features subpacket from the signatures hashed area. - * - * @param signature signature - * @return features subpacket - */ - public static @Nullable Features getFeatures(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.features); - } - - /** - * Parse out the features subpacket of a signature. - * If the signature has no features subpacket, return null. - * Otherwise, return the features as a feature set. - * - * @param signature signature - * @return features as set - */ - public static @Nullable Set parseFeatures(PGPSignature signature) { - Features features = getFeatures(signature); - if (features == null) { - return null; - } - return new LinkedHashSet<>(Feature.fromBitmask(features.getData()[0])); - } - - /** - * Return the signature target subpacket from the signature. - * We search for this subpacket in the hashed and unhashed area (in this order). - * - * @param signature signature - * @return signature target - */ - public static @Nullable SignatureTarget getSignatureTarget(PGPSignature signature) { - return hashedOrUnhashed(signature, SignatureSubpacket.signatureTarget); - } - - /** - * Return the notation data subpackets from the signatures hashed area. - * - * @param signature signature - * @return hashed notations - */ - public static @Nonnull List getHashedNotationData(PGPSignature signature) { - NotationData[] notations = signature.getHashedSubPackets().getNotationDataOccurrences(); - return Arrays.asList(notations); - } - - /** - * Return a list of all {@link NotationData} objects from the hashed area of the signature that have a - * notation name equal to the given notationName argument. - * - * @param signature signature - * @param notationName notation name - * @return list of matching notation data objects - */ - public static @Nonnull List getHashedNotationData(PGPSignature signature, String notationName) { - List allNotations = getHashedNotationData(signature); - List withName = new ArrayList<>(); - for (NotationData data : allNotations) { - if (data.getNotationName().equals(notationName)) { - withName.add(data); - } - } - return withName; - } - - /** - * Return the notation data subpackets from the signatures unhashed area. - * - * @param signature signature - * @return unhashed notations - */ - public static @Nonnull List getUnhashedNotationData(PGPSignature signature) { - NotationData[] notations = signature.getUnhashedSubPackets().getNotationDataOccurrences(); - return Arrays.asList(notations); - } - - /** - * Return a list of all {@link NotationData} objects from the unhashed area of the signature that have a - * notation name equal to the given notationName argument. - * - * @param signature signature - * @param notationName notation name - * @return list of matching notation data objects - */ - public static @Nonnull List getUnhashedNotationData(PGPSignature signature, String notationName) { - List allNotations = getUnhashedNotationData(signature); - List withName = new ArrayList<>(); - for (NotationData data : allNotations) { - if (data.getNotationName().equals(notationName)) { - withName.add(data); - } - } - return withName; - } - - /** - * Return the revocation key subpacket from the signatures hashed area. - * - * @param signature signature - * @return revocation key - */ - public static @Nullable RevocationKey getRevocationKey(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.revocationKey); - } - - /** - * Return the signers user-id from the hashed area of the signature. - * TODO: Can this subpacket also be found in the unhashed area? - * - * @param signature signature - * @return signers user-id - */ - public static @Nullable SignerUserID getSignerUserID(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.signerUserId); - } - - /** - * Return the intended recipients fingerprint subpackets from the hashed area of this signature. - * - * @param signature signature - * @return intended recipient fingerprint subpackets - */ - public static @Nonnull List getIntendedRecipientFingerprints(PGPSignature signature) { - org.bouncycastle.bcpg.SignatureSubpacket[] subpackets = signature.getHashedSubPackets().getSubpackets(SignatureSubpacket.intendedRecipientFingerprint.getCode()); - List intendedRecipients = new ArrayList<>(subpackets.length); - for (org.bouncycastle.bcpg.SignatureSubpacket subpacket : subpackets) { - intendedRecipients.add((IntendedRecipientFingerprint) subpacket); - } - return intendedRecipients; - } - - /** - * Return the embedded signature subpacket from the signatures hashed area. - * - * @param signature signature - * @return embedded signature - * - * @throws PGPException in case the embedded signatures cannot be parsed - */ - public static @Nullable PGPSignatureList getEmbeddedSignature(PGPSignature signature) throws PGPException { - PGPSignatureList hashed = signature.getHashedSubPackets().getEmbeddedSignatures(); - if (!hashed.isEmpty()) { - return hashed; - } - return signature.getUnhashedSubPackets().getEmbeddedSignatures(); - } - - /** - * Return the signatures exportable certification subpacket from the hashed area. - * - * @param signature signature - * @return exportable certification subpacket - */ - public static @Nullable Exportable getExportableCertification(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.exportableCertification); - } - - public static boolean isExportable(PGPSignature signature) { - Exportable exportable = getExportableCertification(signature); - return exportable == null || exportable.isExportable(); - } - - /** - * Return the trust signature packet from the signatures hashed area. - * - * @param signature signature - * @return trust signature subpacket - */ - public static @Nullable TrustSignature getTrustSignature(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.trustSignature); - } - - public static int getTrustDepthOr(PGPSignature signature, int defaultDepth) { - TrustSignature packet = getTrustSignature(signature); - if (packet != null) { - return packet.getDepth(); - } - return defaultDepth; - } - - public static int getTrustAmountOr(PGPSignature signature, int defaultAmount) { - TrustSignature packet = getTrustSignature(signature); - if (packet != null) { - return packet.getTrustAmount(); - } - return defaultAmount; - } - - /** - * Return all regular expression subpackets from the hashed area of the given signature. - * - * @param signature signature - * @return list of regular expressions - */ - public static List getRegularExpressions(PGPSignature signature) { - org.bouncycastle.bcpg.SignatureSubpacket[] subpackets = signature.getHashedSubPackets() - .getSubpackets(SignatureSubpacket.regularExpression.getCode()); - List regularExpressions = new ArrayList<>(subpackets.length); - for (org.bouncycastle.bcpg.SignatureSubpacket subpacket : subpackets) { - regularExpressions.add((RegularExpression) subpacket); - } - return regularExpressions; - } - - - /** - * Select a list of all signature subpackets of the given type, which are present in the hashed area of - * the given signature. - * - * @param signature signature - * @param type subpacket type - * @param

generic subpacket type - * @return list of subpackets from the hashed area - */ - private static @Nullable

P hashed(PGPSignature signature, SignatureSubpacket type) { - return getSignatureSubpacket(signature.getHashedSubPackets(), type); - } - - /** - * Select a list of all signature subpackets of the given type, which are present in the unhashed area of - * the given signature. - * - * @param signature signature - * @param type subpacket type - * @param

generic subpacket type - * @return list of subpackets from the unhashed area - */ - private static @Nullable

P unhashed(PGPSignature signature, SignatureSubpacket type) { - return getSignatureSubpacket(signature.getUnhashedSubPackets(), type); - } - - /** - * Select a list of all signature subpackets of the given type, which are present in either the hashed - * or the unhashed area of the given signature. - * - * @param signature signature - * @param type subpacket type - * @param

generic subpacket type - * @return list of subpackets from the hashed/unhashed area - */ - private static @Nullable

P hashedOrUnhashed(PGPSignature signature, SignatureSubpacket type) { - P hashedSubpacket = hashed(signature, type); - return hashedSubpacket != null ? hashedSubpacket : unhashed(signature, type); - } - - /** - * Return the last occurrence of a subpacket type in the given signature subpacket vector. - * - * @param vector subpacket vector (hashed/unhashed) - * @param type subpacket type - * @param

generic return type of the subpacket - * @return last occurrence of the subpacket in the vector - */ - public static @Nullable

P getSignatureSubpacket(PGPSignatureSubpacketVector vector, SignatureSubpacket type) { - if (vector == null) { - // Almost never happens, but may be caused by broken signatures. - return null; - } - org.bouncycastle.bcpg.SignatureSubpacket[] allPackets = vector.getSubpackets(type.getCode()); - if (allPackets.length == 0) { - return null; - } - - org.bouncycastle.bcpg.SignatureSubpacket last = allPackets[allPackets.length - 1]; - return (P) last; - } - - /** - * Make sure that the given key type can carry the given key flags. - * - * @param type key type - * @param flags key flags - */ - public static void assureKeyCanCarryFlags(KeyType type, KeyFlag... flags) { - final int mask = KeyFlag.toBitmask(flags); - - if (!type.canCertify() && KeyFlag.hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag CERTIFY_OTHER."); - } - - if (!type.canSign() && KeyFlag.hasKeyFlag(mask, KeyFlag.SIGN_DATA)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag SIGN_DATA."); - } - - if (!type.canEncryptCommunication() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag ENCRYPT_COMMS."); - } - - if (!type.canEncryptStorage() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag ENCRYPT_STORAGE."); - } - - if (!type.canAuthenticate() && KeyFlag.hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag AUTHENTICATION."); - } - } - - /** - * Make sure that a key of the given {@link PublicKeyAlgorithm} is able to carry the given key flags. - * - * @param algorithm key algorithm - * @param flags key flags - */ - public static void assureKeyCanCarryFlags(PublicKeyAlgorithm algorithm, KeyFlag... flags) { - final int mask = KeyFlag.toBitmask(flags); - - if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag CERTIFY_OTHER."); - } - - if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.SIGN_DATA)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag SIGN_DATA."); - } - - if (!algorithm.isEncryptionCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag ENCRYPT_COMMS."); - } - - if (!algorithm.isEncryptionCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag ENCRYPT_STORAGE."); - } - - if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag AUTHENTICATION."); - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt new file mode 100644 index 00000000..23fe716c --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt @@ -0,0 +1,575 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +import org.bouncycastle.bcpg.sig.* +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.PGPSignatureList +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector +import org.pgpainless.algorithm.* +import org.pgpainless.algorithm.KeyFlag.Companion.hasKeyFlag +import org.pgpainless.algorithm.KeyFlag.Companion.toBitmask +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.OpenPgpV4Fingerprint +import org.pgpainless.key.OpenPgpV5Fingerprint +import org.pgpainless.key.OpenPgpV6Fingerprint +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.key.util.KeyIdUtil +import org.pgpainless.signature.SignatureUtils +import java.util.* + +class SignatureSubpacketsUtil { + companion object { + + /** + * Return the issuer-fingerprint subpacket of the signature. + * Since this packet is self-authenticating, we expect it to be in the unhashed area, + * however as it cannot hurt we search for it in the hashed area first. + * + * @param signature signature + * @return issuer fingerprint or null + */ + @JvmStatic + fun getIssuerFingerprint(signature: PGPSignature): IssuerFingerprint? = + hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint) + + /** + * Return the [IssuerFingerprint] subpacket of the signature into a [org.pgpainless.key.OpenPgpFingerprint]. + * If no v4, v5 or v6 issuer fingerprint is present in the signature, return null. + * + * @param signature signature + * @return fingerprint of the issuer, or null + */ + @JvmStatic + fun getIssuerFingerprintAsOpenPgpFingerprint(signature: PGPSignature): OpenPgpFingerprint? { + val subpacket = getIssuerFingerprint(signature) ?: return null + return when(subpacket.keyVersion) { + 4 -> OpenPgpV4Fingerprint(subpacket.fingerprint) + 5 -> OpenPgpV5Fingerprint(subpacket.fingerprint) + 6 -> OpenPgpV6Fingerprint(subpacket.fingerprint) + else -> null + } + } + + @JvmStatic + fun getIssuerKeyId(signature: PGPSignature): IssuerKeyID? = + hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId) + + /** + * Inspect the given signature's [IssuerKeyID] packet to determine the issuer key-id. + * If no such packet is present, return null. + * + * @param signature signature + * @return issuer key-id as {@link Long} + */ + @JvmStatic + fun getIssuerKeyIdAsLong(signature: PGPSignature): Long? = + getIssuerKeyId(signature)?.keyID + + /** + * Return the revocation reason subpacket of the signature. + * Since this packet is rather important for revocations, we only search for it in the + * hashed area of the signature. + * + * @param signature signature + * @return revocation reason + */ + @JvmStatic + fun getRevocationReason(signature: PGPSignature): RevocationReason? = + hashed(signature, SignatureSubpacket.revocationReason) + + /** + * Return the signature creation time subpacket. + * Since this packet is rather important, we only search for it in the hashed area + * of the signature. + * + * @param signature signature + * @return signature creation time subpacket + */ + @JvmStatic + fun getSignatureCreationTime(signature: PGPSignature): SignatureCreationTime? = + if (signature.version == 3) SignatureCreationTime(false, signature.creationTime) + else hashed(signature, SignatureSubpacket.signatureCreationTime) + + /** + * Return the signature expiration time subpacket of the signature. + * Since this packet is rather important, we only search for it in the hashed area of the signature. + * + * @param signature signature + * @return signature expiration time + */ + @JvmStatic + fun getSignatureExpirationTime(signature: PGPSignature): SignatureExpirationTime? = + hashed(signature, SignatureSubpacket.signatureExpirationTime) + + /** + * Return the signatures' expiration time as a date. + * The expiration date is computed by adding the expiration time to the signature creation date. + * If the signature has no expiration time subpacket, or the expiration time is set to '0', this message returns null. + * + * @param signature signature + * @return expiration time as date + */ + @JvmStatic + fun getSignatureExpirationTimeAsDate(signature: PGPSignature): Date? = + getSignatureExpirationTime(signature)?.let { + SignatureUtils.datePlusSeconds(signature.creationTime, it.time) + } + + /** + * Return the key expiration time subpacket of this signature. + * We only look for it in the hashed area of the signature. + * + * @param signature signature + * @return key expiration time + */ + @JvmStatic + fun getKeyExpirationTime(signature: PGPSignature): KeyExpirationTime? = + hashed(signature, SignatureSubpacket.keyExpirationTime) + + /** + * Return the signatures key-expiration time as a date. + * The expiration date is computed by adding the signatures' key-expiration time to the signing keys + * creation date. + * If the signature does not have a key-expiration time subpacket, or its value is '0', this method returns null. + * + * @param signature self-signature carrying the key-expiration time subpacket + * @param signingKey signature creation key + * @return key expiration time as date + */ + @JvmStatic + fun getKeyExpirationTimeAsDate(signature: PGPSignature, signingKey: PGPPublicKey): Date? = + require(signature.keyID == signingKey.keyID) { + "Provided key (${KeyIdUtil.formatKeyId(signingKey.keyID)}) did not create the signature (${KeyIdUtil.formatKeyId(signature.keyID)})" + }.run { + getKeyExpirationTime(signature)?.let { + SignatureUtils.datePlusSeconds(signingKey.creationTime, it.time) + } + } + + /** + * Calculate the duration in seconds until the key expires after creation. + * + * @param expirationTime new expiration date + * @param creationTime key creation time + * @return lifetime of the key in seconds + */ + @JvmStatic + fun getKeyLifetimeInSeconds(creationTime: Date, expirationTime: Date?): Long = + expirationTime?.let { + require(creationTime <= it) { + "Key MUST NOT expire before being created.\n" + + "(creation: $creationTime, expiration: $it)" + }.run { + (it.time - creationTime.time) / 1000 + } + } ?: 0 // 0 means "no expiration" + + /** + * Return the revocable subpacket of this signature. + * We only look for it in the hashed area of the signature. + * + * @param signature signature + * @return revocable subpacket + */ + @JvmStatic + fun getRevocable(signature: PGPSignature): Revocable? = + hashed(signature, SignatureSubpacket.revocable) + + /** + * Return the symmetric algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return symm. algo. prefs + */ + @JvmStatic + fun getPreferredSymmetricAlgorithms(signature: PGPSignature): PreferredAlgorithms? = + hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms) + + /** + * Return the preferred [SymmetricKeyAlgorithms][SymmetricKeyAlgorithm] as present in the signature. + * If no preference is given with regard to symmetric encryption algorithms, return an empty set. + * + * In any case, the resulting set is ordered by occurrence. + * @param signature signature + * @return ordered set of symmetric key algorithm preferences + */ + @JvmStatic + fun parsePreferredSymmetricKeyAlgorithms(signature: PGPSignature): Set = + getPreferredSymmetricAlgorithms(signature) + ?.preferences + ?.map { SymmetricKeyAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() ?: setOf() + + /** + * Return the hash algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return hash algo prefs + */ + @JvmStatic + fun getPreferredHashAlgorithms(signature: PGPSignature): PreferredAlgorithms? = + hashed(signature, SignatureSubpacket.preferredHashAlgorithms) + + /** + * Return the preferred [HashAlgorithms][HashAlgorithm] as present in the signature. + * If no preference is given with regard to hash algorithms, return an empty set. + * + * In any case, the resulting set is ordered by occurrence. + * @param signature signature + * @return ordered set of hash algorithm preferences + */ + @JvmStatic + fun parsePreferredHashAlgorithms(signature: PGPSignature): Set = + getPreferredHashAlgorithms(signature) + ?.preferences + ?.map { HashAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() ?: setOf() + + /** + * Return the compression algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return compression algo prefs + */ + @JvmStatic + fun getPreferredCompressionAlgorithms(signature: PGPSignature): PreferredAlgorithms? = + hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms) + + /** + * Return the preferred [CompressionAlgorithms][CompressionAlgorithm] as present in the signature. + * If no preference is given with regard to compression algorithms, return an empty set. + * + * In any case, the resulting set is ordered by occurrence. + * @param signature signature + * @return ordered set of compression algorithm preferences + */ + @JvmStatic + fun parsePreferredCompressionAlgorithms(signature: PGPSignature): Set = + getPreferredCompressionAlgorithms(signature) + ?.preferences + ?.map { CompressionAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() ?: setOf() + + @JvmStatic + fun getPreferredAeadAlgorithms(signature: PGPSignature): PreferredAEADCiphersuites? = + hashed(signature, SignatureSubpacket.preferredAEADAlgorithms) + + /** + * Return the primary user-id subpacket from the signatures hashed area. + * + * @param signature signature + * @return primary user id + */ + @JvmStatic + fun getPrimaryUserId(signature: PGPSignature): PrimaryUserID? = + hashed(signature, SignatureSubpacket.primaryUserId) + + /** + * Return the key flags subpacket from the signatures hashed area. + * + * @param signature signature + * @return key flags + */ + @JvmStatic + fun getKeyFlags(signature: PGPSignature): KeyFlags? = + hashed(signature, SignatureSubpacket.keyFlags) + + /** + * Return a list of key flags carried by the signature. + * If the signature is null, or has no [KeyFlags] subpacket, return null. + * + * @param signature signature + * @return list of key flags + */ + @JvmStatic + fun parseKeyFlags(signature: PGPSignature?): List? = + signature?.let { sig -> + getKeyFlags(sig)?.let { + KeyFlag.fromBitmask(it.flags) + } + } + + /** + * Return the features subpacket from the signatures hashed area. + * + * @param signature signature + * @return features subpacket + */ + @JvmStatic + fun getFeatures(signature: PGPSignature): Features? = + hashed(signature, SignatureSubpacket.features) + + /** + * Parse out the features subpacket of a signature. + * If the signature has no features subpacket, return null. + * Otherwise, return the features as a feature set. + * + * @param signature signature + * @return features as set + */ + @JvmStatic + fun parseFeatures(signature: PGPSignature): Set? = + getFeatures(signature)?.let { + Feature.fromBitmask(it.features.toInt()).toSet() + } + + /** + * Return the signature target subpacket from the signature. + * We search for this subpacket in the hashed and unhashed area (in this order). + * + * @param signature signature + * @return signature target + */ + @JvmStatic + fun getSignatureTarget(signature: PGPSignature): SignatureTarget? = + hashedOrUnhashed(signature, SignatureSubpacket.signatureTarget) + + /** + * Return the notation data subpackets from the signatures hashed area. + * + * @param signature signature + * @return hashed notations + */ + @JvmStatic + fun getHashedNotationData(signature: PGPSignature): List = + signature.hashedSubPackets.notationDataOccurrences.toList() + + /** + * Return a list of all [NotationData] objects from the hashed area of the signature that have a + * notation name equal to the given notationName argument. + * + * @param signature signature + * @param notationName notation name + * @return list of matching notation data objects + */ + @JvmStatic + fun getHashedNotationData(signature: PGPSignature, notationName: String): List = + getHashedNotationData(signature) + .filter { it.notationName == notationName } + + /** + * Return the notation data subpackets from the signatures unhashed area. + * + * @param signature signature + * @return unhashed notations + */ + @JvmStatic + fun getUnhashedNotationData(signature: PGPSignature): List = + signature.unhashedSubPackets.notationDataOccurrences.toList() + + /** + * Return a list of all [NotationData] objects from the unhashed area of the signature that have a + * notation name equal to the given notationName argument. + * + * @param signature signature + * @param notationName notation name + * @return list of matching notation data objects + */ + @JvmStatic + fun getUnhashedNotationData(signature: PGPSignature, notationName: String) = + getUnhashedNotationData(signature) + .filter { it.notationName == notationName } + + /** + * Return the revocation key subpacket from the signatures hashed area. + * + * @param signature signature + * @return revocation key + */ + @JvmStatic + fun getRevocationKey(signature: PGPSignature): RevocationKey? = + hashed(signature, SignatureSubpacket.revocationKey) + + /** + * Return the signers user-id from the hashed area of the signature. + * TODO: Can this subpacket also be found in the unhashed area? + * + * @param signature signature + * @return signers user-id + */ + @JvmStatic + fun getSignerUserID(signature: PGPSignature): SignerUserID? = + hashed(signature, SignatureSubpacket.signerUserId) + + /** + * Return the intended recipients fingerprint subpackets from the hashed area of this signature. + * + * @param signature signature + * @return intended recipient fingerprint subpackets + */ + @JvmStatic + fun getIntendedRecipientFingerprints(signature: PGPSignature): List = + signature.hashedSubPackets.intendedRecipientFingerprints.toList() + + /** + * Return the embedded signature subpacket from the signatures hashed area or unhashed area. + * + * @param signature signature + * @return embedded signature + */ + @JvmStatic + fun getEmbeddedSignature(signature: PGPSignature): PGPSignatureList = + signature.hashedSubPackets.embeddedSignatures.let { + if (it.isEmpty) signature.unhashedSubPackets.embeddedSignatures + else it + } + + /** + * Return the signatures exportable certification subpacket from the hashed area. + * + * @param signature signature + * @return exportable certification subpacket + */ + @JvmStatic + fun getExportableCertification(signature: PGPSignature): Exportable? = + hashed(signature, SignatureSubpacket.exportableCertification) + + /** + * Return true, if the signature is not explicitly marked as non-exportable. + */ + @JvmStatic + fun isExportable(signature: PGPSignature): Boolean = + getExportableCertification(signature)?.isExportable ?: true + + /** + * Return the trust signature packet from the signatures hashed area. + * + * @param signature signature + * @return trust signature subpacket + */ + @JvmStatic + fun getTrustSignature(signature: PGPSignature): TrustSignature? = + hashed(signature, SignatureSubpacket.trustSignature) + + /** + * Return the trust depth set in the signatures [TrustSignature] packet, or [defaultDepth] if no such packet + * is found. + * + * @param signature signature + * @param defaultDepth default value that is returned if no trust signature packet is found + * @return depth or default depth + */ + @JvmStatic + fun getTrustDepthOr(signature: PGPSignature, defaultDepth: Int): Int = + getTrustSignature(signature)?.depth ?: defaultDepth + + /** + * Return the trust amount set in the signatures [TrustSignature] packet, or [defaultAmount] if no such packet + * is found. + * + * @param signature signature + * @param defaultAmount default value that is returned if no trust signature packet is found + * @return amount or default amount + */ + @JvmStatic + fun getTrustAmountOr(signature: PGPSignature, defaultAmount: Int): Int = + getTrustSignature(signature)?.trustAmount ?: defaultAmount + + /** + * Return all regular expression subpackets from the hashed area of the given signature. + * + * @param signature signature + * @return list of regular expressions + */ + @JvmStatic + fun getRegularExpressions(signature: PGPSignature): List = + signature.hashedSubPackets.regularExpressions.toList() + + /** + * Select a list of all signature subpackets of the given type, which are present in either the hashed + * or the unhashed area of the given signature. + * + * @param signature signature + * @param type subpacket type + * @param

generic subpacket type + * @return list of subpackets from the hashed/unhashed area + */ + @JvmStatic + fun

hashedOrUnhashed(signature: PGPSignature, type: SignatureSubpacket): P? { + return hashed(signature, type) ?: unhashed(signature, type) + } + + /** + * Select a list of all signature subpackets of the given type, which are present in the hashed area of + * the given signature. + * + * @param signature signature + * @param type subpacket type + * @param

generic subpacket type + * @return list of subpackets from the hashed area + */ + @JvmStatic + fun

hashed(signature: PGPSignature, type: SignatureSubpacket): P? { + return getSignatureSubpacket(signature.hashedSubPackets, type) + } + + /** + * Select a list of all signature subpackets of the given type, which are present in the unhashed area of + * the given signature. + * + * @param signature signature + * @param type subpacket type + * @param

generic subpacket type + * @return list of subpackets from the unhashed area + */ + @JvmStatic + fun

unhashed(signature: PGPSignature, type: SignatureSubpacket): P? { + return getSignatureSubpacket(signature.unhashedSubPackets, type) + } + + /** + * Return the last occurrence of a subpacket type in the given signature subpacket vector. + * + * @param vector subpacket vector (hashed/unhashed) + * @param type subpacket type + * @param

generic return type of the subpacket + * @return last occurrence of the subpacket in the vector + */ + @JvmStatic + fun

getSignatureSubpacket(vector: PGPSignatureSubpacketVector?, type: SignatureSubpacket): P? { + val allPackets = vector?.getSubpackets(type.code) ?: return null + return if (allPackets.isEmpty()) + null + else + @Suppress("UNCHECKED_CAST") + allPackets.last() as P + } + + @JvmStatic + fun assureKeyCanCarryFlags(type: KeyType, vararg flags: KeyFlag) { + assureKeyCanCarryFlags(type.algorithm, *flags) + } + + @JvmStatic + fun assureKeyCanCarryFlags(algorithm: PublicKeyAlgorithm, vararg flags: KeyFlag) { + val mask = toBitmask(*flags) + + if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag CERTIFY_OTHER.") + } + + if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.SIGN_DATA)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag SIGN_DATA.") + } + + if (!algorithm.isEncryptionCapable() && hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag ENCRYPT_COMMS.") + } + + if (!algorithm.isEncryptionCapable() && hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag ENCRYPT_STORAGE.") + } + + if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag AUTHENTICATION.") + } + } + } +} \ No newline at end of file From 4a19e6ca2003efc62776cbe56cb851c128268747 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 13:50:49 +0200 Subject: [PATCH 044/155] WIP: Kotlin conversion: ConsumerOptions --- .../ConsumerOptions.java | 508 ------------------ .../ConsumerOptions.kt | 394 ++++++++++++++ .../OpenPgpMessageInputStream.kt | 42 +- .../pgpainless/signature/SignatureUtils.kt | 2 + .../MissingPassphraseForDecryptionTest.java | 24 - 5 files changed, 417 insertions(+), 553 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java deleted file mode 100644 index d0d9230b..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java +++ /dev/null @@ -1,508 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy; -import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.signature.SignatureUtils; -import org.pgpainless.util.Passphrase; -import org.pgpainless.util.SessionKey; - -/** - * Options for decryption and signature verification. - */ -public class ConsumerOptions { - - private boolean ignoreMDCErrors = false; - private boolean forceNonOpenPgpData = false; - - private Date verifyNotBefore = null; - private Date verifyNotAfter = new Date(); - - private final CertificateSource certificates = new CertificateSource(); - private final Set detachedSignatures = new HashSet<>(); - private MissingPublicKeyCallback missingCertificateCallback = null; - - // Session key for decryption without passphrase/key - private SessionKey sessionKey = null; - private final Map customPublicKeyDataDecryptorFactories = - new HashMap<>(); - - private final Map decryptionKeys = new HashMap<>(); - private final Set decryptionPassphrases = new HashSet<>(); - private MissingKeyPassphraseStrategy missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE; - - private MultiPassStrategy multiPassStrategy = new InMemoryMultiPassStrategy(); - - public static ConsumerOptions get() { - return new ConsumerOptions(); - } - - /** - * Consider signatures on the message made before the given timestamp invalid. - * Null means no limitation. - * - * @param timestamp timestamp - * @return options - */ - public ConsumerOptions verifyNotBefore(Date timestamp) { - this.verifyNotBefore = timestamp; - return this; - } - - /** - * Return the earliest creation date on which signatures on the message are considered valid. - * Signatures made earlier than this date are considered invalid. - * - * @return earliest allowed signature creation date or null - */ - public @Nullable Date getVerifyNotBefore() { - return verifyNotBefore; - } - - /** - * Consider signatures on the message made after the given timestamp invalid. - * Null means no limitation. - * - * @param timestamp timestamp - * @return options - */ - public ConsumerOptions verifyNotAfter(Date timestamp) { - this.verifyNotAfter = timestamp; - return this; - } - - /** - * Return the latest possible creation date on which signatures made on the message are considered valid. - * Signatures made later than this date are considered invalid. - * - * @return Latest possible creation date or null. - */ - public Date getVerifyNotAfter() { - return verifyNotAfter; - } - - /** - * Add a certificate (public key ring) for signature verification. - * - * @param verificationCert certificate for signature verification - * @return options - */ - public ConsumerOptions addVerificationCert(PGPPublicKeyRing verificationCert) { - this.certificates.addCertificate(verificationCert); - return this; - } - - /** - * Add a set of certificates (public key rings) for signature verification. - * - * @param verificationCerts certificates for signature verification - * @return options - */ - public ConsumerOptions addVerificationCerts(PGPPublicKeyRingCollection verificationCerts) { - for (PGPPublicKeyRing certificate : verificationCerts) { - addVerificationCert(certificate); - } - return this; - } - - /** - * Add some detached signatures from the given {@link InputStream} for verification. - * - * @param signatureInputStream input stream of detached signatures - * @return options - * - * @throws IOException in case of an IO error - * @throws PGPException in case of an OpenPGP error - */ - public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream) - throws IOException, PGPException { - List signatures = SignatureUtils.readSignatures(signatureInputStream); - return addVerificationOfDetachedSignatures(signatures); - } - - /** - * Add some detached signatures for verification. - * - * @param detachedSignatures detached signatures - * @return options - */ - public ConsumerOptions addVerificationOfDetachedSignatures(List detachedSignatures) { - for (PGPSignature signature : detachedSignatures) { - addVerificationOfDetachedSignature(signature); - } - return this; - } - - /** - * Add a detached signature for the signature verification process. - * - * @param detachedSignature detached signature - * @return options - */ - public ConsumerOptions addVerificationOfDetachedSignature(PGPSignature detachedSignature) { - detachedSignatures.add(detachedSignature); - return this; - } - - /** - * Set a callback that's used when a certificate (public key) is missing for signature verification. - * - * @param callback callback - * @return options - */ - public ConsumerOptions setMissingCertificateCallback(MissingPublicKeyCallback callback) { - this.missingCertificateCallback = callback; - return this; - } - - - /** - * Attempt decryption using a session key. - * - * Note: PGPainless does not yet support decryption with session keys. - * - * @see RFC4880 on Session Keys - * - * @param sessionKey session key - * @return options - */ - public ConsumerOptions setSessionKey(@Nonnull SessionKey sessionKey) { - this.sessionKey = sessionKey; - return this; - } - - /** - * Return the session key. - * - * @return session key or null - */ - public @Nullable SessionKey getSessionKey() { - return sessionKey; - } - - /** - * Add a key for message decryption. - * The key is expected to be unencrypted. - * - * @param key unencrypted key - * @return options - */ - public ConsumerOptions addDecryptionKey(@Nonnull PGPSecretKeyRing key) { - return addDecryptionKey(key, SecretKeyRingProtector.unprotectedKeys()); - } - - /** - * Add a key for message decryption. If the key is encrypted, the {@link SecretKeyRingProtector} - * is used to decrypt it when needed. - * - * @param key key - * @param keyRingProtector protector for the secret key - * @return options - */ - public ConsumerOptions addDecryptionKey(@Nonnull PGPSecretKeyRing key, - @Nonnull SecretKeyRingProtector keyRingProtector) { - decryptionKeys.put(key, keyRingProtector); - return this; - } - - /** - * Add the keys in the provided key collection for message decryption. - * - * @param keys key collection - * @param keyRingProtector protector for encrypted secret keys - * @return options - */ - public ConsumerOptions addDecryptionKeys(@Nonnull PGPSecretKeyRingCollection keys, - @Nonnull SecretKeyRingProtector keyRingProtector) { - for (PGPSecretKeyRing key : keys) { - addDecryptionKey(key, keyRingProtector); - } - return this; - } - - /** - * Add a passphrase for message decryption. - * This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase. - * - * @see Symmetrically Encrypted Data Packet - * - * @param passphrase passphrase - * @return options - */ - public ConsumerOptions addDecryptionPassphrase(@Nonnull Passphrase passphrase) { - decryptionPassphrases.add(passphrase); - return this; - } - - /** - * Add a custom {@link PublicKeyDataDecryptorFactory} which enable decryption of messages, e.g. using - * hardware-backed secret keys. - * (See e.g. {@link org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory}). - * - * @param factory decryptor factory - * @return options - */ - public ConsumerOptions addCustomDecryptorFactory(@Nonnull CustomPublicKeyDataDecryptorFactory factory) { - this.customPublicKeyDataDecryptorFactories.put(factory.getSubkeyIdentifier(), factory); - return this; - } - - /** - * Return the custom {@link PublicKeyDataDecryptorFactory PublicKeyDataDecryptorFactories} that were - * set by the user. - * These factories can be used to decrypt session keys using a custom logic. - * - * @return custom decryptor factories - */ - Map getCustomDecryptorFactories() { - return new HashMap<>(customPublicKeyDataDecryptorFactories); - } - - /** - * Return the set of available decryption keys. - * - * @return decryption keys - */ - public @Nonnull Set getDecryptionKeys() { - return Collections.unmodifiableSet(decryptionKeys.keySet()); - } - - /** - * Return the set of available message decryption passphrases. - * - * @return decryption passphrases - */ - public @Nonnull Set getDecryptionPassphrases() { - return Collections.unmodifiableSet(decryptionPassphrases); - } - - /** - * Return the explicitly set verification certificates. - * - * @deprecated use {@link #getCertificateSource()} instead. - * @return verification certs - */ - @Deprecated - public @Nonnull Set getCertificates() { - return certificates.getExplicitCertificates(); - } - - /** - * Return an object holding available certificates for signature verification. - * - * @return certificate source - */ - public @Nonnull CertificateSource getCertificateSource() { - return certificates; - } - - /** - * Return the callback that gets called when a certificate for signature verification is missing. - * This method might return

null
if the users hasn't set a callback. - * - * @return missing public key callback - */ - public @Nullable MissingPublicKeyCallback getMissingCertificateCallback() { - return missingCertificateCallback; - } - - /** - * Return the {@link SecretKeyRingProtector} for the given {@link PGPSecretKeyRing}. - * - * @param decryptionKeyRing secret key - * @return protector for that particular secret key - */ - public @Nonnull SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) { - return decryptionKeys.get(decryptionKeyRing); - } - - /** - * Return the set of detached signatures the user provided. - * - * @return detached signatures - */ - public @Nonnull Set getDetachedSignatures() { - return Collections.unmodifiableSet(detachedSignatures); - } - - /** - * By default, PGPainless will require encrypted messages to make use of SEIP data packets. - * Those are Symmetrically Encrypted Integrity Protected Data packets. - * Symmetrically Encrypted Data Packets without integrity protection are rejected by default. - * Furthermore, PGPainless will throw an exception if verification of the MDC error detection - * code of the SEIP packet fails. - * - * Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an - * attack or data corruption. - * - * This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data - * without integrity protection. - * If the flag
ignoreMDCErrors
is set to true, PGPainless will - *
    - *
  • not throw exceptions for SEIP packets with tampered ciphertext
  • - *
  • not throw exceptions for SEIP packets with tampered MDC
  • - *
  • not throw exceptions for MDCs with bad CTB
  • - *
  • not throw exceptions for MDCs with bad length
  • - *
- * - * It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC - * - * @see - * Sym. Encrypted Integrity Protected Data Packet - * @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise. - * @return options - */ - @Deprecated - public ConsumerOptions setIgnoreMDCErrors(boolean ignoreMDCErrors) { - this.ignoreMDCErrors = ignoreMDCErrors; - return this; - } - - /** - * Return true, if PGPainless is ignoring MDC errors. - * - * @return ignore mdc errors - */ - boolean isIgnoreMDCErrors() { - return ignoreMDCErrors; - } - - /** - * Force PGPainless to handle the data provided by the {@link InputStream} as non-OpenPGP data. - * This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data. - * - * @return options - */ - public ConsumerOptions forceNonOpenPgpData() { - this.forceNonOpenPgpData = true; - return this; - } - - /** - * Return true, if the ciphertext should be handled as binary non-OpenPGP data. - * - * @return true if non-OpenPGP data is forced - */ - boolean isForceNonOpenPgpData() { - return forceNonOpenPgpData; - } - - /** - * Specify the {@link MissingKeyPassphraseStrategy}. - * This strategy defines, how missing passphrases for unlocking secret keys are handled. - * In interactive mode ({@link MissingKeyPassphraseStrategy#INTERACTIVE}) PGPainless will try to obtain missing - * passphrases for secret keys via the {@link SecretKeyRingProtector SecretKeyRingProtectors} - * {@link org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider} callback. - * - * In non-interactice mode ({@link MissingKeyPassphraseStrategy#THROW_EXCEPTION}, PGPainless will instead - * throw a {@link org.pgpainless.exception.MissingPassphraseException} containing the ids of all keys for which - * there are missing passphrases. - * - * @param strategy strategy - * @return options - */ - public ConsumerOptions setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy strategy) { - this.missingKeyPassphraseStrategy = strategy; - return this; - } - - /** - * Return the currently configured {@link MissingKeyPassphraseStrategy}. - * - * @return missing key passphrase strategy - */ - MissingKeyPassphraseStrategy getMissingKeyPassphraseStrategy() { - return missingKeyPassphraseStrategy; - } - - /** - * Set a custom multi-pass strategy for processing cleartext-signed messages. - * Uses {@link InMemoryMultiPassStrategy} by default. - * - * @param multiPassStrategy multi-pass caching strategy - * @return builder - */ - public ConsumerOptions setMultiPassStrategy(@Nonnull MultiPassStrategy multiPassStrategy) { - this.multiPassStrategy = multiPassStrategy; - return this; - } - - /** - * Return the currently configured {@link MultiPassStrategy}. - * Defaults to {@link InMemoryMultiPassStrategy}. - * - * @return multi-pass strategy - */ - public MultiPassStrategy getMultiPassStrategy() { - return multiPassStrategy; - } - - /** - * Source for OpenPGP certificates. - * When verifying signatures on a message, this object holds available signer certificates. - */ - public static class CertificateSource { - - private Set explicitCertificates = new HashSet<>(); - - /** - * Add a certificate as verification cert explicitly. - * - * @param certificate certificate - */ - public void addCertificate(PGPPublicKeyRing certificate) { - this.explicitCertificates.add(certificate); - } - - /** - * Return the set of explicitly set verification certificates. - * @return explicitly set verification certs - */ - public Set getExplicitCertificates() { - return Collections.unmodifiableSet(explicitCertificates); - } - - /** - * Return a certificate which contains a subkey with the given keyId. - * This method first checks all explicitly set verification certs and if no cert is found it consults - * the certificate stores. - * - * @param keyId key id - * @return certificate - */ - public PGPPublicKeyRing getCertificate(long keyId) { - - for (PGPPublicKeyRing cert : explicitCertificates) { - if (cert.getPublicKey(keyId) != null) { - return cert; - } - } - - return null; - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt new file mode 100644 index 00000000..5bbe098e --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt @@ -0,0 +1,394 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory +import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy +import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.signature.SignatureUtils +import org.pgpainless.util.Passphrase +import org.pgpainless.util.SessionKey +import java.io.IOException +import java.io.InputStream +import java.util.* + +/** + * Options for decryption and signature verification. + */ +class ConsumerOptions { + + private var ignoreMDCErrors = false + private var forceNonOpenPgpData = false + private var verifyNotBefore: Date? = null + private var verifyNotAfter: Date? = Date() + + private val certificates = CertificateSource() + private val detachedSignatures = mutableSetOf() + private var missingCertificateCallback: MissingPublicKeyCallback? = null + + private var sessionKey: SessionKey? = null + private val customDecryptorFactories = mutableMapOf() + private val decryptionKeys = mutableMapOf() + private val decryptionPassphrases = mutableSetOf() + private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE + private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy() + + /** + * Consider signatures on the message made before the given timestamp invalid. + * Null means no limitation. + * + * @param timestamp timestamp + * @return options + */ + fun verifyNotBefore(timestamp: Date?): ConsumerOptions = apply { + this.verifyNotBefore = timestamp + } + + fun getVerifyNotBefore() = verifyNotBefore + + /** + * Consider signatures on the message made after the given timestamp invalid. + * Null means no limitation. + * + * @param timestamp timestamp + * @return options + */ + fun verifyNotAfter(timestamp: Date?): ConsumerOptions = apply { + this.verifyNotAfter = timestamp + } + + fun getVerifyNotAfter() = verifyNotAfter + + /** + * Add a certificate (public key ring) for signature verification. + * + * @param verificationCert certificate for signature verification + * @return options + */ + fun addVerificationCert(verificationCert: PGPPublicKeyRing): ConsumerOptions = apply { + this.certificates.addCertificate(verificationCert) + } + + /** + * Add a set of certificates (public key rings) for signature verification. + * + * @param verificationCerts certificates for signature verification + * @return options + */ + fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions = apply { + for (cert in verificationCerts) { + addVerificationCert(cert) + } + } + + /** + * Add some detached signatures from the given [InputStream] for verification. + * + * @param signatureInputStream input stream of detached signatures + * @return options + * + * @throws IOException in case of an IO error + * @throws PGPException in case of an OpenPGP error + */ + @Throws(IOException::class, PGPException::class) + fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions = apply { + val signatures = SignatureUtils.readSignatures(signatureInputStream) + addVerificationOfDetachedSignatures(signatures) + } + + /** + * Add some detached signatures for verification. + * + * @param detachedSignatures detached signatures + * @return options + */ + fun addVerificationOfDetachedSignatures(detachedSignatures: List): ConsumerOptions = apply { + for (signature in detachedSignatures) { + addVerificationOfDetachedSignature(signature) + } + } + + /** + * Add a detached signature for the signature verification process. + * + * @param detachedSignature detached signature + * @return options + */ + fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions = apply { + detachedSignatures.add(detachedSignature) + } + + fun getDetachedSignatures() = detachedSignatures.toList() + + /** + * Set a callback that's used when a certificate (public key) is missing for signature verification. + * + * @param callback callback + * @return options + */ + fun setMissingCertificateCallback(callback: MissingPublicKeyCallback): ConsumerOptions = apply { + this.missingCertificateCallback = callback + } + + /** + * Attempt decryption using a session key. + * + * Note: PGPainless does not yet support decryption with session keys. + * + * See [RFC4880 on Session Keys](https://datatracker.ietf.org/doc/html/rfc4880#section-2.1) + * + * @param sessionKey session key + * @return options + */ + fun setSessionKey(sessionKey: SessionKey) = apply { this.sessionKey = sessionKey } + + fun getSessionKey() = sessionKey + + /** + * Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] + * is used to decrypt it when needed. + * + * @param key key + * @param keyRingProtector protector for the secret key + * @return options + */ + @JvmOverloads + fun addDecryptionKey(key: PGPSecretKeyRing, + protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply { + decryptionKeys[key] = protector + } + + /** + * Add the keys in the provided key collection for message decryption. + * + * @param keys key collection + * @param keyRingProtector protector for encrypted secret keys + * @return options + */ + @JvmOverloads + fun addDecryptionKeys(keys: PGPSecretKeyRingCollection, + protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply { + for (key in keys) { + addDecryptionKey(key, protector) + } + } + + /** + * Add a passphrase for message decryption. + * This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase. + * + * See [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7) + * + * @param passphrase passphrase + * @return options + */ + fun addDecryptionPassphrase(passphrase: Passphrase) = apply { + decryptionPassphrases.add(passphrase) + } + + /** + * Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using + * hardware-backed secret keys. + * (See e.g. [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]). + * + * @param factory decryptor factory + * @return options + */ + fun addCustomDecryptorFactory(factory: CustomPublicKeyDataDecryptorFactory) = apply { + customDecryptorFactories[factory.subkeyIdentifier] = factory + } + + /** + * Return the custom [PublicKeyDataDecryptorFactory] that were + * set by the user. + * These factories can be used to decrypt session keys using a custom logic. + * + * @return custom decryptor factories + */ + fun getCustomDecryptorFactories() = customDecryptorFactories.toMap() + + /** + * Return the set of available decryption keys. + * + * @return decryption keys + */ + fun getDecryptionKeys() = decryptionKeys.keys.toSet() + + /** + * Return the set of available message decryption passphrases. + * + * @return decryption passphrases + */ + fun getDecryptionPassphrases() = decryptionPassphrases.toSet() + + /** + * Return an object holding available certificates for signature verification. + * + * @return certificate source + */ + fun getCertificateSource() = certificates + + /** + * Return the callback that gets called when a certificate for signature verification is missing. + * This method might return `null` if the users hasn't set a callback. + * + * @return missing public key callback + */ + fun getMissingCertificateCallback() = missingCertificateCallback + + /** + * Return the [SecretKeyRingProtector] for the given [PGPSecretKeyRing]. + * + * @param decryptionKeyRing secret key + * @return protector for that particular secret key + */ + fun getSecretKeyProtector(decryptionKeyRing: PGPSecretKeyRing): SecretKeyRingProtector? { + return decryptionKeys[decryptionKeyRing] + } + + /** + * By default, PGPainless will require encrypted messages to make use of SEIP data packets. + * Those are Symmetrically Encrypted Integrity Protected Data packets. + * Symmetrically Encrypted Data Packets without integrity protection are rejected by default. + * Furthermore, PGPainless will throw an exception if verification of the MDC error detection + * code of the SEIP packet fails. + * + * Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an + * attack or data corruption. + * + * This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data + * without integrity protection. + * If the flag
ignoreMDCErrors
is set to true, PGPainless will + * + * * not throw exceptions for SEIP packets with tampered ciphertext + * * not throw exceptions for SEIP packets with tampered MDC + * * not throw exceptions for MDCs with bad CTB + * * not throw exceptions for MDCs with bad length + * + * + * It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC + * + * See [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13) + * + * @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise. + * @return options + */ + @Deprecated("Ignoring non-integrity-protected packets is discouraged.") + fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply { this.ignoreMDCErrors = ignoreMDCErrors } + + fun isIgnoreMDCErrors() = ignoreMDCErrors + + /** + * Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. + * This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data. + * + * @return options + */ + fun forceNonOpenPgpData(): ConsumerOptions = apply { + this.forceNonOpenPgpData = true + } + + /** + * Return true, if the ciphertext should be handled as binary non-OpenPGP data. + * + * @return true if non-OpenPGP data is forced + */ + fun isForceNonOpenPgpData() = forceNonOpenPgpData + + /** + * Specify the [MissingKeyPassphraseStrategy]. + * This strategy defines, how missing passphrases for unlocking secret keys are handled. + * In interactive mode ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing + * passphrases for secret keys via the [SecretKeyRingProtector] + * [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider] callback. + * + * In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will instead + * throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of all keys for which + * there are missing passphrases. + * + * @param strategy strategy + * @return options + */ + fun setMissingKeyPassphraseStrategy(strategy: MissingKeyPassphraseStrategy): ConsumerOptions { + this.missingKeyPassphraseStrategy = strategy + return this + } + + /** + * Return the currently configured [MissingKeyPassphraseStrategy]. + * + * @return missing key passphrase strategy + */ + fun getMissingKeyPassphraseStrategy(): MissingKeyPassphraseStrategy { + return missingKeyPassphraseStrategy + } + + /** + * Set a custom multi-pass strategy for processing cleartext-signed messages. + * Uses [InMemoryMultiPassStrategy] by default. + * + * @param multiPassStrategy multi-pass caching strategy + * @return builder + */ + fun setMultiPassStrategy(multiPassStrategy: MultiPassStrategy): ConsumerOptions { + this.multiPassStrategy = multiPassStrategy + return this + } + + /** + * Return the currently configured [MultiPassStrategy]. + * Defaults to [InMemoryMultiPassStrategy]. + * + * @return multi-pass strategy + */ + fun getMultiPassStrategy(): MultiPassStrategy { + return multiPassStrategy + } + + /** + * Source for OpenPGP certificates. + * When verifying signatures on a message, this object holds available signer certificates. + */ + class CertificateSource { + private val explicitCertificates: MutableSet = mutableSetOf() + + /** + * Add a certificate as verification cert explicitly. + * + * @param certificate certificate + */ + fun addCertificate(certificate: PGPPublicKeyRing) { + explicitCertificates.add(certificate) + } + + /** + * Return the set of explicitly set verification certificates. + * @return explicitly set verification certs + */ + fun getExplicitCertificates(): Set { + return explicitCertificates.toSet() + } + + /** + * Return a certificate which contains a subkey with the given keyId. + * This method first checks all explicitly set verification certs and if no cert is found it consults + * the certificate stores. + * + * @param keyId key id + * @return certificate + */ + fun getCertificate(keyId: Long): PGPPublicKeyRing? { + return explicitCertificates.firstOrNull { it.getPublicKey(keyId) != null } + } + } + + companion object { + @JvmStatic + fun get() = ConsumerOptions() + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 25b29100..49a6edf5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -53,7 +53,7 @@ class OpenPgpMessageInputStream( // Add detached signatures only on the outermost OpenPgpMessageInputStream if (metadata is Message) { - signatures.addDetachedSignatures(options.detachedSignatures) + signatures.addDetachedSignatures(options.getDetachedSignatures()) } when(type) { @@ -67,7 +67,7 @@ class OpenPgpMessageInputStream( } Type.cleartext_signed -> { - val multiPassStrategy = options.multiPassStrategy + val multiPassStrategy = options.getMultiPassStrategy() val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage( inputStream, multiPassStrategy.messageOutputStream) @@ -75,7 +75,7 @@ class OpenPgpMessageInputStream( signatures.addDetachedSignature(signature) } - options.forceNonOpenPgpData() + options.isForceNonOpenPgpData() nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures) } @@ -212,7 +212,7 @@ class OpenPgpMessageInputStream( val encDataList = packetInputStream!!.readEncryptedDataList() if (!encDataList.isIntegrityProtected) { LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.") - if (!options.isIgnoreMDCErrors) { + if (!options.isIgnoreMDCErrors()) { throw MessageNotIntegrityProtectedException() } } @@ -223,7 +223,7 @@ class OpenPgpMessageInputStream( " have an anonymous recipient.") // try custom decryptor factories - for ((key, decryptorFactory) in options.customDecryptorFactories) { + for ((key, decryptorFactory) in options.getCustomDecryptorFactories()) { LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.") esks.pkesks.filter { // find matching PKESK @@ -237,8 +237,8 @@ class OpenPgpMessageInputStream( } // try provided session key - if (options.sessionKey != null) { - val sk = options.sessionKey!! + if (options.getSessionKey() != null) { + val sk = options.getSessionKey()!! LOGGER.debug("Attempt decryption with provided session key.") throwIfUnacceptable(sk.algorithm) @@ -260,7 +260,7 @@ class OpenPgpMessageInputStream( } // try passwords - for (passphrase in options.decryptionPassphrases) { + for (passphrase in options.getDecryptionPassphrases()) { for (skesk in esks.skesks) { LOGGER.debug("Attempt decryption with provided passphrase") val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm) @@ -295,7 +295,7 @@ class OpenPgpMessageInputStream( } LOGGER.debug("Attempt decryption using secret key $decryptionKeyId") - val protector = options.getSecretKeyProtector(decryptionKeys) + val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue if (!protector.hasPassphraseFor(keyId)) { LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") postponedDueToMissingPassphrase.add(secretKey to pkesk) @@ -317,7 +317,7 @@ class OpenPgpMessageInputStream( } LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeyId.") - val protector = options.getSecretKeyProtector(decryptionKeys) + val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue if (!protector.hasPassphraseFor(secretKey.keyID)) { LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") @@ -332,7 +332,7 @@ class OpenPgpMessageInputStream( } } - if (options.missingKeyPassphraseStrategy == MissingKeyPassphraseStrategy.THROW_EXCEPTION) { + if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) { // Non-interactive mode: Throw an exception with all locked decryption keys postponedDueToMissingPassphrase.map { SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) @@ -340,7 +340,7 @@ class OpenPgpMessageInputStream( if (it.isNotEmpty()) throw MissingPassphraseException(it.toSet()) } - } else if (options.missingKeyPassphraseStrategy == MissingKeyPassphraseStrategy.INTERACTIVE) { + } else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) { for ((secretKey, pkesk) in postponedDueToMissingPassphrase) { val keyId = secretKey.keyID val decryptionKeys = getDecryptionKey(keyId)!! @@ -532,7 +532,7 @@ class OpenPgpMessageInputStream( return MessageMetadata((metadata as Message)) } - private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.decryptionKeys.firstOrNull { + private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull { it.any { k -> k.keyID == keyId }.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { @@ -543,7 +543,7 @@ class OpenPgpMessageInputStream( private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List> { val algorithm = pkesk.algorithm val candidates = mutableListOf>() - options.decryptionKeys.forEach { + options.getDecryptionKeys().forEach { val info = PGPainless.inspectKeyRing(it) for (key in info.decryptionSubkeys) { if (key.algorithm == algorithm && info.isSecretKeyAvailable(key.keyID)) { @@ -679,7 +679,7 @@ class OpenPgpMessageInputStream( SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID)) try { - SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) .verify(signature) CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy) LOGGER.debug("Acceptable signature by key ${verification.signingKey}") @@ -713,13 +713,13 @@ class OpenPgpMessageInputStream( } fun findCertificate(keyId: Long): PGPPublicKeyRing? { - val cert = options.certificateSource.getCertificate(keyId) + val cert = options.getCertificateSource().getCertificate(keyId) if (cert != null) { return cert } - if (options.missingCertificateCallback != null) { - return options.missingCertificateCallback!!.onMissingPublicKeyEncountered(keyId) + if (options.getMissingCertificateCallback() != null) { + return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(keyId) } return null // TODO: Missing cert for sig } @@ -772,7 +772,7 @@ class OpenPgpMessageInputStream( for (detached in detachedSignatures) { val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier) try { - SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) .verify(detached.signature) CertificateValidator.validateCertificateAndVerifyInitializedSignature( detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy) @@ -787,7 +787,7 @@ class OpenPgpMessageInputStream( for (prepended in prependedSignatures) { val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier) try { - SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter) + SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) .verify(prepended.signature) CertificateValidator.validateCertificateAndVerifyInitializedSignature( prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy) @@ -877,7 +877,7 @@ class OpenPgpMessageInputStream( val openPgpIn = OpenPgpInputStream(inputStream) openPgpIn.reset() - if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData) { + if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) { return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy) } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt index b9b80f8f..4ca30e86 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt @@ -14,6 +14,7 @@ import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.util.RevocationAttributes.Reason import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.util.ArmorUtils +import java.io.IOException import java.io.InputStream import java.util.* @@ -127,6 +128,7 @@ class SignatureUtils { } @JvmStatic + @Throws(IOException::class, PGPException::class) fun readSignatures(inputStream: InputStream): List { return readSignatures(inputStream, MAX_ITERATIONS) } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java index f7d36aba..42562713 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java @@ -7,7 +7,6 @@ package org.pgpainless.decryption_verification; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -59,29 +58,6 @@ public class MissingPassphraseForDecryptionTest { message = out.toByteArray(); } - @Test - public void invalidPostponedKeysStrategyTest() { - SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() { - @Override - public Passphrase getPassphraseFor(Long keyId) { - fail("MUST NOT get called in if postponed key strategy is invalid."); - return null; - } - - @Override - public boolean hasPassphrase(Long keyId) { - return true; - } - }; - ConsumerOptions options = new ConsumerOptions() - .setMissingKeyPassphraseStrategy(null) // illegal - .addDecryptionKey(secretKeys, SecretKeyRingProtector.defaultSecretKeyRingProtector(callback)); - - assertThrows(IllegalStateException.class, () -> PGPainless.decryptAndOrVerify() - .onInputStream(new ByteArrayInputStream(message)) - .withOptions(options)); - } - @Test public void interactiveStrategy() throws PGPException, IOException { // interactive callback From 9988ba994011305993f5e1094a22b4b840badb11 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 13:59:34 +0200 Subject: [PATCH 045/155] Kotlin conversion: DecryptionBuilder --- .../DecryptionBuilder.java | 42 ------------------- .../DecryptionBuilderInterface.java | 36 ---------------- .../DecryptionBuilder.kt | 26 ++++++++++++ .../DecryptionBuilderInterface.kt | 34 +++++++++++++++ 4 files changed, 60 insertions(+), 78 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilder.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilderInterface.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java deleted file mode 100644 index 96b4ad60..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.IOException; -import java.io.InputStream; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPException; - -/** - * Builder class that takes an {@link InputStream} of ciphertext (or plaintext signed data) - * and combines it with a configured {@link ConsumerOptions} object to form a {@link DecryptionStream} which - * can be used to decrypt an OpenPGP message or verify signatures. - */ -public class DecryptionBuilder implements DecryptionBuilderInterface { - - @Override - public DecryptWith onInputStream(@Nonnull InputStream inputStream) { - return new DecryptWithImpl(inputStream); - } - - static class DecryptWithImpl implements DecryptWith { - - private final InputStream inputStream; - - DecryptWithImpl(InputStream inputStream) { - this.inputStream = inputStream; - } - - @Override - public DecryptionStream withOptions(ConsumerOptions consumerOptions) throws PGPException, IOException { - if (consumerOptions == null) { - throw new IllegalArgumentException("Consumer options cannot be null."); - } - - return OpenPgpMessageInputStream.create(inputStream, consumerOptions); - } - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java deleted file mode 100644 index 07db42f0..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.IOException; -import java.io.InputStream; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPException; - -public interface DecryptionBuilderInterface { - - /** - * Create a {@link DecryptionStream} on an {@link InputStream} which contains the encrypted and/or signed data. - * - * @param inputStream encrypted and/or signed data. - * @return api handle - */ - DecryptWith onInputStream(@Nonnull InputStream inputStream); - - interface DecryptWith { - - /** - * Add options for decryption / signature verification, such as keys, passphrases etc. - * - * @param consumerOptions consumer options - * @return decryption stream - * @throws PGPException in case of an OpenPGP related error - * @throws IOException in case of an IO error - */ - DecryptionStream withOptions(ConsumerOptions consumerOptions) throws PGPException, IOException; - - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilder.kt new file mode 100644 index 00000000..4934f5de --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilder.kt @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import java.io.InputStream + +/** + * Builder class that takes an [InputStream] of ciphertext (or plaintext signed data) + * and combines it with a configured [ConsumerOptions] object to form a [DecryptionStream] which + * can be used to decrypt an OpenPGP message or verify signatures. + */ +class DecryptionBuilder: DecryptionBuilderInterface { + + override fun onInputStream(inputStream: InputStream): DecryptionBuilderInterface.DecryptWith { + return DecryptWithImpl(inputStream) + } + + class DecryptWithImpl(val inputStream: InputStream): DecryptionBuilderInterface.DecryptWith { + + override fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream { + return OpenPgpMessageInputStream.create(inputStream, consumerOptions) + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilderInterface.kt new file mode 100644 index 00000000..c15f301e --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilderInterface.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.openpgp.PGPException +import java.io.IOException +import java.io.InputStream + +interface DecryptionBuilderInterface { + + /** + * Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed data. + * + * @param inputStream encrypted and/or signed data. + * @return api handle + */ + fun onInputStream(inputStream: InputStream): DecryptWith + + interface DecryptWith { + + /** + * Add options for decryption / signature verification, such as keys, passphrases etc. + * + * @param consumerOptions consumer options + * @return decryption stream + * @throws PGPException in case of an OpenPGP related error + * @throws IOException in case of an IO error + */ + @Throws(PGPException::class, IOException::class) + fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream + } +} \ No newline at end of file From 23f8777c3461d81c0c7e0a3cc8d7d45101b6a9ff Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 14:12:05 +0200 Subject: [PATCH 046/155] Kotlin conversion: DecryptionStream --- .../DecryptionStream.java | 33 -------------- .../DecryptionStream.kt | 33 ++++++++++++++ .../OpenPgpMessageInputStream.kt | 45 ++++++++++--------- 3 files changed, 56 insertions(+), 55 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java deleted file mode 100644 index 28642bbf..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.InputStream; - -/** - * Abstract definition of an {@link InputStream} which can be used to decrypt / verify OpenPGP messages. - */ -public abstract class DecryptionStream extends InputStream { - - /** - * Return {@link MessageMetadata metadata} about the decrypted / verified message. - * The {@link DecryptionStream} MUST be closed via {@link #close()} before the metadata object can be accessed. - * - * @return message metadata - */ - public abstract MessageMetadata getMetadata(); - - /** - * Return a {@link OpenPgpMetadata} object containing information about the decrypted / verified message. - * The {@link DecryptionStream} MUST be closed via {@link #close()} before the metadata object can be accessed. - * - * @return message metadata - * @deprecated use {@link #getMetadata()} instead. - */ - @Deprecated - public OpenPgpMetadata getResult() { - return getMetadata().toLegacyMetadata(); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt new file mode 100644 index 00000000..2800db09 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import java.io.InputStream + +/** + * Abstract definition of an [InputStream] which can be used to decrypt / verify OpenPGP messages. + */ +abstract class DecryptionStream: InputStream() { + + /** + * Return [MessageMetadata] about the decrypted / verified message. + * The [DecryptionStream] MUST be closed via [close] before the metadata object can be accessed. + * + * @return message metadata + */ + abstract val metadata: MessageMetadata + + /** + * Return a [OpenPgpMetadata] object containing information about the decrypted / verified message. + * The [DecryptionStream] MUST be closed via [close] before the metadata object can be accessed. + * + * @return message metadata + * @deprecated use [metadata] instead. + */ + @Deprecated("Use of OpenPgpMetadata is discouraged.", + ReplaceWith("metadata")) + val result: OpenPgpMetadata + get() = metadata.toLegacyMetadata() +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 49a6edf5..a255e90e 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -40,7 +40,7 @@ class OpenPgpMessageInputStream( type: Type, inputStream: InputStream, private val options: ConsumerOptions, - private val metadata: Layer, + private val layerMetadata: Layer, private val policy: Policy) : DecryptionStream() { private val signatures: Signatures = Signatures(options) @@ -52,7 +52,7 @@ class OpenPgpMessageInputStream( init { // Add detached signatures only on the outermost OpenPgpMessageInputStream - if (metadata is Message) { + if (layerMetadata is Message) { signatures.addDetachedSignatures(options.getDetachedSignatures()) } @@ -151,12 +151,12 @@ class OpenPgpMessageInputStream( } private fun processLiteralData() { - LOGGER.debug("Literal Data Packet at depth ${metadata.depth} encountered.") + LOGGER.debug("Literal Data Packet at depth ${layerMetadata.depth} encountered.") syntaxVerifier.next(InputSymbol.LITERAL_DATA) val literalData = packetInputStream!!.readLiteralData() // Extract Metadata - metadata.setChild(LiteralData( + layerMetadata.setChild(LiteralData( literalData.fileName, literalData.modificationTime, StreamEncoding.requireFromCode(literalData.format))) @@ -171,16 +171,16 @@ class OpenPgpMessageInputStream( // Extract Metadata val compressionLayer = CompressedData( CompressionAlgorithm.requireFromId(compressedData.algorithm), - metadata.depth + 1) + layerMetadata.depth + 1) - LOGGER.debug("Compressed Data Packet (${compressionLayer.algorithm}) at depth ${metadata.depth} encountered.") + LOGGER.debug("Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.") nestedInputStream = OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy) } private fun processOnePassSignature() { syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE) val ops = packetInputStream!!.readOnePassSignature() - LOGGER.debug("One-Pass-Signature Packet by key ${KeyIdUtil.formatKeyId(ops.keyID)} at depth ${metadata.depth} encountered.") + LOGGER.debug("One-Pass-Signature Packet by key ${KeyIdUtil.formatKeyId(ops.keyID)} at depth ${layerMetadata.depth} encountered.") signatures.addOnePassSignature(ops) } @@ -191,23 +191,23 @@ class OpenPgpMessageInputStream( val signature = try { packetInputStream!!.readSignature() } catch (e : UnsupportedPacketVersionException) { - LOGGER.debug("Unsupported Signature at depth ${metadata.depth} encountered.", e) + LOGGER.debug("Unsupported Signature at depth ${layerMetadata.depth} encountered.", e) return } val keyId = SignatureUtils.determineIssuerKeyId(signature) if (isSigForOps) { - LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${metadata.depth} encountered.") + LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${layerMetadata.depth} encountered.") signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with - signatures.addCorrespondingOnePassSignature(signature, metadata, policy) + signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy) } else { - LOGGER.debug("Prepended Signature Packet by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${metadata.depth} encountered.") + LOGGER.debug("Prepended Signature Packet by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${layerMetadata.depth} encountered.") signatures.addPrependedSignature(signature) } } private fun processEncryptedData(): Boolean { - LOGGER.debug("Symmetrically Encrypted Data Packet at depth ${metadata.depth} encountered.") + LOGGER.debug("Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.") syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA) val encDataList = packetInputStream!!.readEncryptedDataList() if (!encDataList.isIntegrityProtected) { @@ -244,7 +244,7 @@ class OpenPgpMessageInputStream( val decryptorFactory = ImplementationFactory.getInstance() .getSessionKeyDataDecryptorFactory(sk) - val layer = EncryptedData(sk.algorithm, metadata.depth + 1) + val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1) val skEncData = encDataList.extractSessionKeyEncryptedData() try { val decrypted = skEncData.getDataStream(decryptorFactory) @@ -392,7 +392,7 @@ class OpenPgpMessageInputStream( val decrypted = skesk.getDataStream(decryptorFactory) val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory)) throwIfUnacceptable(sessionKey.algorithm) - val encryptedData = EncryptedData(sessionKey.algorithm, metadata.depth + 1) + val encryptedData = EncryptedData(sessionKey.algorithm, layerMetadata.depth + 1) encryptedData.sessionKey = sessionKey encryptedData.recipients = esks.pkesks.map { it.keyID } LOGGER.debug("Successfully decrypted data with passphrase") @@ -418,7 +418,7 @@ class OpenPgpMessageInputStream( val encryptedData = EncryptedData( SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)), - metadata.depth + 1) + layerMetadata.depth + 1) encryptedData.decryptionKey = decryptionKeyId encryptedData.sessionKey = sessionKey encryptedData.recipients = esks.pkesks.plus(esks.anonPkesks).map { it.keyID } @@ -460,7 +460,7 @@ class OpenPgpMessageInputStream( throw RuntimeException(e) } } - signatures.finish(metadata, policy) + signatures.finish(layerMetadata, policy) } return r } @@ -487,7 +487,7 @@ class OpenPgpMessageInputStream( throw RuntimeException(e) } } - signatures.finish(metadata, policy) + signatures.finish(layerMetadata, policy) } return r } @@ -522,15 +522,16 @@ class OpenPgpMessageInputStream( private fun collectMetadata() { if (nestedInputStream is OpenPgpMessageInputStream) { val child = nestedInputStream as OpenPgpMessageInputStream - metadata.setChild(child.metadata as Nested) + layerMetadata.setChild(child.layerMetadata as Nested) } } - override fun getMetadata(): MessageMetadata { - check(closed) { "Stream must be closed before access to metadata can be granted." } + override val metadata: MessageMetadata + get() { + check(closed) { "Stream must be closed before access to metadata can be granted." } - return MessageMetadata((metadata as Message)) - } + return MessageMetadata((layerMetadata as Message)) + } private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull { it.any { From 8c25b59c8bc7ec780fc35ed2c2f5dbbeaab4a675 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 14:36:54 +0200 Subject: [PATCH 047/155] Add missing utility methods to MessageMetadata class --- .../MessageMetadata.java | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java index 041a5437..1f7a5b03 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java @@ -21,6 +21,7 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.authentication.CertificateAuthenticity; import org.pgpainless.authentication.CertificateAuthority; import org.pgpainless.exception.MalformedOpenPgpMessageException; +import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.util.SessionKey; @@ -35,42 +36,6 @@ public class MessageMetadata { this.message = message; } - /** - * Convert this {@link MessageMetadata} object into a legacy {@link OpenPgpMetadata} object. - * This method is intended to be used for a transition period between the 1.3 / 1.4+ branches. - * TODO: Remove in 1.6.X - * - * @return converted {@link OpenPgpMetadata} object - */ - public @Nonnull OpenPgpMetadata toLegacyMetadata() { - OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder(); - resultBuilder.setCompressionAlgorithm(getCompressionAlgorithm()); - resultBuilder.setModificationDate(getModificationDate()); - resultBuilder.setFileName(getFilename()); - resultBuilder.setFileEncoding(getLiteralDataEncoding()); - resultBuilder.setSessionKey(getSessionKey()); - resultBuilder.setDecryptionKey(getDecryptionKey()); - - for (SignatureVerification accepted : getVerifiedDetachedSignatures()) { - resultBuilder.addVerifiedDetachedSignature(accepted); - } - for (SignatureVerification.Failure rejected : getRejectedDetachedSignatures()) { - resultBuilder.addInvalidDetachedSignature(rejected.getSignatureVerification(), rejected.getValidationException()); - } - - for (SignatureVerification accepted : getVerifiedInlineSignatures()) { - resultBuilder.addVerifiedInbandSignature(accepted); - } - for (SignatureVerification.Failure rejected : getRejectedInlineSignatures()) { - resultBuilder.addInvalidInbandSignature(rejected.getSignatureVerification(), rejected.getValidationException()); - } - if (message.isCleartextSigned()) { - resultBuilder.setCleartextSigned(); - } - - return resultBuilder.build(); - } - public boolean isUsingCleartextSignatureFramework() { return message.isCleartextSigned(); } @@ -240,6 +205,28 @@ public class MessageMetadata { return isVerifiedInlineSignedBy(keys) || isVerifiedDetachedSignedBy(keys); } + /** + * Return true, if the message was verifiable signed by a certificate that either has the given fingerprint + * as primary key, or as the signing subkey. + * + * @param fingerprint fingerprint + * @return true if message was signed by a cert identified by the given fingerprint + */ + public boolean isVerifiedSignedBy(@Nonnull OpenPgpFingerprint fingerprint) { + List verifications = getVerifiedSignatures(); + for (SignatureVerification verification : verifications) { + if (verification.getSigningKey() == null) { + continue; + } + + if (fingerprint.equals(verification.getSigningKey().getPrimaryKeyFingerprint()) || + fingerprint.equals(verification.getSigningKey().getSubkeyFingerprint())) { + return true; + } + } + return false; + } + public List getVerifiedSignatures() { List allVerifiedSignatures = getVerifiedInlineSignatures(); allVerifiedSignatures.addAll(getVerifiedDetachedSignatures()); @@ -269,6 +256,30 @@ public class MessageMetadata { return message.getRejectedDetachedSignatures(); } + /** + * Return a list of all rejected signatures. + * + * @return rejected signatures + */ + public @Nonnull List getRejectedSignatures() { + List rejected = new ArrayList<>(); + rejected.addAll(getRejectedInlineSignatures()); + rejected.addAll(getRejectedDetachedSignatures()); + return rejected; + } + + public boolean hasRejectedSignatures() { + return !getRejectedSignatures().isEmpty(); + } + + /** + * Return true, if the message contains any (verified or rejected) signature. + * @return true if message has signature + */ + public boolean hasSignature() { + return isVerifiedSigned() || hasRejectedSignatures(); + } + public boolean isVerifiedInlineSignedBy(@Nonnull PGPKeyRing keys) { return containsSignatureBy(getVerifiedInlineSignatures(), keys); } From 1a701333e3c0339cf9fbf0fe7ebbfcd55e72304a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 14:38:50 +0200 Subject: [PATCH 048/155] Remove deprecated OpenPgpMetadata class --- .../OpenPgpMetadata.java | 380 ------------------ .../DecryptionStream.kt | 12 - ...artialLengthLiteralDataRegressionTest.java | 2 +- .../CanonicalizedDataEncryptionTest.java | 56 +-- .../CleartextSignatureVerificationTest.java | 20 +- .../DecryptAndVerifyMessageTest.java | 26 +- .../DecryptHiddenRecipientMessageTest.java | 5 +- .../IgnoreUnknownSignatureVersionsTest.java | 16 +- ...ntDecryptionUsingNonEncryptionKeyTest.java | 4 +- ...ificationWithoutCertIsStillSignedTest.java | 8 +- .../VerifyDetachedSignatureTest.java | 8 +- .../VerifyNotBeforeNotAfterTest.java | 44 +- .../VerifyVersion3SignaturePacketTest.java | 8 +- ...erifyWithMissingPublicKeyCallbackTest.java | 4 +- .../WrongSignerUserIdTest.java | 6 +- .../EncryptDecryptTest.java | 13 +- .../encryption_signing/SigningTest.java | 11 +- .../GenerateKeyWithoutUserIdTest.java | 8 +- .../signature/IgnoreMarkerPacketsTest.java | 10 +- 19 files changed, 123 insertions(+), 518 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMetadata.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMetadata.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMetadata.java deleted file mode 100644 index e2d5f1ca..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMetadata.java +++ /dev/null @@ -1,380 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.StreamEncoding; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.util.SessionKey; - -/** - * Legacy class containing metadata about an OpenPGP message. - * It is advised to use {@link MessageMetadata} instead. - * - * TODO: Remove in 1.6.X - */ -public class OpenPgpMetadata { - - private final Set recipientKeyIds; - private final SubkeyIdentifier decryptionKey; - private final List verifiedInbandSignatures; - private final List invalidInbandSignatures; - private final List verifiedDetachedSignatures; - private final List invalidDetachedSignatures; - private final SessionKey sessionKey; - private final CompressionAlgorithm compressionAlgorithm; - private final String fileName; - private final Date modificationDate; - private final StreamEncoding fileEncoding; - private final boolean cleartextSigned; - - public OpenPgpMetadata(Set recipientKeyIds, - SubkeyIdentifier decryptionKey, - SessionKey sessionKey, - CompressionAlgorithm algorithm, - List verifiedInbandSignatures, - List invalidInbandSignatures, - List verifiedDetachedSignatures, - List invalidDetachedSignatures, - String fileName, - Date modificationDate, - StreamEncoding fileEncoding, - boolean cleartextSigned) { - - this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds); - this.decryptionKey = decryptionKey; - this.sessionKey = sessionKey; - this.compressionAlgorithm = algorithm; - this.verifiedInbandSignatures = Collections.unmodifiableList(verifiedInbandSignatures); - this.invalidInbandSignatures = Collections.unmodifiableList(invalidInbandSignatures); - this.verifiedDetachedSignatures = Collections.unmodifiableList(verifiedDetachedSignatures); - this.invalidDetachedSignatures = Collections.unmodifiableList(invalidDetachedSignatures); - this.fileName = fileName; - this.modificationDate = modificationDate; - this.fileEncoding = fileEncoding; - this.cleartextSigned = cleartextSigned; - } - - /** - * Return a set of key-ids the messages was encrypted for. - * - * @return recipient ids - */ - public @Nonnull Set getRecipientKeyIds() { - return recipientKeyIds; - } - - /** - * Return true, if the message was encrypted. - * - * @return true if encrypted, false otherwise - */ - public boolean isEncrypted() { - return sessionKey != null && sessionKey.getAlgorithm() != SymmetricKeyAlgorithm.NULL; - } - - /** - * Return the {@link SubkeyIdentifier} of the key that was used to decrypt the message. - * This can be null if the message was decrypted using a {@link org.pgpainless.util.Passphrase}, or if it was not - * encrypted at all (e.g. signed only). - * - * @return subkey identifier of decryption key - */ - public @Nullable SubkeyIdentifier getDecryptionKey() { - return decryptionKey; - } - - /** - * Return the algorithm that was used to symmetrically encrypt the message. - * - * @return encryption algorithm - */ - public @Nullable SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() { - return sessionKey == null ? null : sessionKey.getAlgorithm(); - } - - public @Nullable SessionKey getSessionKey() { - return sessionKey; - } - - /** - * Return the {@link CompressionAlgorithm} that was used to compress the message. - * - * @return compression algorithm - */ - public @Nullable CompressionAlgorithm getCompressionAlgorithm() { - return compressionAlgorithm; - } - - /** - * Return a set of all signatures on the message. - * Note: This method returns just the signatures. There is no guarantee that the signatures are verified or even correct. - * - * Use {@link #getVerifiedSignatures()} instead to get all verified signatures. - * @return unverified and verified signatures - */ - public @Nonnull Set getSignatures() { - Set signatures = new HashSet<>(); - for (SignatureVerification v : getVerifiedDetachedSignatures()) { - signatures.add(v.getSignature()); - } - for (SignatureVerification v : getVerifiedInbandSignatures()) { - signatures.add(v.getSignature()); - } - for (SignatureVerification.Failure f : getInvalidDetachedSignatures()) { - signatures.add(f.getSignatureVerification().getSignature()); - } - for (SignatureVerification.Failure f : getInvalidInbandSignatures()) { - signatures.add(f.getSignatureVerification().getSignature()); - } - return signatures; - } - - /** - * Return true if the message contained at least one signature. - * - * Note: This method does not reflect, whether the signature on the message is correct. - * Use {@link #isVerified()} instead to determine, if the message carries a verifiable signature. - * - * @return true if message contains at least one unverified or verified signature, false otherwise. - */ - public boolean isSigned() { - return !getSignatures().isEmpty(); - } - - /** - * Return a map of all verified signatures on the message. - * The map contains verified signatures as value, with the {@link SubkeyIdentifier} of the key that was used to verify - * the signature as the maps keys. - * - * @return verified detached and one-pass signatures - */ - public Map getVerifiedSignatures() { - Map verifiedSignatures = new ConcurrentHashMap<>(); - for (SignatureVerification detachedSignature : getVerifiedDetachedSignatures()) { - verifiedSignatures.put(detachedSignature.getSigningKey(), detachedSignature.getSignature()); - } - for (SignatureVerification inbandSignatures : verifiedInbandSignatures) { - verifiedSignatures.put(inbandSignatures.getSigningKey(), inbandSignatures.getSignature()); - } - - return verifiedSignatures; - } - - public List getVerifiedInbandSignatures() { - return verifiedInbandSignatures; - } - - public List getVerifiedDetachedSignatures() { - return verifiedDetachedSignatures; - } - - public List getInvalidInbandSignatures() { - return invalidInbandSignatures; - } - - public List getInvalidDetachedSignatures() { - return invalidDetachedSignatures; - } - - /** - * Return true, if the message is signed and at least one signature on the message was verified successfully. - * - * @return true if message is verified, false otherwise - */ - public boolean isVerified() { - return !getVerifiedSignatures().isEmpty(); - } - - /** - * Return true, if the message contains at least one verified signature made by a key in the - * given certificate. - * - * @param certificate certificate - * @return true if message was signed by the certificate (and the signature is valid), false otherwise - */ - public boolean containsVerifiedSignatureFrom(PGPPublicKeyRing certificate) { - for (PGPPublicKey key : certificate) { - OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(key); - if (containsVerifiedSignatureFrom(fingerprint)) { - return true; - } - } - return false; - } - - /** - * Return true, if the message contains at least one valid signature made by the key with the given - * fingerprint, false otherwise. - * - * The fingerprint might be of the signing subkey, or the primary key of the signing certificate. - * - * @param fingerprint fingerprint of primary key or signing subkey - * @return true if validly signed, false otherwise - */ - public boolean containsVerifiedSignatureFrom(OpenPgpFingerprint fingerprint) { - for (SubkeyIdentifier verifiedSigningKey : getVerifiedSignatures().keySet()) { - if (verifiedSigningKey.getPrimaryKeyFingerprint().equals(fingerprint) || - verifiedSigningKey.getSubkeyFingerprint().equals(fingerprint)) { - return true; - } - } - return false; - } - - /** - * Return the name of the encrypted / signed file. - * - * @return file name - */ - public String getFileName() { - return fileName; - } - - /** - * Return true, if the encrypted data is intended for your eyes only. - * - * @return true if for-your-eyes-only - */ - public boolean isForYourEyesOnly() { - return PGPLiteralData.CONSOLE.equals(getFileName()); - } - - /** - * Return the modification date of the encrypted / signed file. - * - * @return modification date - */ - public Date getModificationDate() { - return modificationDate; - } - - /** - * Return the encoding format of the encrypted / signed file. - * - * @return encoding - */ - public StreamEncoding getFileEncoding() { - return fileEncoding; - } - - /** - * Return true if the message was signed using the cleartext signature framework. - * - * @return true if cleartext signed. - */ - public boolean isCleartextSigned() { - return cleartextSigned; - } - - public static Builder getBuilder() { - return new Builder(); - } - - public static class Builder { - - private final Set recipientFingerprints = new HashSet<>(); - private SessionKey sessionKey; - private SubkeyIdentifier decryptionKey; - private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED; - private String fileName; - private StreamEncoding fileEncoding; - private Date modificationDate; - private boolean cleartextSigned = false; - - private final List verifiedInbandSignatures = new ArrayList<>(); - private final List verifiedDetachedSignatures = new ArrayList<>(); - private final List invalidInbandSignatures = new ArrayList<>(); - private final List invalidDetachedSignatures = new ArrayList<>(); - - - public Builder addRecipientKeyId(Long keyId) { - this.recipientFingerprints.add(keyId); - return this; - } - - public Builder setDecryptionKey(SubkeyIdentifier decryptionKey) { - this.decryptionKey = decryptionKey; - return this; - } - - public Builder setSessionKey(SessionKey sessionKey) { - this.sessionKey = sessionKey; - return this; - } - - public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) { - this.compressionAlgorithm = algorithm; - return this; - } - - public Builder setFileName(@Nullable String fileName) { - this.fileName = fileName; - return this; - } - - public Builder setModificationDate(Date modificationDate) { - this.modificationDate = modificationDate; - return this; - } - - public Builder setFileEncoding(StreamEncoding encoding) { - this.fileEncoding = encoding; - return this; - } - - public Builder addVerifiedInbandSignature(SignatureVerification signatureVerification) { - this.verifiedInbandSignatures.add(signatureVerification); - return this; - } - - public Builder addVerifiedDetachedSignature(SignatureVerification signatureVerification) { - this.verifiedDetachedSignatures.add(signatureVerification); - return this; - } - - public Builder addInvalidInbandSignature(SignatureVerification signatureVerification, SignatureValidationException e) { - this.invalidInbandSignatures.add(new SignatureVerification.Failure(signatureVerification, e)); - return this; - } - - public Builder addInvalidDetachedSignature(SignatureVerification signatureVerification, SignatureValidationException e) { - this.invalidDetachedSignatures.add(new SignatureVerification.Failure(signatureVerification, e)); - return this; - } - - public Builder setCleartextSigned() { - this.cleartextSigned = true; - return this; - } - - public OpenPgpMetadata build() { - return new OpenPgpMetadata( - recipientFingerprints, decryptionKey, - sessionKey, compressionAlgorithm, - verifiedInbandSignatures, invalidInbandSignatures, - verifiedDetachedSignatures, invalidDetachedSignatures, - fileName, modificationDate, fileEncoding, cleartextSigned); - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt index 2800db09..b9499784 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt @@ -18,16 +18,4 @@ abstract class DecryptionStream: InputStream() { * @return message metadata */ abstract val metadata: MessageMetadata - - /** - * Return a [OpenPgpMetadata] object containing information about the decrypted / verified message. - * The [DecryptionStream] MUST be closed via [close] before the metadata object can be accessed. - * - * @return message metadata - * @deprecated use [metadata] instead. - */ - @Deprecated("Use of OpenPgpMetadata is discouraged.", - ReplaceWith("metadata")) - val result: OpenPgpMetadata - get() = metadata.toLegacyMetadata() } \ No newline at end of file diff --git a/pgpainless-core/src/test/java/investigations/OnePassSignatureVerificationWithPartialLengthLiteralDataRegressionTest.java b/pgpainless-core/src/test/java/investigations/OnePassSignatureVerificationWithPartialLengthLiteralDataRegressionTest.java index a9b20e9e..7ec53edb 100644 --- a/pgpainless-core/src/test/java/investigations/OnePassSignatureVerificationWithPartialLengthLiteralDataRegressionTest.java +++ b/pgpainless-core/src/test/java/investigations/OnePassSignatureVerificationWithPartialLengthLiteralDataRegressionTest.java @@ -130,6 +130,6 @@ public class OnePassSignatureVerificationWithPartialLengthLiteralDataRegressionT Streams.pipeAll(decryptionStream, out); decryptionStream.close(); - decryptionStream.getResult(); + decryptionStream.getMetadata(); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CanonicalizedDataEncryptionTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CanonicalizedDataEncryptionTest.java index c427af99..36e473ac 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CanonicalizedDataEncryptionTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CanonicalizedDataEncryptionTest.java @@ -122,9 +122,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void noInputEncodingBinaryDataBinarySig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.BINARY_DOCUMENT, StreamEncoding.BINARY, false); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -136,9 +136,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void noInputEncodingBinaryDataTextSig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT, StreamEncoding.BINARY, false); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -150,9 +150,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void noInputEncodingTextDataBinarySig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.BINARY_DOCUMENT, StreamEncoding.TEXT, false); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -164,9 +164,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void noInputEncodingTextDataTextSig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT, StreamEncoding.TEXT, false); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -178,9 +178,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void noInputEncodingUtf8DataBinarySig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.BINARY_DOCUMENT, StreamEncoding.UTF8, false); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -192,9 +192,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void noInputEncodingUtf8DataTextSig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT, StreamEncoding.UTF8, false); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -207,9 +207,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void inputEncodingBinaryDataBinarySig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.BINARY_DOCUMENT, StreamEncoding.BINARY, true); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -221,9 +221,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void inputEncodingBinaryDataTextSig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT, StreamEncoding.BINARY, true); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -235,9 +235,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void inputEncodingTextDataBinarySig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.BINARY_DOCUMENT, StreamEncoding.TEXT, true); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -249,9 +249,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void inputEncodingTextDataTextSig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT, StreamEncoding.TEXT, true); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -263,9 +263,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void inputEncodingUtf8DataBinarySig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.BINARY_DOCUMENT, StreamEncoding.UTF8, true); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -277,9 +277,9 @@ public class CanonicalizedDataEncryptionTest { @Test public void inputEncodingUtf8DataTextSig() throws PGPException, IOException { String msg = encryptAndSign(message, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT, StreamEncoding.UTF8, true); - OpenPgpMetadata metadata = decryptAndVerify(msg); + MessageMetadata metadata = decryptAndVerify(msg); - if (!metadata.isVerified()) { + if (!metadata.isVerifiedSigned()) { // CHECKSTYLE:OFF System.out.println("Not verified. Session-Key: " + metadata.getSessionKey()); System.out.println(msg); @@ -360,7 +360,7 @@ public class CanonicalizedDataEncryptionTest { return msg; } - private OpenPgpMetadata decryptAndVerify(String msg) throws PGPException, IOException { + private MessageMetadata decryptAndVerify(String msg) throws PGPException, IOException { ByteArrayInputStream in = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)); DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() .onInputStream(in) @@ -371,7 +371,7 @@ public class CanonicalizedDataEncryptionTest { Streams.drain(decryptionStream); decryptionStream.close(); - return decryptionStream.getResult(); + return decryptionStream.getMetadata(); } @Test @@ -439,8 +439,8 @@ public class CanonicalizedDataEncryptionTest { Streams.pipeAll(decryptionStream, decrypted); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); - assertTrue(metadata.isVerified(), "Not verified! Sig Type: " + sigType + " StreamEncoding: " + streamEncoding); + MessageMetadata metadata = decryptionStream.getMetadata(); + assertTrue(metadata.isVerifiedSigned(), "Not verified! Sig Type: " + sigType + " StreamEncoding: " + streamEncoding); assertArrayEquals(msg, decrypted.toByteArray()); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java index cabfdbb1..720a0d53 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java @@ -96,11 +96,11 @@ public class CleartextSignatureVerificationTest { Streams.pipeAll(decryptionStream, out); decryptionStream.close(); - OpenPgpMetadata result = decryptionStream.getResult(); - assertTrue(result.isVerified()); - assertTrue(result.isCleartextSigned()); + MessageMetadata result = decryptionStream.getMetadata(); + assertTrue(result.isVerifiedSigned()); + assertTrue(result.isUsingCleartextSignatureFramework()); - PGPSignature signature = result.getVerifiedSignatures().values().iterator().next(); + PGPSignature signature = result.getVerifiedSignatures().iterator().next().getSignature(); assertEquals(signature.getKeyID(), signingKeys.getPublicKey().getKeyID()); assertArrayEquals(MESSAGE_BODY, out.toByteArray()); @@ -125,10 +125,10 @@ public class CleartextSignatureVerificationTest { Streams.pipeAll(decryptionStream, out); decryptionStream.close(); - OpenPgpMetadata result = decryptionStream.getResult(); - assertTrue(result.isVerified()); + MessageMetadata result = decryptionStream.getMetadata(); + assertTrue(result.isVerifiedSigned()); - PGPSignature signature = result.getVerifiedSignatures().values().iterator().next(); + PGPSignature signature = result.getVerifiedSignatures().iterator().next().getSignature(); assertEquals(signature.getKeyID(), signingKeys.getPublicKey().getKeyID()); FileInputStream fileIn = new FileInputStream(file); @@ -178,7 +178,7 @@ public class CleartextSignatureVerificationTest { Streams.pipeAll(decryptionStream, out); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); + MessageMetadata metadata = decryptionStream.getMetadata(); assertEquals(1, metadata.getVerifiedSignatures().size()); } @@ -210,8 +210,8 @@ public class CleartextSignatureVerificationTest { Streams.pipeAll(verificationStream, msgOut); verificationStream.close(); - OpenPgpMetadata metadata = verificationStream.getResult(); - assertTrue(metadata.isVerified()); + MessageMetadata metadata = verificationStream.getMetadata(); + assertTrue(metadata.isVerifiedSigned()); } @Test diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptAndVerifyMessageTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptAndVerifyMessageTest.java index e939de0a..ad61e132 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptAndVerifyMessageTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptAndVerifyMessageTest.java @@ -64,7 +64,7 @@ public class DecryptAndVerifyMessageTest { Streams.pipeAll(decryptor, toPlain); decryptor.close(); toPlain.close(); - OpenPgpMetadata metadata = decryptor.getResult(); + MessageMetadata metadata = decryptor.getMetadata(); byte[] expected = TestKeys.TEST_MESSAGE_01_PLAIN.getBytes(UTF8); byte[] actual = toPlain.toByteArray(); @@ -72,14 +72,13 @@ public class DecryptAndVerifyMessageTest { assertArrayEquals(expected, actual); assertTrue(metadata.isEncrypted()); - assertTrue(metadata.isSigned()); - assertFalse(metadata.isCleartextSigned()); - assertTrue(metadata.isVerified()); + assertFalse(metadata.isUsingCleartextSignatureFramework()); + assertTrue(metadata.isVerifiedSigned()); assertEquals(CompressionAlgorithm.ZLIB, metadata.getCompressionAlgorithm()); - assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getSymmetricKeyAlgorithm()); - assertEquals(1, metadata.getSignatures().size()); + assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getEncryptionAlgorithm()); assertEquals(1, metadata.getVerifiedSignatures().size()); - assertTrue(metadata.containsVerifiedSignatureFrom(TestKeys.JULIET_FINGERPRINT)); + assertEquals(1, metadata.getVerifiedSignatures().size()); + assertTrue(metadata.isVerifiedSignedBy(TestKeys.JULIET_FINGERPRINT)); assertEquals(new SubkeyIdentifier(TestKeys.JULIET_FINGERPRINT), metadata.getDecryptionKey()); } @@ -104,7 +103,7 @@ public class DecryptAndVerifyMessageTest { decryptor.close(); toPlain.close(); - OpenPgpMetadata metadata = decryptor.getResult(); + MessageMetadata metadata = decryptor.getMetadata(); byte[] expected = TestKeys.TEST_MESSAGE_01_PLAIN.getBytes(UTF8); byte[] actual = toPlain.toByteArray(); @@ -112,14 +111,13 @@ public class DecryptAndVerifyMessageTest { assertArrayEquals(expected, actual); assertTrue(metadata.isEncrypted()); - assertTrue(metadata.isSigned()); - assertFalse(metadata.isCleartextSigned()); - assertTrue(metadata.isVerified()); + assertFalse(metadata.isUsingCleartextSignatureFramework()); + assertTrue(metadata.isVerifiedSigned()); assertEquals(CompressionAlgorithm.ZLIB, metadata.getCompressionAlgorithm()); - assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getSymmetricKeyAlgorithm()); - assertEquals(1, metadata.getSignatures().size()); + assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getEncryptionAlgorithm()); assertEquals(1, metadata.getVerifiedSignatures().size()); - assertTrue(metadata.containsVerifiedSignatureFrom(TestKeys.JULIET_FINGERPRINT)); + assertEquals(1, metadata.getVerifiedSignatures().size()); + assertTrue(metadata.isVerifiedSignedBy(TestKeys.JULIET_FINGERPRINT)); assertEquals(new SubkeyIdentifier(TestKeys.JULIET_FINGERPRINT), metadata.getDecryptionKey()); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessageTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessageTest.java index 5400d17c..4eb7b203 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessageTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessageTest.java @@ -139,8 +139,9 @@ public class DecryptHiddenRecipientMessageTest { Streams.pipeAll(decryptionStream, out); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); - assertEquals(0, metadata.getRecipientKeyIds().size()); + MessageMetadata metadata = decryptionStream.getMetadata(); + assertEquals(1, metadata.getRecipientKeyIds().size()); + assertEquals(0L, metadata.getRecipientKeyIds().get(0)); KeyRingInfo info = new KeyRingInfo(secretKeys); List encryptionKeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY); diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/IgnoreUnknownSignatureVersionsTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/IgnoreUnknownSignatureVersionsTest.java index aa1da741..2b222c83 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/IgnoreUnknownSignatureVersionsTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/IgnoreUnknownSignatureVersionsTest.java @@ -104,9 +104,9 @@ public class IgnoreUnknownSignatureVersionsTest { "ou1uiXJaDzZ6wQfB\n" + "=uHRc\n" + "-----END PGP SIGNATURE-----\n"; - OpenPgpMetadata metadata = verifySignature(cert, BASE_CASE); + MessageMetadata metadata = verifySignature(cert, BASE_CASE); - assertTrue(metadata.isVerified()); + assertTrue(metadata.isVerifiedSigned()); } @Test @@ -137,9 +137,9 @@ public class IgnoreUnknownSignatureVersionsTest { "ou1uiXJaDzZ6wQfB\n" + "=/JL1\n" + "-----END PGP SIGNATURE-----\n"; - OpenPgpMetadata metadata = verifySignature(cert, SIG4SIG23); + MessageMetadata metadata = verifySignature(cert, SIG4SIG23); - assertTrue(metadata.isVerified()); + assertTrue(metadata.isVerifiedSigned()); } @Test @@ -170,12 +170,12 @@ public class IgnoreUnknownSignatureVersionsTest { "ou1uiXJaDzZ6wQfB\n" + "=Yc8d\n" + "-----END PGP SIGNATURE-----\n"; - OpenPgpMetadata metadata = verifySignature(cert, SIG23SIG4); + MessageMetadata metadata = verifySignature(cert, SIG23SIG4); - assertTrue(metadata.isVerified()); + assertTrue(metadata.isVerifiedSigned()); } - private OpenPgpMetadata verifySignature(PGPPublicKeyRing cert, String BASE_CASE) throws PGPException, IOException { + private MessageMetadata verifySignature(PGPPublicKeyRing cert, String BASE_CASE) throws PGPException, IOException { DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8))) .withOptions(new ConsumerOptions() .addVerificationCert(cert) @@ -184,6 +184,6 @@ public class IgnoreUnknownSignatureVersionsTest { Streams.drain(decryptionStream); decryptionStream.close(); - return decryptionStream.getResult(); + return decryptionStream.getMetadata(); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java index 04a98265..f06f0233 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java @@ -184,7 +184,7 @@ public class PreventDecryptionUsingNonEncryptionKeyTest { Streams.drain(decryptionStream); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); + MessageMetadata metadata = decryptionStream.getMetadata(); assertEquals(new SubkeyIdentifier(secretKeys, secretKeys.getPublicKey().getKeyID()), metadata.getDecryptionKey()); } @@ -200,7 +200,7 @@ public class PreventDecryptionUsingNonEncryptionKeyTest { Streams.drain(decryptionStream); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); + MessageMetadata metadata = decryptionStream.getMetadata(); assertEquals(new SubkeyIdentifier(secretKeys, secretKeys.getPublicKey().getKeyID()), metadata.getDecryptionKey()); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/SignedMessageVerificationWithoutCertIsStillSignedTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/SignedMessageVerificationWithoutCertIsStillSignedTest.java index 27bc9954..9f85b241 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/SignedMessageVerificationWithoutCertIsStillSignedTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/SignedMessageVerificationWithoutCertIsStillSignedTest.java @@ -39,10 +39,10 @@ public class SignedMessageVerificationWithoutCertIsStillSignedTest { Streams.pipeAll(verificationStream, out); verificationStream.close(); - OpenPgpMetadata metadata = verificationStream.getResult(); + MessageMetadata metadata = verificationStream.getMetadata(); - assertFalse(metadata.isCleartextSigned()); - assertTrue(metadata.isSigned(), "Message is signed, even though we miss the verification cert."); - assertFalse(metadata.isVerified(), "Message is not verified because we lack the verification cert."); + assertFalse(metadata.isUsingCleartextSignatureFramework()); + assertTrue(metadata.hasRejectedSignatures(), "Message is signed, even though we miss the verification cert."); + assertFalse(metadata.isVerifiedSigned(), "Message is not verified because we lack the verification cert."); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyDetachedSignatureTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyDetachedSignatureTest.java index fa1427d3..e1406f87 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyDetachedSignatureTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyDetachedSignatureTest.java @@ -65,8 +65,8 @@ public class VerifyDetachedSignatureTest { Streams.drain(verifier); verifier.close(); - OpenPgpMetadata metadata = verifier.getResult(); - assertTrue(metadata.isVerified()); + MessageMetadata metadata = verifier.getMetadata(); + assertTrue(metadata.isVerifiedSigned()); } @Test @@ -140,7 +140,7 @@ public class VerifyDetachedSignatureTest { Streams.drain(verifier); verifier.close(); - OpenPgpMetadata metadata = verifier.getResult(); - assertTrue(metadata.isVerified()); + MessageMetadata metadata = verifier.getMetadata(); + assertTrue(metadata.isVerifiedSigned()); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyNotBeforeNotAfterTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyNotBeforeNotAfterTest.java index d67b3d95..069a5f2d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyNotBeforeNotAfterTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyNotBeforeNotAfterTest.java @@ -68,8 +68,8 @@ public class VerifyNotBeforeNotAfterTest { .onInputStream(new ByteArrayInputStream(inlineSigned)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertTrue(metadata.getVerifiedSignatures().containsKey(new SubkeyIdentifier(certificate))); + MessageMetadata metadata = processSignedData(verifier); + assertTrue(metadata.isVerifiedSignedBy(certificate)); } @Test @@ -81,8 +81,8 @@ public class VerifyNotBeforeNotAfterTest { .onInputStream(new ByteArrayInputStream(data)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertTrue(metadata.containsVerifiedSignatureFrom(certificate)); + MessageMetadata metadata = processSignedData(verifier); + assertTrue(metadata.isVerifiedSignedBy(certificate)); } @Test @@ -93,8 +93,8 @@ public class VerifyNotBeforeNotAfterTest { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(inlineSigned)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertTrue(metadata.getVerifiedSignatures().containsKey(signingKey)); + MessageMetadata metadata = processSignedData(verifier); + assertTrue(metadata.isVerifiedSignedBy(certificate)); } @Test @@ -106,8 +106,8 @@ public class VerifyNotBeforeNotAfterTest { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(data)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertTrue(metadata.getVerifiedSignatures().containsKey(signingKey)); + MessageMetadata metadata = processSignedData(verifier); + assertTrue(metadata.isVerifiedSignedBy(certificate)); } @Test @@ -118,8 +118,8 @@ public class VerifyNotBeforeNotAfterTest { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(inlineSigned)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertFalse(metadata.getVerifiedSignatures().containsKey(signingKey)); + MessageMetadata metadata = processSignedData(verifier); + assertFalse(metadata.isVerifiedInlineSignedBy(certificate)); } @Test @@ -131,8 +131,8 @@ public class VerifyNotBeforeNotAfterTest { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(data)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertFalse(metadata.getVerifiedSignatures().containsKey(signingKey)); + MessageMetadata metadata = processSignedData(verifier); + assertFalse(metadata.isVerifiedSignedBy(certificate)); } @Test @@ -143,8 +143,8 @@ public class VerifyNotBeforeNotAfterTest { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(inlineSigned)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertTrue(metadata.getVerifiedSignatures().containsKey(signingKey)); + MessageMetadata metadata = processSignedData(verifier); + assertTrue(metadata.isVerifiedSignedBy(certificate)); } @Test @@ -156,8 +156,8 @@ public class VerifyNotBeforeNotAfterTest { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(data)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertTrue(metadata.getVerifiedSignatures().containsKey(signingKey)); + MessageMetadata metadata = processSignedData(verifier); + assertTrue(metadata.isVerifiedSignedBy(certificate)); } @Test @@ -168,8 +168,8 @@ public class VerifyNotBeforeNotAfterTest { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(inlineSigned)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertFalse(metadata.getVerifiedSignatures().containsKey(signingKey)); + MessageMetadata metadata = processSignedData(verifier); + assertFalse(metadata.isVerifiedSignedBy(certificate)); } @Test @@ -181,13 +181,13 @@ public class VerifyNotBeforeNotAfterTest { DecryptionStream verifier = PGPainless.decryptAndOrVerify() .onInputStream(new ByteArrayInputStream(data)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertFalse(metadata.getVerifiedSignatures().containsKey(signingKey)); + MessageMetadata metadata = processSignedData(verifier); + assertFalse(metadata.isVerifiedSignedBy(certificate)); } - private OpenPgpMetadata processSignedData(DecryptionStream verifier) throws IOException { + private MessageMetadata processSignedData(DecryptionStream verifier) throws IOException { Streams.drain(verifier); verifier.close(); - return verifier.getResult(); + return verifier.getMetadata(); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyVersion3SignaturePacketTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyVersion3SignaturePacketTest.java index 2a12e74a..6b9d9cab 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyVersion3SignaturePacketTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyVersion3SignaturePacketTest.java @@ -43,8 +43,8 @@ class VerifyVersion3SignaturePacketTest { .onInputStream(new ByteArrayInputStream(DATA)) .withOptions(options); - OpenPgpMetadata metadata = processSignedData(verifier); - assertTrue(metadata.containsVerifiedSignatureFrom(TestKeys.getEmilPublicKeyRing())); + MessageMetadata metadata = processSignedData(verifier); + assertTrue(metadata.isVerifiedSignedBy(TestKeys.getEmilPublicKeyRing())); } private static PGPSignature generateV3Signature() throws IOException, PGPException { @@ -61,9 +61,9 @@ class VerifyVersion3SignaturePacketTest { return signatureGenerator.generate(); } - private OpenPgpMetadata processSignedData(DecryptionStream verifier) throws IOException { + private MessageMetadata processSignedData(DecryptionStream verifier) throws IOException { Streams.drain(verifier); verifier.close(); - return verifier.getResult(); + return verifier.getMetadata(); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallbackTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallbackTest.java index 2821ca89..52877626 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallbackTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallbackTest.java @@ -74,7 +74,7 @@ public class VerifyWithMissingPublicKeyCallbackTest { verificationStream.close(); assertArrayEquals(msg.getBytes(StandardCharsets.UTF_8), plainOut.toByteArray()); - OpenPgpMetadata metadata = verificationStream.getResult(); - assertTrue(metadata.containsVerifiedSignatureFrom(signingPubKeys)); + MessageMetadata metadata = verificationStream.getMetadata(); + assertTrue(metadata.isVerifiedSignedBy(signingPubKeys)); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/WrongSignerUserIdTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/WrongSignerUserIdTest.java index b3ecabc8..f3336373 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/WrongSignerUserIdTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/WrongSignerUserIdTest.java @@ -102,12 +102,12 @@ public class WrongSignerUserIdTest { Streams.pipeAll(decryptionStream, out); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); + MessageMetadata metadata = decryptionStream.getMetadata(); if (expectSuccessfulVerification) { - assertTrue(metadata.isVerified()); + assertTrue(metadata.isVerifiedSigned()); } else { - assertFalse(metadata.isVerified()); + assertFalse(metadata.isVerifiedSigned()); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java index 216d0c65..3e620386 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java @@ -33,7 +33,7 @@ import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.DecryptionStream; -import org.pgpainless.decryption_verification.OpenPgpMetadata; +import org.pgpainless.decryption_verification.MessageMetadata; import org.pgpainless.exception.KeyException; import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.TestKeys; @@ -185,11 +185,10 @@ public class EncryptDecryptTest { decryptor.close(); assertArrayEquals(secretMessage, decryptedSecretMessage.toByteArray()); - OpenPgpMetadata result = decryptor.getResult(); - assertTrue(result.containsVerifiedSignatureFrom(senderPub)); - assertTrue(result.isSigned()); + MessageMetadata result = decryptor.getMetadata(); + assertTrue(result.isVerifiedSignedBy(senderPub)); assertTrue(result.isEncrypted()); - assertTrue(result.isVerified()); + assertTrue(result.isVerifiedSigned()); } @TestTemplate @@ -233,7 +232,7 @@ public class EncryptDecryptTest { Streams.pipeAll(verifier, dummyOut); verifier.close(); - OpenPgpMetadata decryptionResult = verifier.getResult(); + MessageMetadata decryptionResult = verifier.getMetadata(); assertFalse(decryptionResult.getVerifiedSignatures().isEmpty()); } @@ -263,7 +262,7 @@ public class EncryptDecryptTest { Streams.pipeAll(verifier, signOut); verifier.close(); - OpenPgpMetadata metadata = verifier.getResult(); + MessageMetadata metadata = verifier.getMetadata(); assertFalse(metadata.getVerifiedSignatures().isEmpty()); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java index 156e6b57..bfd330aa 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java @@ -34,7 +34,7 @@ import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.DecryptionStream; -import org.pgpainless.decryption_verification.OpenPgpMetadata; +import org.pgpainless.decryption_verification.MessageMetadata; import org.pgpainless.exception.KeyException; import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.TestKeys; @@ -106,12 +106,11 @@ public class SigningTest { Streams.pipeAll(decryptionStream, plaintextOut); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); + MessageMetadata metadata = decryptionStream.getMetadata(); assertTrue(metadata.isEncrypted()); - assertTrue(metadata.isSigned()); - assertTrue(metadata.isVerified()); - assertTrue(metadata.containsVerifiedSignatureFrom(KeyRingUtils.publicKeyRingFrom(cryptieKeys))); - assertFalse(metadata.containsVerifiedSignatureFrom(julietKeys)); + assertTrue(metadata.isVerifiedSigned()); + assertTrue(metadata.isVerifiedSignedBy(KeyRingUtils.publicKeyRingFrom(cryptieKeys))); + assertFalse(metadata.isVerifiedSignedBy(julietKeys)); } @TestTemplate diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithoutUserIdTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithoutUserIdTest.java index 24484cd0..e7884f3f 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithoutUserIdTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithoutUserIdTest.java @@ -14,7 +14,7 @@ import org.pgpainless.PGPainless; import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.DecryptionStream; -import org.pgpainless.decryption_verification.OpenPgpMetadata; +import org.pgpainless.decryption_verification.MessageMetadata; import org.pgpainless.decryption_verification.SignatureVerification; import org.pgpainless.encryption_signing.EncryptionOptions; import org.pgpainless.encryption_signing.EncryptionResult; @@ -88,10 +88,10 @@ public class GenerateKeyWithoutUserIdTest { Streams.pipeAll(decryptionStream, plaintextOut); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); + MessageMetadata metadata = decryptionStream.getMetadata(); - assertTrue(metadata.containsVerifiedSignatureFrom(certificate), - failuresToString(metadata.getInvalidInbandSignatures())); + assertTrue(metadata.isVerifiedSignedBy(certificate), + failuresToString(metadata.getRejectedInlineSignatures())); assertTrue(metadata.isEncrypted()); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPacketsTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPacketsTest.java index 6ece093b..ff26506d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPacketsTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPacketsTest.java @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.DecryptionStream; -import org.pgpainless.decryption_verification.OpenPgpMetadata; +import org.pgpainless.decryption_verification.MessageMetadata; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.util.KeyRingUtils; @@ -154,8 +154,8 @@ public class IgnoreMarkerPacketsTest { Streams.pipeAll(decryptionStream, outputStream); decryptionStream.close(); - OpenPgpMetadata metadata = decryptionStream.getResult(); - assertTrue(metadata.containsVerifiedSignatureFrom(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"))); + MessageMetadata metadata = decryptionStream.getMetadata(); + assertTrue(metadata.isVerifiedSignedBy(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"))); } @Test @@ -204,8 +204,8 @@ public class IgnoreMarkerPacketsTest { decryptionStream.close(); assertArrayEquals(data.getBytes(StandardCharsets.UTF_8), outputStream.toByteArray()); - OpenPgpMetadata metadata = decryptionStream.getResult(); - assertTrue(metadata.containsVerifiedSignatureFrom(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"))); + MessageMetadata metadata = decryptionStream.getMetadata(); + assertTrue(metadata.isVerifiedSignedBy(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"))); } @Test From 145555997c8e50bb98b063beff60a5b086a1e28e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 14:43:33 +0200 Subject: [PATCH 049/155] Kotlin conversion: SignatureCheck --- .../signature/consumer/SignatureCheck.java | 73 ------------------- .../signature/consumer/SignatureCheck.kt | 23 ++++++ 2 files changed, 23 insertions(+), 73 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureCheck.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCheck.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureCheck.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureCheck.java deleted file mode 100644 index bc9f1f0b..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureCheck.java +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.consumer; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.SubkeyIdentifier; - -/** - * Tuple-class which bundles together a signature, the signing key that created the signature, - * an identifier of the signing key and a record of whether the signature was verified. - */ -public class SignatureCheck { - private final PGPSignature signature; - private final PGPKeyRing signingKeyRing; - private final SubkeyIdentifier signingKeyIdentifier; - - /** - * Create a new {@link SignatureCheck} object. - * - * @param signature signature - * @param signingKeyRing signing key that created the signature - * @param signingKeyIdentifier identifier of the used signing key - */ - public SignatureCheck(PGPSignature signature, PGPKeyRing signingKeyRing, SubkeyIdentifier signingKeyIdentifier) { - this.signature = signature; - this.signingKeyRing = signingKeyRing; - this.signingKeyIdentifier = signingKeyIdentifier; - } - - /** - * Return the OpenPGP signature. - * - * @return signature - */ - public PGPSignature getSignature() { - return signature; - } - - /** - * Return an identifier pointing to the exact signing key which was used to create this signature. - * - * @return signing key identifier - */ - public SubkeyIdentifier getSigningKeyIdentifier() { - return signingKeyIdentifier; - } - - /** - * Return the key ring that contains the signing key that created this signature. - * - * @return key ring - */ - public PGPKeyRing getSigningKeyRing() { - return signingKeyRing; - } - - /** - * Return the {@link OpenPgpFingerprint} of the key that created the signature. - * - * @return fingerprint of the signing key - * @deprecated use {@link #getSigningKeyIdentifier()} instead. - * - * TODO: Remove in 1.2.X - */ - @Deprecated - public OpenPgpFingerprint getFingerprint() { - return signingKeyIdentifier.getSubkeyFingerprint(); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCheck.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCheck.kt new file mode 100644 index 00000000..48a3aa96 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCheck.kt @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.key.SubkeyIdentifier + +/** + * Tuple-class which bundles together a signature, the signing key that created the signature, + * an identifier of the signing key and a record of whether the signature was verified. + * + * @param signature OpenPGP signature + * @param signingKeyIdentifier identifier pointing to the exact signing key which was used to create the signature + * @param signingKeyRing certificate or key ring that contains the signing key that created the signature + */ +data class SignatureCheck( + val signature: PGPSignature, + val signingKeyRing: PGPKeyRing, + val signingKeyIdentifier: SubkeyIdentifier) { +} \ No newline at end of file From 8d67820f50435d179c83467906170604a56de503 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 15:14:21 +0200 Subject: [PATCH 050/155] Kotlin conversion: OnePassSignatureCheck --- .../consumer/OnePassSignatureCheck.java | 72 ------------------- .../consumer/OnePassSignatureCheck.kt | 33 +++++++++ 2 files changed, 33 insertions(+), 72 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/OnePassSignatureCheck.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/OnePassSignatureCheck.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/OnePassSignatureCheck.java deleted file mode 100644 index 7a6a5b10..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/OnePassSignatureCheck.java +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.consumer; - -import org.bouncycastle.openpgp.PGPOnePassSignature; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.key.SubkeyIdentifier; - -/** - * Tuple-class that bundles together a {@link PGPOnePassSignature} object, a {@link PGPPublicKeyRing} - * destined to verify the signature, the {@link PGPSignature} itself and a record of whether the signature - * was verified. - */ -public class OnePassSignatureCheck { - private final PGPOnePassSignature onePassSignature; - private final PGPPublicKeyRing verificationKeys; - private PGPSignature signature; - - /** - * Create a new {@link OnePassSignatureCheck}. - * - * @param onePassSignature one-pass signature packet used to initialize the signature verifier. - * @param verificationKeys verification keys - */ - public OnePassSignatureCheck(PGPOnePassSignature onePassSignature, PGPPublicKeyRing verificationKeys) { - this.onePassSignature = onePassSignature; - this.verificationKeys = verificationKeys; - } - - public void setSignature(PGPSignature signature) { - this.signature = signature; - } - - /** - * Return the {@link PGPOnePassSignature} object. - * - * @return onePassSignature - */ - public PGPOnePassSignature getOnePassSignature() { - return onePassSignature; - } - - /** - * Return an identifier for the signing key. - * - * @return signing key fingerprint - */ - public SubkeyIdentifier getSigningKey() { - return new SubkeyIdentifier(verificationKeys, onePassSignature.getKeyID()); - } - - /** - * Return the signature. - * - * @return signature - */ - public PGPSignature getSignature() { - return signature; - } - - /** - * Return the key ring used to verify the signature. - * - * @return verification keys - */ - public PGPPublicKeyRing getVerificationKeys() { - return verificationKeys; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt new file mode 100644 index 00000000..a315f577 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import org.bouncycastle.openpgp.PGPOnePassSignature +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.key.SubkeyIdentifier + +/** + * Tuple-class that bundles together a [PGPOnePassSignature] object, a [PGPPublicKeyRing] + * destined to verify the signature, the [PGPSignature] itself and a record of whether the signature + * was verified. + * + * @param onePassSignature the one-pass-signature packet + * @param verificationKeys certificate containing the signing subkey + * @param signature the signature packet + */ +data class OnePassSignatureCheck( + val onePassSignature: PGPOnePassSignature, + val verificationKeys: PGPPublicKeyRing, + var signature: PGPSignature? = null) { + + /** + * Return an identifier for the signing key. + * + * @return signing key fingerprint + */ + val signingKey: SubkeyIdentifier + get() = SubkeyIdentifier(verificationKeys, onePassSignature.keyID) +} \ No newline at end of file From 48af91efbf060a53ee32d26c5b0af7f884027448 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 15:59:25 +0200 Subject: [PATCH 051/155] Kotlin conversion: Cleartext Signature Framework --- .../ClearsignedMessageUtil.java | 169 ------------------ .../InMemoryMultiPassStrategy.java | 35 ---- .../MultiPassStrategy.java | 70 -------- .../WriteToFileMultiPassStrategy.java | 54 ------ .../cleartext_signatures/package-info.java | 8 - .../ClearsignedMessageUtil.kt | 153 ++++++++++++++++ .../InMemoryMultiPassStrategy.kt | 29 +++ .../cleartext_signatures/MultiPassStrategy.kt | 71 ++++++++ .../WriteToFileMultiPassStrategy.kt | 43 +++++ .../org/pgpainless/sop/InlineDetachImpl.java | 2 +- 10 files changed, 297 insertions(+), 337 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.java deleted file mode 100644 index cd0f6b35..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.java +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.cleartext_signatures; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.util.Strings; -import org.pgpainless.exception.WrongConsumingMethodException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.util.ArmoredInputStreamFactory; - -/** - * Utility class to deal with cleartext-signed messages. - * Based on Bouncycastle's {@link org.bouncycastle.openpgp.examples.ClearSignedFileProcessor}. - */ -public final class ClearsignedMessageUtil { - - private ClearsignedMessageUtil() { - - } - - /** - * Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided - * messageOutputStream. - * - * @param clearsignedInputStream input stream containing a clearsigned message - * @param messageOutputStream output stream to which the dearmored message shall be written - * @return signatures - * - * @throws IOException if the message is not clearsigned or some other IO error happens - * @throws WrongConsumingMethodException in case the armored message is not cleartext signed - */ - public static PGPSignatureList detachSignaturesFromInbandClearsignedMessage(InputStream clearsignedInputStream, - OutputStream messageOutputStream) - throws IOException, WrongConsumingMethodException { - ArmoredInputStream in; - if (clearsignedInputStream instanceof ArmoredInputStream) { - in = (ArmoredInputStream) clearsignedInputStream; - } else { - in = ArmoredInputStreamFactory.get(clearsignedInputStream); - } - - if (!in.isClearText()) { - throw new WrongConsumingMethodException("Message is not using the Cleartext Signature Framework."); - } - - OutputStream out = new BufferedOutputStream(messageOutputStream); - try { - ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); - int lookAhead = readInputLine(lineOut, in); - byte[] lineSep = getLineSeparator(); - - if (lookAhead != -1 && in.isClearText()) { - byte[] line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); - - while (lookAhead != -1 && in.isClearText()) { - lookAhead = readInputLine(lineOut, lookAhead, in); - line = lineOut.toByteArray(); - out.write(lineSep); - out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); - } - } else { - if (lookAhead != -1) { - byte[] line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); - } - } - } finally { - out.close(); - } - - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(in); - PGPSignatureList signatures = (PGPSignatureList) objectFactory.nextObject(); - - return signatures; - } - - public static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) - throws IOException { - bOut.reset(); - - int lookAhead = -1; - int ch; - - while ((ch = fIn.read()) >= 0) { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - - return lookAhead; - } - - public static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) - throws IOException { - bOut.reset(); - - int ch = lookAhead; - - do { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - while ((ch = fIn.read()) >= 0); - - if (ch < 0) { - lookAhead = -1; - } - - return lookAhead; - } - - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) - throws IOException { - int lookAhead = fIn.read(); - - if (lastCh == '\r' && lookAhead == '\n') { - bOut.write(lookAhead); - lookAhead = fIn.read(); - } - - return lookAhead; - } - - - private static byte[] getLineSeparator() { - String nl = Strings.lineSeparator(); - byte[] nlBytes = new byte[nl.length()]; - - for (int i = 0; i != nlBytes.length; i++) { - nlBytes[i] = (byte) nl.charAt(i); - } - - return nlBytes; - } - - private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isWhiteSpace(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isLineEnding(byte b) { - return b == '\r' || b == '\n'; - } - - private static boolean isWhiteSpace(byte b) { - return isLineEnding(b) || b == '\t' || b == ' '; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.java deleted file mode 100644 index 62433a2a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.java +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.cleartext_signatures; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - -/** - * Implementation of the {@link MultiPassStrategy}. - * This class keeps the read data in memory by caching the data inside a {@link ByteArrayOutputStream}. - * - * Note, that this class is suitable and efficient for processing small amounts of data. - * For larger data like encrypted files, use of the {@link WriteToFileMultiPassStrategy} is recommended to - * prevent {@link OutOfMemoryError OutOfMemoryErrors} and other issues. - */ -public class InMemoryMultiPassStrategy implements MultiPassStrategy { - - private final ByteArrayOutputStream cache = new ByteArrayOutputStream(); - - @Override - public ByteArrayOutputStream getMessageOutputStream() { - return cache; - } - - @Override - public ByteArrayInputStream getMessageInputStream() { - return new ByteArrayInputStream(getBytes()); - } - - public byte[] getBytes() { - return getMessageOutputStream().toByteArray(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.java deleted file mode 100644 index 5aa9f548..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.java +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.cleartext_signatures; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures, - * a strategy for how to cache the read data is required. - * Otherwise, large data kept in memory could cause {@link OutOfMemoryError OutOfMemoryErrors} or other issues. - * - * This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes - * to do verification. - * - * This interface can be used to write the signed data stream out via {@link #getMessageOutputStream()} and later - * get access to the data again via {@link #getMessageInputStream()}. - * Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away. - */ -public interface MultiPassStrategy { - - /** - * Provide an {@link OutputStream} into which the signed data can be read into. - * - * @return output stream - * @throws IOException io error - */ - OutputStream getMessageOutputStream() throws IOException; - - /** - * Provide an {@link InputStream} which contains the data that was previously written away in - * {@link #getMessageOutputStream()}. - * - * As there may be multiple signatures that need to be processed, each call of this method MUST return - * a new {@link InputStream}. - * - * @return input stream - * @throws IOException io error - */ - InputStream getMessageInputStream() throws IOException; - - /** - * Write the message content out to a file and re-read it to verify signatures. - * This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory. - * After the message has been processed completely, the messages content are available at the provided file. - * - * @param file target file - * @return strategy - */ - static MultiPassStrategy writeMessageToFile(File file) { - return new WriteToFileMultiPassStrategy(file); - } - - /** - * Read the message content into memory. - * This strategy is best suited for small messages which fit into memory. - * After the message has been processed completely, the message content can be accessed by calling - * {@link ByteArrayOutputStream#toByteArray()} on {@link #getMessageOutputStream()}. - * - * @return strategy - */ - static InMemoryMultiPassStrategy keepMessageInMemory() { - return new InMemoryMultiPassStrategy(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.java deleted file mode 100644 index 6c8d03ca..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.java +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.cleartext_signatures; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Implementation of the {@link MultiPassStrategy}. - * When processing signed data the first time, the data is being written out into a file. - * For the second pass, that file is being read again. - * - * This strategy is recommended when larger amounts of data need to be processed. - * For smaller files, {@link InMemoryMultiPassStrategy} yields higher efficiency. - */ -public class WriteToFileMultiPassStrategy implements MultiPassStrategy { - - private final File file; - - /** - * Create a {@link MultiPassStrategy} which writes data to a file. - * Note that {@link #getMessageOutputStream()} will create the file if necessary. - * - * @param file file to write the data to and read from - */ - public WriteToFileMultiPassStrategy(File file) { - this.file = file; - } - - @Override - public OutputStream getMessageOutputStream() throws IOException { - if (!file.exists()) { - boolean created = file.createNewFile(); - if (!created) { - throw new IOException("New file '" + file.getAbsolutePath() + "' was not created."); - } - } - return new FileOutputStream(file); - } - - @Override - public InputStream getMessageInputStream() throws IOException { - if (!file.exists()) { - throw new IOException("File '" + file.getAbsolutePath() + "' does no longer exist."); - } - return new FileInputStream(file); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/package-info.java deleted file mode 100644 index 3123338f..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to cleartext signature verification. - */ -package org.pgpainless.decryption_verification.cleartext_signatures; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt new file mode 100644 index 00000000..7a3b93ee --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.cleartext_signatures + +import org.bouncycastle.bcpg.ArmoredInputStream +import org.bouncycastle.openpgp.PGPSignatureList +import org.bouncycastle.util.Strings +import org.pgpainless.exception.WrongConsumingMethodException +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.util.ArmoredInputStreamFactory +import java.io.* +import kotlin.jvm.Throws + +/** + * Utility class to deal with cleartext-signed messages. + * Based on Bouncycastle's [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor]. + */ +class ClearsignedMessageUtil { + + companion object { + + /** + * Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided + * messageOutputStream. + * + * @param clearsignedInputStream input stream containing a clearsigned message + * @param messageOutputStream output stream to which the dearmored message shall be written + * @return signatures + * + * @throws IOException if the message is not clearsigned or some other IO error happens + * @throws WrongConsumingMethodException in case the armored message is not cleartext signed + */ + @JvmStatic + @Throws(WrongConsumingMethodException::class, IOException::class) + fun detachSignaturesFromInbandClearsignedMessage( + clearsignedInputStream: InputStream, + messageOutputStream: OutputStream): PGPSignatureList { + val input: ArmoredInputStream = if (clearsignedInputStream is ArmoredInputStream) { + clearsignedInputStream + } else { + ArmoredInputStreamFactory.get(clearsignedInputStream) + } + + if (!input.isClearText) { + throw WrongConsumingMethodException("Message isn't using the Cleartext Signature Framework.") + } + + BufferedOutputStream(messageOutputStream).use { output -> + val lineOut = ByteArrayOutputStream() + var lookAhead = readInputLine(lineOut, input) + val lineSep = getLineSeparator() + + if (lookAhead != -1 && input.isClearText) { + var line = lineOut.toByteArray() + output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)) + + while (lookAhead != -1 && input.isClearText) { + lookAhead = readInputLine(lineOut, lookAhead, input) + line = lineOut.toByteArray() + output.write(lineSep) + output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)) + } + } else { + if (lookAhead != -1) { + val line = lineOut.toByteArray() + output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)) + } + } + } + + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(input) + val next = objectFactory.nextObject() ?: PGPSignatureList(arrayOf()) + return next as PGPSignatureList + } + + @JvmStatic + private fun readInputLine(bOut: ByteArrayOutputStream, fIn: InputStream): Int { + bOut.reset() + + var lookAhead = -1 + var ch: Int + + while (fIn.read().also { ch = it } >= 0) { + bOut.write(ch) + if (ch == '\r'.code || ch == '\n'.code) { + lookAhead = readPassedEOL(bOut, ch, fIn) + break + } + } + + return lookAhead + } + + @JvmStatic + private fun readInputLine(bOut: ByteArrayOutputStream, lookAhead: Int, fIn: InputStream): Int { + var mLookAhead = lookAhead + bOut.reset() + var ch = mLookAhead + do { + bOut.write(ch) + if (ch == '\r'.code || ch == '\n'.code) { + mLookAhead = readPassedEOL(bOut, ch, fIn) + break + } + } while (fIn.read().also { ch = it } >= 0) + if (ch < 0) { + mLookAhead = -1 + } + return mLookAhead + } + + @JvmStatic + private fun readPassedEOL(bOut: ByteArrayOutputStream, lastCh: Int, fIn: InputStream): Int { + var lookAhead = fIn.read() + if (lastCh == '\r'.code && lookAhead == '\n'.code) { + bOut.write(lookAhead) + lookAhead = fIn.read() + } + return lookAhead + } + + @JvmStatic + private fun getLineSeparator(): ByteArray { + val nl = Strings.lineSeparator() + val nlBytes = ByteArray(nl.length) + for (i in nlBytes.indices) { + nlBytes[i] = nl[i].code.toByte() + } + return nlBytes + } + + @JvmStatic + private fun getLengthWithoutSeparatorOrTrailingWhitespace(line: ByteArray): Int { + var end = line.size - 1 + while (end >= 0 && isWhiteSpace(line[end])) { + end-- + } + return end + 1 + } + + @JvmStatic + private fun isLineEnding(b: Byte): Boolean { + return b == '\r'.code.toByte() || b == '\n'.code.toByte() + } + + @JvmStatic + private fun isWhiteSpace(b: Byte): Boolean { + return isLineEnding(b) || b == '\t'.code.toByte() || b == ' '.code.toByte() + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt new file mode 100644 index 00000000..eed6438f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.cleartext_signatures + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +/** + * Implementation of the [MultiPassStrategy]. + * This class keeps the read data in memory by caching the data inside a [ByteArrayOutputStream]. + * + * Note, that this class is suitable and efficient for processing small amounts of data. + * For larger data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to + * prevent [OutOfMemoryError] and other issues. + */ +class InMemoryMultiPassStrategy : MultiPassStrategy { + + private val cache = ByteArrayOutputStream() + + override val messageOutputStream: ByteArrayOutputStream + get() = cache + + override val messageInputStream: ByteArrayInputStream + get() = ByteArrayInputStream(getBytes()) + + fun getBytes(): ByteArray = messageOutputStream.toByteArray() +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt new file mode 100644 index 00000000..ae69b9c5 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.cleartext_signatures + +import java.io.* + +/** + * Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures, + * a strategy for how to cache the read data is required. + * Otherwise, large data kept in memory could cause an [OutOfMemoryError] or other issues. + * + * This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes + * to do verification. + * + * This interface can be used to write the signed data stream out via [messageOutputStream] and later + * get access to the data again via [messageInputStream]. + * Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away. + */ +interface MultiPassStrategy { + + /** + * Provide an [OutputStream] into which the signed data can be read into. + * + * @return output stream + * @throws IOException io error + */ + val messageOutputStream: OutputStream + + /** + * Provide an [InputStream] which contains the data that was previously written away in + * [messageOutputStream]. + * + * As there may be multiple signatures that need to be processed, each call of this method MUST return + * a new [InputStream]. + * + * @return input stream + * @throws IOException io error + */ + val messageInputStream: InputStream + + companion object { + + /** + * Write the message content out to a file and re-read it to verify signatures. + * This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory. + * After the message has been processed completely, the messages content are available at the provided file. + * + * @param file target file + * @return strategy + */ + @JvmStatic + fun writeMessageToFile(file: File): MultiPassStrategy { + return WriteToFileMultiPassStrategy(file) + } + + /** + * Read the message content into memory. + * This strategy is best suited for small messages which fit into memory. + * After the message has been processed completely, the message content can be accessed by calling + * [ByteArrayOutputStream.toByteArray] on [messageOutputStream]. + * + * @return strategy + */ + @JvmStatic + fun keepMessageInMemory(): InMemoryMultiPassStrategy { + return InMemoryMultiPassStrategy() + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt new file mode 100644 index 00000000..5d6567a4 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.cleartext_signatures + +import java.io.* + +/** + * Implementation of the [MultiPassStrategy]. + * When processing signed data the first time, the data is being written out into a file. + * For the second pass, that file is being read again. + * + * This strategy is recommended when larger amounts of data need to be processed. + * For smaller files, [InMemoryMultiPassStrategy] yields higher efficiency. + * + * @param file file to write the data to and read from + */ +class WriteToFileMultiPassStrategy( + private val file: File +) : MultiPassStrategy { + + override val messageOutputStream: OutputStream + @Throws(IOException::class) + get() { + if (!file.exists()) { + if (!file.createNewFile()) { + throw IOException("New file '${file.absolutePath}' could not be created.") + } + } + return FileOutputStream(file) + } + + override val messageInputStream: InputStream + @Throws(IOException::class) + get() { + if (!file.exists()) { + throw IOException("File '${file.absolutePath}' does no longer exist.") + } + return FileInputStream(file) + } + +} \ No newline at end of file diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineDetachImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineDetachImpl.java index bafc2794..da6e0917 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineDetachImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineDetachImpl.java @@ -69,7 +69,7 @@ public class InlineDetachImpl implements InlineDetach { if (armorIn.isClearText()) { try { signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(armorIn, messageOutputStream); - if (signatures == null) { + if (signatures.isEmpty()) { throw new SOPGPException.BadData("Data did not contain OpenPGP signatures."); } } catch (WrongConsumingMethodException e) { From 02511ac1ca6125e190bccdf139f7eb56f17714f5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 16:10:51 +0200 Subject: [PATCH 052/155] Kotlin conversion: SignatureVerification --- .../SignatureVerification.java | 106 ------------------ .../SignatureVerification.kt | 48 ++++++++ 2 files changed, 48 insertions(+), 106 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerification.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerification.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerification.java deleted file mode 100644 index 4a810eb9..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerification.java +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.util.encoders.Hex; -import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.key.SubkeyIdentifier; - -/** - * Tuple of a signature and an identifier of its corresponding verification key. - * Semantic meaning of the signature verification (success, failure) is merely given by context. - * E.g. {@link OpenPgpMetadata#getVerifiedInbandSignatures()} contains verified verifications, - * while the class {@link Failure} contains failed verifications. - */ -public class SignatureVerification { - - private final PGPSignature signature; - private final SubkeyIdentifier signingKey; - - /** - * Construct a verification tuple. - * - * @param signature PGPSignature object - * @param signingKey identifier of the signing key - */ - public SignatureVerification(PGPSignature signature, @Nullable SubkeyIdentifier signingKey) { - this.signature = signature; - this.signingKey = signingKey; - } - - /** - * Return the {@link PGPSignature}. - * - * @return signature - */ - public PGPSignature getSignature() { - return signature; - } - - /** - * Return a {@link SubkeyIdentifier} of the (sub-) key that is used for signature verification. - * Note, that this method might return null, e.g. in case of a {@link Failure} due to missing verification key. - * - * @return verification key identifier - */ - @Nullable - public SubkeyIdentifier getSigningKey() { - return signingKey; - } - - @Override - public String toString() { - return "Signature: " + (signature != null ? Hex.toHexString(signature.getDigestPrefix()) : "null") - + "; Key: " + (signingKey != null ? signingKey.toString() : "null") + ";"; - } - - /** - * Tuple object of a {@link SignatureVerification} and the corresponding {@link SignatureValidationException} - * that caused the verification to fail. - */ - public static class Failure { - - private final SignatureVerification signatureVerification; - private final SignatureValidationException validationException; - - /** - * Construct a signature verification failure object. - * - * @param verification verification - * @param validationException exception that caused the verification to fail - */ - public Failure(SignatureVerification verification, SignatureValidationException validationException) { - this.signatureVerification = verification; - this.validationException = validationException; - } - - /** - * Return the verification (tuple of {@link PGPSignature} and corresponding {@link SubkeyIdentifier}) - * of the signing/verification key. - * - * @return verification - */ - public SignatureVerification getSignatureVerification() { - return signatureVerification; - } - - /** - * Return the {@link SignatureValidationException} that caused the verification to fail. - * - * @return exception - */ - public SignatureValidationException getValidationException() { - return validationException; - } - - @Override - public String toString() { - return signatureVerification.toString() + " Failure: " + getValidationException().getMessage(); - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt new file mode 100644 index 00000000..a188f6a5 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.decryption_verification.SignatureVerification.Failure +import org.pgpainless.exception.SignatureValidationException +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.signature.SignatureUtils + +/** + * Tuple of a signature and an identifier of its corresponding verification key. + * Semantic meaning of the signature verification (success, failure) is merely given by context. + * E.g. [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications, + * while the class [Failure] contains failed verifications. + * + * @param signature PGPSignature object + * @param signingKey [SubkeyIdentifier] of the (sub-) key that is used for signature verification. + * Note, that this might be null, e.g. in case of a [Failure] due to missing verification key. + */ +data class SignatureVerification( + val signature: PGPSignature, + val signingKey: SubkeyIdentifier? +) { + + override fun toString(): String { + return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" + + " Key: ${signingKey?.toString() ?: "null"};" + } + + /** + * Tuple object of a [SignatureVerification] and the corresponding [SignatureValidationException] + * that caused the verification to fail. + * + * @param signatureVerification verification (tuple of [PGPSignature] and corresponding [SubkeyIdentifier]) + * @param validationException exception that caused the verification to fail + */ + data class Failure( + val signatureVerification: SignatureVerification, + val validationException: SignatureValidationException + ) { + override fun toString(): String { + return "$signatureVerification Failure: ${validationException.message}" + } + } +} \ No newline at end of file From 43335cbcd3b646d49102c558001adaa55d2a5c0a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 Aug 2023 16:22:21 +0200 Subject: [PATCH 053/155] Kotlin conversion: SessionKey --- .../java/org/pgpainless/util/SessionKey.java | 66 ------------------- .../kotlin/org/pgpainless/util/SessionKey.kt | 48 ++++++++++++++ 2 files changed, 48 insertions(+), 66 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/SessionKey.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/SessionKey.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/SessionKey.java b/pgpainless-core/src/main/java/org/pgpainless/util/SessionKey.java deleted file mode 100644 index 1e71bb03..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/SessionKey.java +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPSessionKey; -import org.bouncycastle.util.encoders.Hex; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; - -/** - * A {@link SessionKey} is the symmetric key that is used to encrypt/decrypt an OpenPGP message. - * The OpenPGP message header contains a copy of the session key, encrypted for the public key of each recipient. - */ -public class SessionKey { - - private final SymmetricKeyAlgorithm algorithm; - private final byte[] key; - - /** - * Constructor to create a session key from a BC {@link PGPSessionKey} object. - * - * @param sessionKey BC session key - */ - public SessionKey(@Nonnull PGPSessionKey sessionKey) { - this(SymmetricKeyAlgorithm.requireFromId(sessionKey.getAlgorithm()), sessionKey.getKey()); - } - - /** - * Create a session key object from an algorithm and a key. - * - * @param algorithm algorithm - * @param key key - */ - public SessionKey(@Nonnull SymmetricKeyAlgorithm algorithm, @Nonnull byte[] key) { - this.algorithm = algorithm; - this.key = key; - } - - /** - * Return the symmetric key algorithm. - * - * @return algorithm - */ - public SymmetricKeyAlgorithm getAlgorithm() { - return algorithm; - } - - /** - * Return the bytes of the key. - * - * @return key - */ - public byte[] getKey() { - byte[] copy = new byte[key.length]; - System.arraycopy(key, 0, copy, 0, copy.length); - return copy; - } - - @Override - public String toString() { - return "" + getAlgorithm().getAlgorithmId() + ":" + Hex.toHexString(getKey()); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/SessionKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/SessionKey.kt new file mode 100644 index 00000000..894d0869 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/SessionKey.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +import org.bouncycastle.openpgp.PGPSessionKey +import org.bouncycastle.util.encoders.Hex +import org.pgpainless.algorithm.SymmetricKeyAlgorithm + +/** + * A [SessionKey] is the symmetric key that is used to encrypt/decrypt an OpenPGP message payload. + * The OpenPGP message header contains a copy of the session key, encrypted for the public key of each recipient. + * + * @param algorithm symmetric key algorithm + * @param key bytes of the key + */ +data class SessionKey(val algorithm: SymmetricKeyAlgorithm, + val key: ByteArray) { + + /** + * Constructor to create a session key from a BC [PGPSessionKey] object. + * + * @param sessionKey BC session key + */ + constructor(sessionKey: PGPSessionKey): + this(SymmetricKeyAlgorithm.requireFromId(sessionKey.algorithm), sessionKey.key) + + override fun toString(): String { + return "${algorithm.algorithmId}:${Hex.toHexString(key)}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SessionKey + + if (algorithm != other.algorithm) return false + if (!key.contentEquals(other.key)) return false + + return true + } + + override fun hashCode(): Int { + return 31 * algorithm.hashCode() + key.contentHashCode() + } +} \ No newline at end of file From eaef1fe44aeb57d281d89ea14157d824a794e810 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Oct 2023 12:33:45 +0200 Subject: [PATCH 054/155] Kotlin conversion: KeyRingBuilder --- .../key/generation/KeyRingBuilder.java | 313 ------------------ .../generation/KeyRingBuilderInterface.java | 45 --- .../generation/KeySpecBuilderInterface.java | 26 -- .../key/generation/KeyRingBuilder.kt | 236 +++++++++++++ .../key/generation/KeyRingBuilderInterface.kt | 34 ++ .../key/generation/KeySpecBuilderInterface.kt | 23 ++ 6 files changed, 293 insertions(+), 384 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilderInterface.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java deleted file mode 100644 index 84251c12..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java +++ /dev/null @@ -1,313 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - - -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyPair; -import org.bouncycastle.openpgp.PGPKeyRingGenerator; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.PGPDigestCalculator; -import org.bouncycastle.util.Strings; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.generation.type.KeyType; -import org.pgpainless.key.protection.UnlockSecretKey; -import org.pgpainless.policy.Policy; -import org.pgpainless.provider.ProviderFactory; -import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; -import org.pgpainless.util.Passphrase; - -public class KeyRingBuilder implements KeyRingBuilderInterface { - - private static final long YEAR_IN_SECONDS = 1000L * 60 * 60 * 24 * 365; - - private KeySpec primaryKeySpec; - private final List subkeySpecs = new ArrayList<>(); - private final Map userIds = new LinkedHashMap<>(); - private Passphrase passphrase = Passphrase.emptyPassphrase(); - private Date expirationDate = new Date(System.currentTimeMillis() + YEAR_IN_SECONDS * 5); // Expiration in 5 years - - @Override - public KeyRingBuilder setPrimaryKey(@Nonnull KeySpec keySpec) { - verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy()); - verifyMasterKeyCanCertify(keySpec); - this.primaryKeySpec = keySpec; - return this; - } - - @Override - public KeyRingBuilder addSubkey(@Nonnull KeySpec keySpec) { - verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy()); - this.subkeySpecs.add(keySpec); - return this; - } - - @Override - public KeyRingBuilder addUserId(@Nonnull String userId) { - this.userIds.put(userId.trim(), null); - return this; - } - - public KeyRingBuilder addUserId( - @Nonnull String userId, - @Nullable SelfSignatureSubpackets.Callback subpacketsCallback) { - this.userIds.put(userId.trim(), subpacketsCallback); - return this; - } - - @Override - public KeyRingBuilder addUserId(@Nonnull byte[] userId) { - return addUserId(Strings.fromUTF8ByteArray(userId)); - } - - @Override - public KeyRingBuilder setExpirationDate(@Nullable Date expirationDate) { - if (expirationDate == null) { - // No expiration - this.expirationDate = null; - return this; - } - - Date now = new Date(); - if (now.after(expirationDate)) { - throw new IllegalArgumentException("Expiration date must be in the future."); - } - this.expirationDate = expirationDate; - return this; - } - - @Override - public KeyRingBuilder setPassphrase(@Nonnull Passphrase passphrase) { - this.passphrase = passphrase; - return this; - } - - private void verifyKeySpecCompliesToPolicy(KeySpec keySpec, Policy policy) { - PublicKeyAlgorithm publicKeyAlgorithm = keySpec.getKeyType().getAlgorithm(); - int bitStrength = keySpec.getKeyType().getBitStrength(); - - if (!policy.getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) { - throw new IllegalArgumentException("Public key algorithm policy violation: " + - publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable."); - } - } - - private void verifyMasterKeyCanCertify(KeySpec spec) { - if (!keyIsCertificationCapable(spec)) { - throw new IllegalArgumentException("Key algorithm " + spec.getKeyType().getName() + " is not capable of creating certifications."); - } - } - - private boolean keyIsCertificationCapable(KeySpec keySpec) { - return keySpec.getKeyType().canCertify(); - } - - @Override - public PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException, - InvalidAlgorithmParameterException { - PGPDigestCalculator keyFingerprintCalculator = ImplementationFactory.getInstance().getV4FingerprintCalculator(); - PBESecretKeyEncryptor secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator); - PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor(); - - passphrase.clear(); - - // Generate Primary Key - PGPKeyPair certKey = generateKeyPair(primaryKeySpec); - PGPContentSignerBuilder signer = buildContentSigner(certKey); - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signer); - - SignatureSubpackets hashedSubPacketGenerator = primaryKeySpec.getSubpacketGenerator(); - hashedSubPacketGenerator.setIssuerFingerprintAndKeyId(certKey.getPublicKey()); - if (expirationDate != null) { - hashedSubPacketGenerator.setKeyExpirationTime(certKey.getPublicKey(), expirationDate); - } - if (!userIds.isEmpty()) { - hashedSubPacketGenerator.setPrimaryUserId(); - } - - PGPSignatureSubpacketGenerator generator = new PGPSignatureSubpacketGenerator(); - SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator); - PGPSignatureSubpacketVector hashedSubPackets = generator.generate(); - PGPKeyRingGenerator ringGenerator; - if (userIds.isEmpty()) { - ringGenerator = new PGPKeyRingGenerator( - certKey, - keyFingerprintCalculator, - hashedSubPackets, - null, - signer, - secretKeyEncryptor); - } else { - String primaryUserId = userIds.entrySet().iterator().next().getKey(); - ringGenerator = new PGPKeyRingGenerator( - SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey, - primaryUserId, keyFingerprintCalculator, - hashedSubPackets, null, signer, secretKeyEncryptor); - } - - addSubKeys(certKey, ringGenerator); - - // Generate secret key ring with only primary user id - PGPSecretKeyRing secretKeyRing = ringGenerator.generateSecretKeyRing(); - - Iterator secretKeys = secretKeyRing.getSecretKeys(); - - // Attempt to add additional user-ids to the primary public key - PGPPublicKey primaryPubKey = secretKeys.next().getPublicKey(); - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.getSecretKey(), secretKeyDecryptor); - Iterator> userIdIterator = - this.userIds.entrySet().iterator(); - if (userIdIterator.hasNext()) { - userIdIterator.next(); // Skip primary user id - } - while (userIdIterator.hasNext()) { - Map.Entry additionalUserId = userIdIterator.next(); - String userIdString = additionalUserId.getKey(); - SelfSignatureSubpackets.Callback callback = additionalUserId.getValue(); - SelfSignatureSubpackets subpackets = null; - if (callback == null) { - subpackets = hashedSubPacketGenerator; - subpackets.setPrimaryUserId(null); - // additional user-ids are not primary - } else { - subpackets = SignatureSubpackets.createHashedSubpackets(primaryPubKey); - callback.modifyHashedSubpackets(subpackets); - } - signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.getCode(), privateKey); - signatureGenerator.setHashedSubpackets( - SignatureSubpacketsHelper.toVector((SignatureSubpackets) subpackets)); - PGPSignature additionalUserIdSignature = - signatureGenerator.generateCertification(userIdString, primaryPubKey); - primaryPubKey = PGPPublicKey.addCertification(primaryPubKey, - userIdString, additionalUserIdSignature); - } - - // "reassemble" secret key ring with modified primary key - PGPSecretKey primarySecKey = new PGPSecretKey( - privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor); - List secretKeyList = new ArrayList<>(); - secretKeyList.add(primarySecKey); - while (secretKeys.hasNext()) { - secretKeyList.add(secretKeys.next()); - } - secretKeyRing = new PGPSecretKeyRing(secretKeyList); - return secretKeyRing; - } - - private void addSubKeys(PGPKeyPair primaryKey, PGPKeyRingGenerator ringGenerator) - throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException { - for (KeySpec subKeySpec : subkeySpecs) { - PGPKeyPair subKey = generateKeyPair(subKeySpec); - if (subKeySpec.isInheritedSubPackets()) { - ringGenerator.addSubKey(subKey); - } else { - PGPSignatureSubpacketVector hashedSubpackets = subKeySpec.getSubpackets(); - try { - hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary( - primaryKey, subKey, hashedSubpackets); - } catch (IOException e) { - throw new PGPException("Exception while adding primary key binding signature to signing subkey", e); - } - ringGenerator.addSubKey(subKey, hashedSubpackets, null); - } - } - } - - private PGPSignatureSubpacketVector addPrimaryKeyBindingSignatureIfNecessary( - PGPKeyPair primaryKey, PGPKeyPair subKey, PGPSignatureSubpacketVector hashedSubpackets) - throws PGPException, IOException { - int keyFlagMask = hashedSubpackets.getKeyFlags(); - if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) && - !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) { - return hashedSubpackets; - } - - PGPSignatureGenerator bindingSignatureGenerator = new PGPSignatureGenerator(buildContentSigner(subKey)); - bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.getCode(), subKey.getPrivateKey()); - PGPSignature primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.getPublicKey(), subKey.getPublicKey()); - PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(hashedSubpackets); - subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig); - return subpacketGenerator.generate(); - } - - private PGPContentSignerBuilder buildContentSigner(PGPKeyPair certKey) { - HashAlgorithm hashAlgorithm = PGPainless.getPolicy() - .getSignatureHashAlgorithmPolicy().defaultHashAlgorithm(); - return ImplementationFactory.getInstance().getPGPContentSignerBuilder( - certKey.getPublicKey().getAlgorithm(), - hashAlgorithm.getAlgorithmId()); - } - - private PBESecretKeyEncryptor buildSecretKeyEncryptor(PGPDigestCalculator keyFingerprintCalculator) { - SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy() - .getSymmetricKeyEncryptionAlgorithmPolicy() - .getDefaultSymmetricKeyAlgorithm(); - if (!passphrase.isValid()) { - throw new IllegalStateException("Passphrase was cleared."); - } - return passphrase.isEmpty() ? null : // unencrypted key pair, otherwise AES-256 encrypted - ImplementationFactory.getInstance().getPBESecretKeyEncryptor( - keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase); - } - - private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException { - if (!passphrase.isValid()) { - throw new IllegalStateException("Passphrase was cleared."); - } - return passphrase.isEmpty() ? null : - ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); - } - - public static PGPKeyPair generateKeyPair(KeySpec spec) - throws NoSuchAlgorithmException, PGPException, - InvalidAlgorithmParameterException { - KeyType type = spec.getKeyType(); - KeyPairGenerator certKeyGenerator = KeyPairGenerator.getInstance(type.getName(), - ProviderFactory.getProvider()); - certKeyGenerator.initialize(type.getAlgorithmSpec()); - - // Create raw Key Pair - KeyPair keyPair = certKeyGenerator.generateKeyPair(); - - Date keyCreationDate = spec.getKeyCreationDate() != null ? spec.getKeyCreationDate() : new Date(); - - // Form PGP key pair - PGPKeyPair pgpKeyPair = ImplementationFactory.getInstance() - .getPGPKeyPair(type.getAlgorithm(), keyPair, keyCreationDate); - return pgpKeyPair; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java deleted file mode 100644 index ecff123b..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.util.Date; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.pgpainless.key.util.UserId; -import org.pgpainless.util.Passphrase; - -public interface KeyRingBuilderInterface> { - - B setPrimaryKey(@Nonnull KeySpec keySpec); - - default B setPrimaryKey(@Nonnull KeySpecBuilder builder) { - return setPrimaryKey(builder.build()); - } - - B addSubkey(@Nonnull KeySpec keySpec); - - default B addSubkey(@Nonnull KeySpecBuilder builder) { - return addSubkey(builder.build()); - } - - default B addUserId(UserId userId) { - return addUserId(userId.toString()); - } - - B addUserId(@Nonnull String userId); - - B addUserId(@Nonnull byte[] userId); - - B setExpirationDate(@Nonnull Date expirationDate); - - B setPassphrase(@Nonnull Passphrase passphrase); - - PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException, - InvalidAlgorithmParameterException; -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilderInterface.java deleted file mode 100644 index 4a99bc8d..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilderInterface.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - -import javax.annotation.Nonnull; - -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; - -import java.util.Date; - -public interface KeySpecBuilderInterface { - - KeySpecBuilder overridePreferredCompressionAlgorithms(@Nonnull CompressionAlgorithm... compressionAlgorithms); - - KeySpecBuilder overridePreferredHashAlgorithms(@Nonnull HashAlgorithm... preferredHashAlgorithms); - - KeySpecBuilder overridePreferredSymmetricKeyAlgorithms(@Nonnull SymmetricKeyAlgorithm... preferredSymmetricKeyAlgorithms); - - KeySpecBuilder setKeyCreationDate(@Nonnull Date creationDate); - - KeySpec build(); -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt new file mode 100644 index 00000000..af650c4b --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -0,0 +1,236 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation + +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder +import org.bouncycastle.openpgp.operator.PGPDigestCalculator +import org.bouncycastle.util.Strings +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.algorithm.SignatureType +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.protection.UnlockSecretKey +import org.pgpainless.policy.Policy +import org.pgpainless.provider.ProviderFactory +import org.pgpainless.signature.subpackets.SelfSignatureSubpackets +import org.pgpainless.signature.subpackets.SignatureSubpackets +import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper +import org.pgpainless.util.Passphrase +import java.io.IOException +import java.security.KeyPairGenerator +import java.util.* + +const val MILLIS_IN_YEAR = 1000L * 60 * 60 * 24 * 365 + +class KeyRingBuilder : KeyRingBuilderInterface { + + private var primaryKeySpec: KeySpec? = null + private val subKeySpecs = mutableListOf() + private val userIds = mutableMapOf() + private var passphrase = Passphrase.emptyPassphrase() + private var expirationDate: Date? = Date(System.currentTimeMillis() + (5 * MILLIS_IN_YEAR)) + + override fun setPrimaryKey(keySpec: KeySpec): KeyRingBuilder = apply { + verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy()) + verifyPrimaryKeyCanCertify(keySpec) + this.primaryKeySpec = keySpec + } + + override fun addSubkey(keySpec: KeySpec): KeyRingBuilder = apply { + verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy()) + subKeySpecs.add(keySpec) + } + + override fun addUserId(userId: CharSequence): KeyRingBuilder = apply { + userIds[userId.toString().trim()] = null + } + + override fun addUserId(userId: ByteArray): KeyRingBuilder = addUserId(Strings.fromUTF8ByteArray(userId)) + + override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply { + if (expirationDate == null) { + this.expirationDate = null + return@apply + } + this.expirationDate = expirationDate.let { + require(Date() < expirationDate) { + "Expiration date must be in the future." + } + expirationDate + } + } + + override fun setPassphrase(passphrase: Passphrase): KeyRingBuilder = apply { + this.passphrase = passphrase + } + + private fun verifyKeySpecCompliesToPolicy(keySpec: KeySpec, policy: Policy) { + val algorithm = keySpec.keyType.algorithm + val bitStrength = keySpec.keyType.bitStrength + require(policy.publicKeyAlgorithmPolicy.isAcceptable(algorithm, bitStrength)) { + "Public key algorithm policy violation: $algorithm with bit strength $bitStrength is not acceptable." + } + } + + private fun verifyPrimaryKeyCanCertify(spec: KeySpec) { + require(keyIsCertificationCapable(spec)) { + "Key algorithm ${spec.keyType.name} is not capable of creation certifications." + } + } + + private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify() + + override fun build(): PGPSecretKeyRing { + val keyFingerprintCalculator = ImplementationFactory.getInstance() + .v4FingerprintCalculator + val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator) + val secretKeyDecryptor = buildSecretKeyDecryptor() + + passphrase.clear() // Passphrase was used above, so we can get rid of it + + // generate primary key + requireNotNull(primaryKeySpec) { + "Primary Key spec required." + } + val certKey = generateKeyPair(primaryKeySpec!!) + val signer = buildContentSigner(certKey) + val signatureGenerator = PGPSignatureGenerator(signer) + + val hashedSubPacketGenerator = primaryKeySpec!!.subpacketGenerator + hashedSubPacketGenerator.setIssuerFingerprintAndKeyId(certKey.publicKey) + expirationDate?.let { hashedSubPacketGenerator.setKeyExpirationTime(certKey.publicKey, it) } + if (userIds.isNotEmpty()) { + hashedSubPacketGenerator.setPrimaryUserId() + } + + val generator = PGPSignatureSubpacketGenerator() + SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator) + val hashedSubPackets = generator.generate() + val ringGenerator = if (userIds.isEmpty()) { + PGPKeyRingGenerator(certKey, keyFingerprintCalculator, hashedSubPackets, null, signer, + secretKeyEncryptor) + } else { + userIds.keys.first().let { primaryUserId -> + PGPKeyRingGenerator(SignatureType.POSITIVE_CERTIFICATION.code, certKey, primaryUserId, + keyFingerprintCalculator, hashedSubPackets, null, signer, + secretKeyEncryptor) + } + } + + addSubKeys(certKey, ringGenerator) + + // Generate secret key ring with only primary userId + val secretKeyRing = ringGenerator.generateSecretKeyRing() + val secretKeys = secretKeyRing.secretKeys + + // Attempt to add additional user-ids to the primary public key + var primaryPubKey = secretKeys.next().publicKey + val privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.secretKey, secretKeyDecryptor) + val userIdIterator = userIds.entries.iterator() + if (userIdIterator.hasNext()) { + userIdIterator.next() // Skip primary userId + } + while (userIdIterator.hasNext()) { + val additionalUserId = userIdIterator.next() + val userIdString = additionalUserId.key + val callback = additionalUserId.value + val subpackets = if (callback == null) { + hashedSubPacketGenerator.also { it.setPrimaryUserId(null) } + } else { + SignatureSubpackets.createHashedSubpackets(primaryPubKey).also { callback.modifyHashedSubpackets(it) } + } + signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey) + signatureGenerator.setHashedSubpackets( + SignatureSubpacketsHelper.toVector(subpackets)) + val additionalUserIdSignature = signatureGenerator.generateCertification(userIdString, primaryPubKey) + primaryPubKey = PGPPublicKey.addCertification(primaryPubKey, userIdString, additionalUserIdSignature) + } + + // Reassemble secret key ring with modified primary key + val primarySecretKey = PGPSecretKey( + privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor) + val secretKeyList = mutableListOf(primarySecretKey) + while (secretKeys.hasNext()) { + secretKeyList.add(secretKeys.next()) + } + return PGPSecretKeyRing(secretKeyList) + } + + private fun addSubKeys(primaryKey: PGPKeyPair, ringGenerator: PGPKeyRingGenerator) { + for (subKeySpec in subKeySpecs) { + val subKey = generateKeyPair(subKeySpec) + if (subKeySpec.isInheritedSubPackets) { + ringGenerator.addSubKey(subKey) + } else { + var hashedSubpackets = subKeySpec.subpackets + try { + hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(primaryKey, subKey, hashedSubpackets) + } catch (e: IOException) { + throw PGPException("Exception while adding primary key binding signature to signing subkey.", e) + } + ringGenerator.addSubKey(subKey, hashedSubpackets, null) + } + } + } + + private fun addPrimaryKeyBindingSignatureIfNecessary(primaryKey: PGPKeyPair, subKey: PGPKeyPair, hashedSubpackets: PGPSignatureSubpacketVector): PGPSignatureSubpacketVector { + val keyFlagMask = hashedSubpackets.keyFlags + if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) && + !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) { + return hashedSubpackets + } + + val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey)) + bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey) + val primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey) + val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets) + subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig) + return subpacketGenerator.generate() + } + + private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder { + val hashAlgorithm = PGPainless.getPolicy().signatureHashAlgorithmPolicy.defaultHashAlgorithm + return ImplementationFactory.getInstance().getPGPContentSignerBuilder( + certKey.publicKey.algorithm, hashAlgorithm.algorithmId) + } + + private fun buildSecretKeyEncryptor(keyFingerprintCalculator: PGPDigestCalculator): PBESecretKeyEncryptor? { + val keyEncryptionAlgorithm = PGPainless.getPolicy().symmetricKeyEncryptionAlgorithmPolicy.defaultSymmetricKeyAlgorithm + check(passphrase.isValid) { + "Passphrase was cleared." + } + return if (passphrase.isEmpty) null else ImplementationFactory.getInstance() + .getPBESecretKeyEncryptor(keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase) + } + + private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? { + check(passphrase.isValid) { + "Passphrase was cleared." + } + return if (passphrase.isEmpty) null else ImplementationFactory.getInstance() + .getPBESecretKeyDecryptor(passphrase) + } + + companion object { + @JvmStatic + fun generateKeyPair(spec: KeySpec): PGPKeyPair { + spec.keyType.let { type -> + // Create raw Key Pair + val keyPair = KeyPairGenerator.getInstance(type.name, ProviderFactory.getProvider()) + .also { it.initialize(type.algorithmSpec) } + .generateKeyPair() + + val keyCreationDate = spec.keyCreationDate ?: Date() + // Form PGP Key Pair + return ImplementationFactory.getInstance() + .getPGPKeyPair(type.algorithm, keyPair, keyCreationDate) + } + } + } + +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt new file mode 100644 index 00000000..0ed301fe --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation + +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.pgpainless.util.Passphrase +import java.security.InvalidAlgorithmParameterException +import java.security.NoSuchAlgorithmException +import java.util.* + +interface KeyRingBuilderInterface> { + + fun setPrimaryKey(keySpec: KeySpec): B + + fun setPrimaryKey(builder: KeySpecBuilder): B = setPrimaryKey(builder.build()) + + fun addSubkey(keySpec: KeySpec): B + + fun addSubkey(builder: KeySpecBuilder): B = addSubkey(builder.build()) + + fun addUserId(userId: CharSequence): B + + fun addUserId(userId: ByteArray): B + + fun setExpirationDate(expirationDate: Date?): B + + fun setPassphrase(passphrase: Passphrase): B + + @Throws(NoSuchAlgorithmException::class, PGPException::class, InvalidAlgorithmParameterException::class) + fun build(): PGPSecretKeyRing +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt new file mode 100644 index 00000000..fcafb9c1 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation + +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import java.util.* + +interface KeySpecBuilderInterface { + + fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder + + fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder + + fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder + + fun setKeyCreationDate(creationDate: Date): KeySpecBuilder + + fun build(): KeySpec +} \ No newline at end of file From bb17c627ce1417b26f1bd585786587e65239c869 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Oct 2023 12:43:16 +0200 Subject: [PATCH 055/155] Kotlin conversion: KeySpecBuilder --- .../key/generation/KeySpecBuilder.java | 88 ------------------- .../key/generation/KeySpecBuilder.kt | 63 +++++++++++++ 2 files changed, 63 insertions(+), 88 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilder.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilder.java deleted file mode 100644 index 1abd7dcd..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilder.java +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - -import java.util.Arrays; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.Set; -import javax.annotation.Nonnull; - -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.AlgorithmSuite; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.Feature; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.key.generation.type.KeyType; -import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; - -public class KeySpecBuilder implements KeySpecBuilderInterface { - - private final KeyType type; - private final KeyFlag[] keyFlags; - private final SelfSignatureSubpackets hashedSubpackets = new SignatureSubpackets(); - private final AlgorithmSuite algorithmSuite = PGPainless.getPolicy().getKeyGenerationAlgorithmSuite(); - private Set preferredCompressionAlgorithms = algorithmSuite.getCompressionAlgorithms(); - private Set preferredHashAlgorithms = algorithmSuite.getHashAlgorithms(); - private Set preferredSymmetricAlgorithms = algorithmSuite.getSymmetricKeyAlgorithms(); - private Date keyCreationDate; - - KeySpecBuilder(@Nonnull KeyType type, KeyFlag... flags) { - if (flags == null) { - this.keyFlags = new KeyFlag[0]; - } else { - SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, flags); - this.keyFlags = flags; - } - this.type = type; - } - - @Override - public KeySpecBuilder overridePreferredCompressionAlgorithms( - @Nonnull CompressionAlgorithm... compressionAlgorithms) { - this.preferredCompressionAlgorithms = new LinkedHashSet<>(Arrays.asList(compressionAlgorithms)); - return this; - } - - @Override - public KeySpecBuilder overridePreferredHashAlgorithms( - @Nonnull HashAlgorithm... preferredHashAlgorithms) { - this.preferredHashAlgorithms = new LinkedHashSet<>(Arrays.asList(preferredHashAlgorithms)); - return this; - } - - @Override - public KeySpecBuilder overridePreferredSymmetricKeyAlgorithms( - @Nonnull SymmetricKeyAlgorithm... preferredSymmetricKeyAlgorithms) { - for (SymmetricKeyAlgorithm algo : preferredSymmetricKeyAlgorithms) { - if (algo == SymmetricKeyAlgorithm.NULL) { - throw new IllegalArgumentException("NULL (unencrypted) is an invalid symmetric key algorithm preference."); - } - } - this.preferredSymmetricAlgorithms = new LinkedHashSet<>(Arrays.asList(preferredSymmetricKeyAlgorithms)); - return this; - } - - @Override - public KeySpecBuilder setKeyCreationDate(@Nonnull Date creationDate) { - this.keyCreationDate = creationDate; - return this; - } - - @Override - public KeySpec build() { - this.hashedSubpackets.setKeyFlags(keyFlags); - this.hashedSubpackets.setPreferredCompressionAlgorithms(preferredCompressionAlgorithms); - this.hashedSubpackets.setPreferredHashAlgorithms(preferredHashAlgorithms); - this.hashedSubpackets.setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms); - this.hashedSubpackets.setFeatures(Feature.MODIFICATION_DETECTION); - - return new KeySpec(type, (SignatureSubpackets) hashedSubpackets, false, keyCreationDate); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt new file mode 100644 index 00000000..a430e796 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation + +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.* +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.signature.subpackets.SelfSignatureSubpackets +import org.pgpainless.signature.subpackets.SignatureSubpackets +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil +import java.util.* + +class KeySpecBuilder constructor( + private val type: KeyType, + private val keyFlags: List, +) : KeySpecBuilderInterface { + + private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets() + private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite + private var preferredCompressionAlgorithms: Set = algorithmSuite.compressionAlgorithms + private var preferredHashAlgorithms: Set = algorithmSuite.hashAlgorithms + private var preferredSymmetricAlgorithms: Set = algorithmSuite.symmetricKeyAlgorithms + private var keyCreationDate = Date() + + constructor(type: KeyType, vararg keyFlags: KeyFlag): this(type, listOf(*keyFlags)) + + init { + SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, *keyFlags.toTypedArray()) + } + + override fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder = apply { + this.preferredCompressionAlgorithms = algorithms.toSet() + } + + override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder = apply { + this.preferredHashAlgorithms = algorithms.toSet() + } + + override fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder = apply { + require(!algorithms.contains(SymmetricKeyAlgorithm.NULL)) { + "NULL (unencrypted) is an invalid symmetric key algorithm preference." + } + this.preferredSymmetricAlgorithms = algorithms.toSet() + } + + override fun setKeyCreationDate(creationDate: Date): KeySpecBuilder = apply { + this.keyCreationDate = creationDate + } + + override fun build(): KeySpec { + return hashedSubpackets.apply { + setKeyFlags(keyFlags) + setPreferredCompressionAlgorithms(preferredCompressionAlgorithms) + setPreferredHashAlgorithms(preferredHashAlgorithms) + setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms) + setFeatures(Feature.MODIFICATION_DETECTION) + }.let { + KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate) + } + } +} \ No newline at end of file From 41f56bdf99ab7db8d992f8bc9a447d95228f7a7a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Oct 2023 12:44:15 +0200 Subject: [PATCH 056/155] Kotlin conversion: KeySpec --- .../pgpainless/key/generation/KeySpec.java | 62 ------------------- .../org/pgpainless/key/generation/KeySpec.kt | 28 +++++++++ 2 files changed, 28 insertions(+), 62 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpec.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpec.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpec.java deleted file mode 100644 index 6ac08aa7..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpec.java +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.key.generation.type.KeyType; -import org.pgpainless.signature.subpackets.SignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; - -import java.util.Date; - -public class KeySpec { - - private final KeyType keyType; - private final SignatureSubpackets subpacketGenerator; - private final boolean inheritedSubPackets; - private final Date keyCreationDate; - - KeySpec(@Nonnull KeyType type, - @Nonnull SignatureSubpackets subpacketGenerator, - boolean inheritedSubPackets, - @Nullable Date keyCreationDate) { - this.keyType = type; - this.subpacketGenerator = subpacketGenerator; - this.inheritedSubPackets = inheritedSubPackets; - this.keyCreationDate = keyCreationDate; - } - - @Nonnull - public KeyType getKeyType() { - return keyType; - } - - @Nonnull - public PGPSignatureSubpacketVector getSubpackets() { - return SignatureSubpacketsHelper.toVector(subpacketGenerator); - } - - @Nonnull - public SignatureSubpackets getSubpacketGenerator() { - return subpacketGenerator; - } - - boolean isInheritedSubPackets() { - return inheritedSubPackets; - } - - @Nullable - public Date getKeyCreationDate() { - return keyCreationDate; - } - - public static KeySpecBuilder getBuilder(KeyType type, KeyFlag... flags) { - return new KeySpecBuilder(type, flags); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt new file mode 100644 index 00000000..fd0b8635 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation + +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.signature.subpackets.SignatureSubpackets +import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper +import java.util.* + +data class KeySpec( + val keyType: KeyType, + val subpacketGenerator: SignatureSubpackets, + val isInheritedSubPackets: Boolean, + val keyCreationDate: Date +) { + + val subpackets: PGPSignatureSubpacketVector + get() = SignatureSubpacketsHelper.toVector(subpacketGenerator) + + companion object { + @JvmStatic + fun getBuilder(type: KeyType, vararg flags: KeyFlag) = KeySpecBuilder(type, *flags) + } +} \ No newline at end of file From b343b4ad1725b30da2375580d9a86d7de6e162a9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 16:41:00 +0200 Subject: [PATCH 057/155] Kotlin conversion: KeyRingTemplates --- .../key/generation/KeyRingTemplates.java | 255 ------------------ .../key/generation/KeyRingTemplates.kt | 185 +++++++++++++ 2 files changed, 185 insertions(+), 255 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java deleted file mode 100644 index 6966232b..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.key.generation.type.KeyType; -import org.pgpainless.key.generation.type.eddsa.EdDSACurve; -import org.pgpainless.key.generation.type.rsa.RsaLength; -import org.pgpainless.key.generation.type.xdh.XDHSpec; -import org.pgpainless.util.Passphrase; - -public final class KeyRingTemplates { - - public KeyRingTemplates() { - - } - - /** - * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, - * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. - * - * @param userId userId or null - * @param length length of the RSA keys - * @return key - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId, - @Nonnull RsaLength length) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - return rsaKeyRing(userId, length, Passphrase.emptyPassphrase()); - } - - /** - * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, - * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. - * - * @param userId userId or null - * @param length length of the RSA keys - * @param password passphrase to encrypt the key with - * @return key - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId, - @Nonnull RsaLength length, - @Nonnull String password) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - Passphrase passphrase = Passphrase.emptyPassphrase(); - if (!isNullOrEmpty(password)) { - passphrase = Passphrase.fromPassword(password); - } - return rsaKeyRing(userId, length, passphrase); - } - - /** - * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, - * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. - * - * @param userId userId or null - * @param length length of the RSA keys - * @param passphrase passphrase to encrypt the key with - * @return key - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId, - @Nonnull RsaLength length, - @Nonnull Passphrase passphrase) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - KeyRingBuilder builder = PGPainless.buildKeyRing() - .setPrimaryKey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER)) - .addSubkey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA)) - .addSubkey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)); - - if (userId != null) { - builder.addUserId(userId.toString()); - } - - if (!passphrase.isEmpty()) { - builder.setPassphrase(passphrase); - } - - return builder.build(); - } - - /** - * Creates a simple, unencrypted RSA KeyPair of length {@code length} with user-id {@code userId}. - * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. - * - * @param userId user id. - * @param length length in bits. - * - * @return {@link PGPSecretKeyRing} containing the KeyPair. - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { - return simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase()); - } - - public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length, @Nonnull Passphrase passphrase) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - KeyRingBuilder builder = PGPainless.buildKeyRing() - .setPrimaryKey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS)) - .setPassphrase(passphrase); - if (userId != null) { - builder.addUserId(userId.toString()); - } - return builder.build(); - } - - /** - * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. - * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. - * - * @param userId user id. - * @param length length in bits. - * @param password Password of the key. Can be null for unencrypted keys. - * - * @return {@link PGPSecretKeyRing} containing the KeyPair. - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length, @Nullable String password) - throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { - Passphrase passphrase = Passphrase.emptyPassphrase(); - if (!isNullOrEmpty(password)) { - passphrase = Passphrase.fromPassword(password); - } - return simpleRsaKeyRing(userId, length, passphrase); - } - - /** - * Creates a key ring consisting of an ed25519 EdDSA primary key and a curve25519 XDH subkey. - * The EdDSA primary key is used for signing messages and certifying the sub key. - * The XDH subkey is used for encryption and decryption of messages. - * - * @param userId user-id - * - * @return {@link PGPSecretKeyRing} containing the key pairs. - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { - return simpleEcKeyRing(userId, Passphrase.emptyPassphrase()); - } - - /** - * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. - * The EdDSA primary key is used for signing messages and certifying the sub key. - * The XDH subkey is used for encryption and decryption of messages. - * - * @param userId user-id - * @param password Password of the private key. Can be null for an unencrypted key. - * - * @return {@link PGPSecretKeyRing} containing the key pairs. - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId, String password) - throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { - Passphrase passphrase = Passphrase.emptyPassphrase(); - if (!isNullOrEmpty(password)) { - passphrase = Passphrase.fromPassword(password); - } - return simpleEcKeyRing(userId, passphrase); - } - - public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId, @Nonnull Passphrase passphrase) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - KeyRingBuilder builder = PGPainless.buildKeyRing() - .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) - .addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)) - .setPassphrase(passphrase); - if (userId != null) { - builder.addUserId(userId.toString()); - } - return builder.build(); - } - - /** - * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify - * an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. - * - * @param userId primary user id - * @return key ring - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - return modernKeyRing(userId, Passphrase.emptyPassphrase()); - } - - /** - * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify - * an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. - * - * @param userId primary user id - * @param password passphrase or null if the key should be unprotected. - * @return key ring - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId, @Nullable String password) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { - Passphrase passphrase = (password != null ? Passphrase.fromPassword(password) : Passphrase.emptyPassphrase()); - return modernKeyRing(userId, passphrase); - } - - public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId, @Nonnull Passphrase passphrase) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - KeyRingBuilder builder = PGPainless.buildKeyRing() - .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) - .addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)) - .addSubkey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA)) - .setPassphrase(passphrase); - if (userId != null) { - builder.addUserId(userId.toString()); - } - return builder.build(); - } - - private static boolean isNullOrEmpty(String password) { - return password == null || password.trim().isEmpty(); - } - -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt new file mode 100644 index 00000000..82215971 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation + +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.pgpainless.PGPainless.Companion.buildKeyRing +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.key.generation.KeySpec.Companion.getBuilder +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.key.generation.type.eddsa.EdDSACurve +import org.pgpainless.key.generation.type.rsa.RsaLength +import org.pgpainless.key.generation.type.xdh.XDHSpec +import org.pgpainless.util.Passphrase + +class KeyRingTemplates { + + /** + * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, + * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. + * + * @param userId userId or null + * @param length length of the RSA keys + * @param passphrase passphrase to encrypt the key with. Can be empty for an unencrytped key. + * @return key + */ + @JvmOverloads + fun rsaKeyRing(userId: CharSequence?, + length: RsaLength, + passphrase: Passphrase = Passphrase.emptyPassphrase() + ): PGPSecretKeyRing = buildKeyRing().apply { + setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER)) + addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA)) + addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) + setPassphrase(passphrase) + if (userId != null) { + addUserId(userId) + } + }.build() + + /** + * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, + * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. + * + * @param userId userId or null + * @param length length of the RSA keys + * @param password passphrase to encrypt the key with. Can be null or blank for unencrypted keys. + * @return key + */ + fun rsaKeyRing(userId: CharSequence?, + length: RsaLength, + password: String? + ): PGPSecretKeyRing = password.let { + if (it.isNullOrBlank()) { + rsaKeyRing(userId, length, Passphrase.emptyPassphrase()) + } else { + rsaKeyRing(userId, length, Passphrase.fromPassword(it)) + } + } + + /** + * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. + * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. + * + * @param userId user id. + * @param length length in bits. + * @param password Password of the key. Can be empty for unencrypted keys. + * + * @return {@link PGPSecretKeyRing} containing the KeyPair. + */ + @JvmOverloads + fun simpleRsaKeyRing(userId: CharSequence?, + length: RsaLength, + passphrase: Passphrase = Passphrase.emptyPassphrase() + ): PGPSecretKeyRing = buildKeyRing().apply { + setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS)) + setPassphrase(passphrase) + if (userId != null) { + addUserId(userId.toString()) + } + }.build() + + /** + * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. + * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. + * + * @param userId user id. + * @param length length in bits. + * @param password Password of the key. Can be null or blank for unencrypted keys. + * + * @return {@link PGPSecretKeyRing} containing the KeyPair. + */ + fun simpleRsaKeyRing(userId: CharSequence?, + length: RsaLength, + password: String? + ) = password.let { + if (it.isNullOrBlank()) { + simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase()) + } else { + simpleRsaKeyRing(userId, length, Passphrase.fromPassword(it)) + } + } + + /** + * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. + * The EdDSA primary key is used for signing messages and certifying the sub key. + * The XDH subkey is used for encryption and decryption of messages. + * + * @param userId user-id + * @param passphrase Password of the private key. Can be empty for an unencrypted key. + * + * @return {@link PGPSecretKeyRing} containing the key pairs. + */ + @JvmOverloads + fun simpleEcKeyRing(userId: CharSequence?, + passphrase: Passphrase = Passphrase.emptyPassphrase() + ): PGPSecretKeyRing = buildKeyRing().apply { + setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) + addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)) + setPassphrase(passphrase) + if (userId != null) { + addUserId(userId.toString()) + } + }.build() + + /** + * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. + * The EdDSA primary key is used for signing messages and certifying the sub key. + * The XDH subkey is used for encryption and decryption of messages. + * + * @param userId user-id + * @param passphrase Password of the private key. Can be null or blank for an unencrypted key. + * + * @return {@link PGPSecretKeyRing} containing the key pairs. + */ + fun simpleEcKeyRing(userId: CharSequence?, + password: String? + ): PGPSecretKeyRing = password.let { + if (it.isNullOrBlank()) { + simpleEcKeyRing(userId, Passphrase.emptyPassphrase()) + } else { + simpleEcKeyRing(userId, Passphrase.fromPassword(it)) + } + } + + /** + * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify + * an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. + * + * @param userId primary user id + * @param passphrase passphrase for the private key. Can be empty for an unencrypted key. + * @return key ring + */ + @JvmOverloads + fun modernKeyRing(userId: CharSequence?, + passphrase: Passphrase = Passphrase.emptyPassphrase() + ): PGPSecretKeyRing = buildKeyRing().apply { + setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) + addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) + addSubkey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA)) + setPassphrase(passphrase) + if (userId != null) { + addUserId(userId) + } + }.build() + + /** + * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify + * an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. + * + * @param userId primary user id + * @param password passphrase for the private key. Can be null or blank for an unencrypted key. + * @return key ring + */ + fun modernKeyRing(userId: CharSequence?, + password: String? + ): PGPSecretKeyRing = password.let { + if (it.isNullOrBlank()) { + modernKeyRing(userId, Passphrase.emptyPassphrase()) + } else { + modernKeyRing(userId, Passphrase.fromPassword(it)) + } + } +} \ No newline at end of file From c40e2ba6c22e692ab33fa37d2c6d87a5641ff853 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 16:44:26 +0200 Subject: [PATCH 058/155] Move const to companion object --- .../kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt index af650c4b..c101e2c0 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -25,7 +25,6 @@ import java.io.IOException import java.security.KeyPairGenerator import java.util.* -const val MILLIS_IN_YEAR = 1000L * 60 * 60 * 24 * 365 class KeyRingBuilder : KeyRingBuilderInterface { @@ -217,6 +216,8 @@ class KeyRingBuilder : KeyRingBuilderInterface { } companion object { + const val MILLIS_IN_YEAR = 1000L * 60 * 60 * 24 * 365 + @JvmStatic fun generateKeyPair(spec: KeySpec): PGPKeyPair { spec.keyType.let { type -> From 5fce443ad9c73efbdcbe8ea5b8530624da0f4f1d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 17:15:36 +0200 Subject: [PATCH 059/155] Kotlin conversion: SecretKeyRingProtector and subclasses --- .../BaseSecretKeyRingProtector.java | 6 +- .../CachingSecretKeyRingProtector.java | 18 +- .../PasswordBasedSecretKeyRingProtector.java | 8 +- .../protection/SecretKeyRingProtector.java | 168 ------------------ .../key/protection/UnlockSecretKey.java | 3 +- .../protection/UnprotectedKeysProtector.java | 6 +- .../MapBasedPassphraseProvider.java | 4 +- .../SecretKeyPassphraseProvider.java | 4 +- .../SolitaryPassphraseProvider.java | 4 +- .../key/protection/SecretKeyRingProtector.kt | 150 ++++++++++++++++ .../MissingPassphraseForDecryptionTest.java | 8 +- ...tionUsingKeyWithMissingPassphraseTest.java | 12 +- .../CachingSecretKeyRingProtectorTest.java | 4 +- .../PassphraseProtectedKeyTest.java | 8 +- .../SecretKeyRingProtectorTest.java | 4 +- .../MatchMakingSecretKeyRingProtector.java | 6 +- 16 files changed, 198 insertions(+), 215 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java index 1a31d0e8..fead3938 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java @@ -44,13 +44,13 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector { } @Override - public boolean hasPassphraseFor(Long keyId) { + public boolean hasPassphraseFor(long keyId) { return passphraseProvider.hasPassphrase(keyId); } @Override @Nullable - public PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException { + public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException { Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); return passphrase == null || passphrase.isEmpty() ? null : ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); @@ -58,7 +58,7 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector { @Override @Nullable - public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException { + public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException { Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); return passphrase == null || passphrase.isEmpty() ? null : ImplementationFactory.getInstance().getPBESecretKeyEncryptor( diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java index 8cdb3efb..0b4fe084 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java @@ -60,12 +60,12 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * containing a key with the same key-id but different passphrases. * * If you can ensure that there will be no key-id clash, and you want to replace the passphrase, you can use - * {@link #replacePassphrase(Long, Passphrase)} to replace the passphrase. + * {@link #replacePassphrase(long, Passphrase)} to replace the passphrase. * * @param keyId id of the key * @param passphrase passphrase */ - public void addPassphrase(@Nonnull Long keyId, @Nonnull Passphrase passphrase) { + public void addPassphrase(long keyId, @Nonnull Passphrase passphrase) { if (this.cache.containsKey(keyId)) { throw new IllegalArgumentException("The cache already holds a passphrase for ID " + Long.toHexString(keyId) + ".\n" + "If you want to replace the passphrase, use replacePassphrase(Long, Passphrase) instead."); @@ -79,7 +79,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * @param keyId keyId * @param passphrase passphrase */ - public void replacePassphrase(@Nonnull Long keyId, @Nonnull Passphrase passphrase) { + public void replacePassphrase(long keyId, @Nonnull Passphrase passphrase) { this.cache.put(keyId, passphrase); } @@ -152,7 +152,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * * @param keyId id of the key */ - public void forgetPassphrase(@Nonnull Long keyId) { + public void forgetPassphrase(long keyId) { Passphrase passphrase = cache.remove(keyId); if (passphrase != null) { passphrase.clear(); @@ -183,7 +183,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se @Override @Nullable - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { Passphrase passphrase = cache.get(keyId); if (passphrase == null || !passphrase.isValid()) { if (provider == null) { @@ -198,25 +198,25 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { Passphrase passphrase = cache.get(keyId); return passphrase != null && passphrase.isValid(); } @Override - public boolean hasPassphraseFor(Long keyId) { + public boolean hasPassphraseFor(long keyId) { return hasPassphrase(keyId); } @Override @Nullable - public PBESecretKeyDecryptor getDecryptor(@Nonnull Long keyId) throws PGPException { + public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException { return protector.getDecryptor(keyId); } @Override @Nullable - public PBESecretKeyEncryptor getEncryptor(@Nonnull Long keyId) throws PGPException { + public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException { return protector.getEncryptor(keyId); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java index c5745068..0e387085 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java @@ -40,12 +40,12 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() { @Override @Nullable - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { return hasPassphrase(keyId) ? passphrase : null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return keyRing.getPublicKey(keyId) != null; } }; @@ -60,7 +60,7 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() { @Nullable @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { if (keyId == singleKeyId) { return passphrase; } @@ -68,7 +68,7 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return keyId == singleKeyId; } }; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java deleted file mode 100644 index d7ed5c85..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; -import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; -import org.pgpainless.util.Passphrase; - -/** - * Task of the {@link SecretKeyRingProtector} is to map encryptor/decryptor objects to key-ids. - * {@link PBESecretKeyEncryptor PBESecretKeyEncryptors}/{@link PBESecretKeyDecryptor PBESecretKeyDecryptors} are used - * to encrypt/decrypt secret keys using a passphrase. - * - * While it is easy to create an implementation of this interface that fits your needs, there are a bunch of - * implementations ready for use. - */ -public interface SecretKeyRingProtector { - - /** - * Returns true, if the protector has a passphrase for the key with the given key-id. - * - * @param keyId key id - * @return true if it has a passphrase, false otherwise - */ - boolean hasPassphraseFor(Long keyId); - - /** - * Return a decryptor for the key of id {@code keyId}. - * This method returns null if the key is unprotected. - * - * @param keyId id of the key - * @return decryptor for the key - * - * @throws PGPException if the decryptor cannot be created for some reason - */ - @Nullable PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException; - - /** - * Return an encryptor for the key of id {@code keyId}. - * This method returns null if the key is unprotected. - * - * @param keyId id of the key - * @return encryptor for the key - * - * @throws PGPException if the encryptor cannot be created for some reason - */ - @Nullable PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException; - - /** - * Return a protector for secret keys. - * The protector maintains an in-memory cache of passphrases and can be extended with new passphrases - * at runtime. - * - * See {@link CachingSecretKeyRingProtector} for how to memorize/forget additional passphrases during runtime. - * - * @param missingPassphraseCallback callback that is used to provide missing passphrases. - * @return caching secret key protector - */ - static CachingSecretKeyRingProtector defaultSecretKeyRingProtector(SecretKeyPassphraseProvider missingPassphraseCallback) { - return new CachingSecretKeyRingProtector( - new HashMap<>(), - KeyRingProtectionSettings.secureDefaultSettings(), - missingPassphraseCallback); - } - - /** - * Use the provided passphrase to lock/unlock all keys in the provided key ring. - * - * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object. - * For other keys that are not present in the ring, it will return null. - * - * @param passphrase passphrase - * @param keys key ring - * @return protector - * @deprecated use {@link #unlockEachKeyWith(Passphrase, PGPSecretKeyRing)} instead. - * - * TODO: Remove in 1.2.X - */ - @Deprecated - static SecretKeyRingProtector unlockAllKeysWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKeyRing keys) { - return unlockEachKeyWith(passphrase, keys); - } - - /** - * Use the provided passphrase to lock/unlock all keys in the provided key ring. - * - * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object. - * For other keys that are not present in the ring, it will return null. - * - * @param passphrase passphrase - * @param keys key ring - * @return protector - */ - static SecretKeyRingProtector unlockEachKeyWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKeyRing keys) { - Map map = new ConcurrentHashMap<>(); - for (PGPSecretKey secretKey : keys) { - map.put(secretKey.getKeyID(), passphrase); - } - return fromPassphraseMap(map); - } - - /** - * Use the provided passphrase to unlock any key. - * - * @param passphrase passphrase - * @return protector - */ - static SecretKeyRingProtector unlockAnyKeyWith(@Nonnull Passphrase passphrase) { - return new BaseSecretKeyRingProtector(new SolitaryPassphraseProvider(passphrase)); - } - - /** - * Use the provided passphrase to lock/unlock only the provided (sub-)key. - * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if - * {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} is getting called with the key-id of the provided key. - * - * Otherwise, this protector will always return null. - * - * @param passphrase passphrase - * @param key key to lock/unlock - * @return protector - */ - static SecretKeyRingProtector unlockSingleKeyWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKey key) { - return PasswordBasedSecretKeyRingProtector.forKey(key, passphrase); - } - - static SecretKeyRingProtector unlockSingleKeyWith(@Nonnull Passphrase passphrase, long keyId) { - return PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase); - } - - /** - * Protector for unprotected keys. - * This protector returns null for all {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} calls, - * no matter what the key-id is. - * - * As a consequence, this protector can only "unlock" keys which are not protected using a passphrase, and it will - * leave keys unprotected, should it be used to "protect" a key - * (e.g. in {@link org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor#changePassphraseFromOldPassphrase(Passphrase)}). - * - * @return protector - */ - static SecretKeyRingProtector unprotectedKeys() { - return new UnprotectedKeysProtector(); - } - - /** - * Use the provided map of key-ids and passphrases to unlock keys. - * - * @param passphraseMap map of key ids and their respective passphrases - * @return protector - */ - static SecretKeyRingProtector fromPassphraseMap(@Nonnull Map passphraseMap) { - return new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java index 78c849ab..154a1aa1 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java @@ -61,6 +61,7 @@ public final class UnlockSecretKey { public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase) throws PGPException, KeyIntegrityException { - return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey)); + return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith( + passphrase == null ? Passphrase.emptyPassphrase() : passphrase, secretKey)); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java index 26b5c596..5d3f3361 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java @@ -15,19 +15,19 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; public class UnprotectedKeysProtector implements SecretKeyRingProtector { @Override - public boolean hasPassphraseFor(Long keyId) { + public boolean hasPassphraseFor(long keyId) { return true; } @Override @Nullable - public PBESecretKeyDecryptor getDecryptor(Long keyId) { + public PBESecretKeyDecryptor getDecryptor(long keyId) { return null; } @Override @Nullable - public PBESecretKeyEncryptor getEncryptor(Long keyId) { + public PBESecretKeyEncryptor getEncryptor(long keyId) { return null; } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java index 859758b1..3f0ee2f9 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java @@ -31,12 +31,12 @@ public class MapBasedPassphraseProvider implements SecretKeyPassphraseProvider { @Nullable @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { return map.get(keyId); } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return map.containsKey(keyId); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java index 59cb39ce..91c2bc95 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java @@ -34,7 +34,7 @@ public interface SecretKeyPassphraseProvider { * @param keyId if of the secret key * @return passphrase or null, if no passphrase record has been found. */ - @Nullable Passphrase getPassphraseFor(Long keyId); + @Nullable Passphrase getPassphraseFor(long keyId); - boolean hasPassphrase(Long keyId); + boolean hasPassphrase(long keyId); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java index b439ef56..9400229b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java @@ -21,13 +21,13 @@ public class SolitaryPassphraseProvider implements SecretKeyPassphraseProvider { @Nullable @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { // always return the same passphrase. return passphrase; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt new file mode 100644 index 00000000..8bdac8d6 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection + +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor +import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider +import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider +import org.pgpainless.util.Passphrase +import kotlin.jvm.Throws + +/** + * Task of the [SecretKeyRingProtector] is to map encryptor/decryptor objects to key-ids. + * [PBESecretKeyEncryptor]/[PBESecretKeyDecryptor] are used to encrypt/decrypt secret keys using a passphrase. + * + * While it is easy to create an implementation of this interface that fits your needs, there are a bunch of + * implementations ready for use. + */ +interface SecretKeyRingProtector { + + /** + * Returns true, if the protector has a passphrase for the key with the given key-id. + * + * @param keyId key id + * @return true if it has a passphrase, false otherwise + */ + fun hasPassphraseFor(keyId: Long): Boolean + + /** + * Return a decryptor for the key of id `keyId`. + * This method returns null if the key is unprotected. + * + * @param keyId id of the key + * @return decryptor for the key + */ + @Throws(PGPException::class) + fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? + + /** + * Return an encryptor for the key of id `keyId`. + * This method returns null if the key is unprotected. + * + * @param keyId id of the key + * @return encryptor for the key + */ + @Throws(PGPException::class) + fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? + + companion object { + + /** + * Return a protector for secret keys. + * The protector maintains an in-memory cache of passphrases and can be extended with new passphrases + * at runtime. + * + * See [CachingSecretKeyRingProtector] for how to memorize/forget additional passphrases during runtime. + * + * @param missingPassphraseCallback callback that is used to provide missing passphrases. + * @return caching secret key protector + */ + @JvmStatic + fun defaultSecretKeyRingProtector( + missingPassphraseCallback: SecretKeyPassphraseProvider? + ): CachingSecretKeyRingProtector = CachingSecretKeyRingProtector( + mapOf(), KeyRingProtectionSettings.secureDefaultSettings(), missingPassphraseCallback) + + /** + * Use the provided passphrase to lock/unlock all keys in the provided key ring. + * + * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object. + * For other keys that are not present in the ring, it will return null. + * + * @param passphrase passphrase + * @param keys key ring + * @return protector + */ + @JvmStatic + fun unlockEachKeyWith(passphrase: Passphrase, keys: PGPSecretKeyRing): SecretKeyRingProtector = + fromPassphraseMap(keys.map { it.keyID }.associateWith { passphrase }) + + /** + * Use the provided passphrase to unlock any key. + * + * @param passphrase passphrase + * @return protector + */ + @JvmStatic + fun unlockAnyKeyWith(passphrase: Passphrase): SecretKeyRingProtector = + BaseSecretKeyRingProtector(SolitaryPassphraseProvider(passphrase)) + + /** + * Use the provided passphrase to lock/unlock only the provided (sub-)key. + * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if + * [getEncryptor]/[getDecryptor] is getting called with the key-id of the provided key. + * + * Otherwise, this protector will always return null. + * + * @param passphrase passphrase + * @param key key to lock/unlock + * @return protector + */ + @JvmStatic + fun unlockSingleKeyWith(passphrase: Passphrase, key: PGPSecretKey): SecretKeyRingProtector = + PasswordBasedSecretKeyRingProtector.forKey(key, passphrase) + + /** + * Use the provided passphrase to lock/unlock only the provided (sub-)key. + * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if + * [getEncryptor]/[getDecryptor] is getting called with the key-id of the provided key. + * + * Otherwise, this protector will always return null. + * + * @param passphrase passphrase + * @param keyId id of the key to lock/unlock + * @return protector + */ + @JvmStatic + fun unlockSingleKeyWith(passphrase: Passphrase, keyId: Long): SecretKeyRingProtector = + PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase) + + /** + * Protector for unprotected keys. + * This protector returns null for all [getEncryptor]/[getDecryptor] calls, + * no matter what the key-id is. + * + * As a consequence, this protector can only "unlock" keys which are not protected using a passphrase, and it will + * leave keys unprotected, should it be used to "protect" a key + * (e.g. in [org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor.changePassphraseFromOldPassphrase]). + * + * @return protector + */ + @JvmStatic + fun unprotectedKeys() = UnprotectedKeysProtector() + + /** + * Use the provided map of key-ids and passphrases to unlock keys. + * + * @param passphraseMap map of key ids and their respective passphrases + * @return protector + */ + @JvmStatic + fun fromPassphraseMap(passphraseMap: Map): SecretKeyRingProtector = + CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null) + } +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java index 42562713..1cf55a4d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java @@ -63,13 +63,13 @@ public class MissingPassphraseForDecryptionTest { // interactive callback SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { // is called in interactive mode return Passphrase.fromPassword(passphrase); } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } }; @@ -95,13 +95,13 @@ public class MissingPassphraseForDecryptionTest { SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { fail("MUST NOT get called in non-interactive mode."); return null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } }; diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PostponeDecryptionUsingKeyWithMissingPassphraseTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PostponeDecryptionUsingKeyWithMissingPassphraseTest.java index c86a823c..914f477e 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PostponeDecryptionUsingKeyWithMissingPassphraseTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PostponeDecryptionUsingKeyWithMissingPassphraseTest.java @@ -120,13 +120,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest { public void missingPassphraseFirst() throws PGPException, IOException { SecretKeyRingProtector protector1 = new CachingSecretKeyRingProtector(new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { fail("Although the first PKESK is for k1, we should have skipped it and tried k2 first, which has passphrase available."); return null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return false; } }); @@ -150,13 +150,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest { SecretKeyRingProtector protector1 = SecretKeyRingProtector.unlockEachKeyWith(p1, k1); SecretKeyRingProtector protector2 = new CachingSecretKeyRingProtector(new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { fail("This callback should not get called, since the first PKESK is for k1, which has a passphrase available."); return null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return false; } }); @@ -178,13 +178,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest { public void messagePassphraseFirst() throws PGPException, IOException { SecretKeyPassphraseProvider provider = new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { fail("Since we provide a decryption passphrase, we should not try to decrypt any key."); return null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return false; } }; diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java index 3f7a9e6e..9869f7b5 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java @@ -32,13 +32,13 @@ public class CachingSecretKeyRingProtectorTest { // Dummy passphrase callback that returns the doubled key-id as passphrase private final SecretKeyPassphraseProvider dummyCallback = new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { long doubled = keyId * 2; return Passphrase.fromPassword(Long.toString(doubled)); } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } }; diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java index 45aaf20f..b5703eef 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java @@ -31,8 +31,8 @@ public class PassphraseProtectedKeyTest { new SecretKeyPassphraseProvider() { @Nullable @Override - public Passphrase getPassphraseFor(Long keyId) { - if (keyId.equals(TestKeys.CRYPTIE_KEY_ID)) { + public Passphrase getPassphraseFor(long keyId) { + if (keyId == TestKeys.CRYPTIE_KEY_ID) { return new Passphrase(TestKeys.CRYPTIE_PASSWORD.toCharArray()); } else { return null; @@ -40,8 +40,8 @@ public class PassphraseProtectedKeyTest { } @Override - public boolean hasPassphrase(Long keyId) { - return keyId.equals(TestKeys.CRYPTIE_KEY_ID); + public boolean hasPassphrase(long keyId) { + return keyId == TestKeys.CRYPTIE_KEY_ID; } }); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java index a5030f74..b6781355 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java @@ -108,12 +108,12 @@ public class SecretKeyRingProtectorTest { CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { return Passphrase.fromPassword("missingP455w0rd"); } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } }); diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/MatchMakingSecretKeyRingProtector.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/MatchMakingSecretKeyRingProtector.java index 5badf1f1..67386bde 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/MatchMakingSecretKeyRingProtector.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/MatchMakingSecretKeyRingProtector.java @@ -88,19 +88,19 @@ public class MatchMakingSecretKeyRingProtector implements SecretKeyRingProtector } @Override - public boolean hasPassphraseFor(Long keyId) { + public boolean hasPassphraseFor(long keyId) { return protector.hasPassphrase(keyId); } @Nullable @Override - public PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException { + public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException { return protector.getDecryptor(keyId); } @Nullable @Override - public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException { + public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException { return protector.getEncryptor(keyId); } From a8bc929f2440d20722ba6d7b3d8a5316eb1a8867 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 17:26:13 +0200 Subject: [PATCH 060/155] Kotlin conversion: BaseSecretKeyRingProtector --- .../BaseSecretKeyRingProtector.java | 70 ------------------- .../protection/BaseSecretKeyRingProtector.kt | 40 +++++++++++ 2 files changed, 40 insertions(+), 70 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/BaseSecretKeyRingProtector.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java deleted file mode 100644 index fead3938..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; -import org.pgpainless.util.Passphrase; - -import javax.annotation.Nullable; - -/** - * Basic {@link SecretKeyRingProtector} implementation that respects the users {@link KeyRingProtectionSettings} when - * encrypting keys. - */ -public class BaseSecretKeyRingProtector implements SecretKeyRingProtector { - - private final SecretKeyPassphraseProvider passphraseProvider; - private final KeyRingProtectionSettings protectionSettings; - - /** - * Constructor that uses the given {@link SecretKeyPassphraseProvider} to retrieve passphrases and PGPainless' - * default {@link KeyRingProtectionSettings}. - * - * @param passphraseProvider provider for passphrases - */ - public BaseSecretKeyRingProtector(SecretKeyPassphraseProvider passphraseProvider) { - this(passphraseProvider, KeyRingProtectionSettings.secureDefaultSettings()); - } - - /** - * Constructor that uses the given {@link SecretKeyPassphraseProvider} and {@link KeyRingProtectionSettings}. - * - * @param passphraseProvider provider for passphrases - * @param protectionSettings protection settings - */ - public BaseSecretKeyRingProtector(SecretKeyPassphraseProvider passphraseProvider, KeyRingProtectionSettings protectionSettings) { - this.passphraseProvider = passphraseProvider; - this.protectionSettings = protectionSettings; - } - - @Override - public boolean hasPassphraseFor(long keyId) { - return passphraseProvider.hasPassphrase(keyId); - } - - @Override - @Nullable - public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException { - Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); - return passphrase == null || passphrase.isEmpty() ? null : - ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); - } - - @Override - @Nullable - public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException { - Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); - return passphrase == null || passphrase.isEmpty() ? null : - ImplementationFactory.getInstance().getPBESecretKeyEncryptor( - protectionSettings.getEncryptionAlgorithm(), - protectionSettings.getHashAlgorithm(), - protectionSettings.getS2kCount(), - passphrase); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/BaseSecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/BaseSecretKeyRingProtector.kt new file mode 100644 index 00000000..450ca7f5 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/BaseSecretKeyRingProtector.kt @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection + +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider + +/** + * Basic [SecretKeyRingProtector] implementation that respects the users [KeyRingProtectionSettings] when encrypting keys. + */ +open class BaseSecretKeyRingProtector( + private val passphraseProvider: SecretKeyPassphraseProvider, + private val protectionSettings: KeyRingProtectionSettings +) : SecretKeyRingProtector { + + constructor(passphraseProvider: SecretKeyPassphraseProvider): + this(passphraseProvider, KeyRingProtectionSettings.secureDefaultSettings()) + + override fun hasPassphraseFor(keyId: Long): Boolean = passphraseProvider.hasPassphrase(keyId) + + override fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? = + passphraseProvider.getPassphraseFor(keyId)?.let { + if (it.isEmpty) null + else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(it) + } + + override fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? = + passphraseProvider.getPassphraseFor(keyId)?.let { + if (it.isEmpty) null + else ImplementationFactory.getInstance().getPBESecretKeyEncryptor( + protectionSettings.encryptionAlgorithm, + protectionSettings.hashAlgorithm, + protectionSettings.s2kCount, + it) + } +} \ No newline at end of file From b125333c89e06eb05a8951baa04f23205f17d835 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 17:28:08 +0200 Subject: [PATCH 061/155] Kotlin conversion: UnprotectedKeysProtector --- .../protection/UnprotectedKeysProtector.java | 33 ------------------- .../protection/UnprotectedKeysProtector.kt | 15 +++++++++ 2 files changed, 15 insertions(+), 33 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java deleted file mode 100644 index 5d3f3361..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection; - -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; - -/** - * Implementation of the {@link SecretKeyRingProtector} which assumes that all handled keys are not password protected. - */ -public class UnprotectedKeysProtector implements SecretKeyRingProtector { - - @Override - public boolean hasPassphraseFor(long keyId) { - return true; - } - - @Override - @Nullable - public PBESecretKeyDecryptor getDecryptor(long keyId) { - return null; - } - - @Override - @Nullable - public PBESecretKeyEncryptor getEncryptor(long keyId) { - return null; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt new file mode 100644 index 00000000..f87c6d3d --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 +package org.pgpainless.key.protection + +/** + * Implementation of the [SecretKeyRingProtector] which assumes that all handled keys are not password protected. + */ +class UnprotectedKeysProtector : SecretKeyRingProtector { + override fun hasPassphraseFor(keyId: Long) = true + + override fun getDecryptor(keyId: Long) = null + + override fun getEncryptor(keyId: Long) = null +} \ No newline at end of file From b9c601b99676c29ed62f49e576201b491d91274c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 17:40:37 +0200 Subject: [PATCH 062/155] Kotlin conversion: PasswordBasedSecretKeyRingProtector --- .../PasswordBasedSecretKeyRingProtector.java | 78 ------------------- .../PasswordBasedSecretKeyRingProtector.kt | 63 +++++++++++++++ 2 files changed, 63 insertions(+), 78 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java deleted file mode 100644 index 0e387085..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; -import org.pgpainless.util.Passphrase; - -/** - * Provides {@link PBESecretKeyDecryptor} and {@link PBESecretKeyEncryptor} objects while getting the passphrases - * from a {@link SecretKeyPassphraseProvider} and using settings from an {@link KeyRingProtectionSettings}. - */ -public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtector { - - public PasswordBasedSecretKeyRingProtector(@Nonnull SecretKeyPassphraseProvider passphraseProvider) { - super(passphraseProvider); - } - - /** - * Constructor. - * Passphrases for keys are sourced from the {@code passphraseProvider} and decryptors/encryptors are constructed - * following the settings given in {@code settings}. - * - * @param settings S2K settings etc. - * @param passphraseProvider provider which provides passphrases. - */ - public PasswordBasedSecretKeyRingProtector(@Nonnull KeyRingProtectionSettings settings, @Nonnull SecretKeyPassphraseProvider passphraseProvider) { - super(passphraseProvider, settings); - } - - public static PasswordBasedSecretKeyRingProtector forKey(PGPKeyRing keyRing, Passphrase passphrase) { - SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() { - @Override - @Nullable - public Passphrase getPassphraseFor(long keyId) { - return hasPassphrase(keyId) ? passphrase : null; - } - - @Override - public boolean hasPassphrase(long keyId) { - return keyRing.getPublicKey(keyId) != null; - } - }; - return new PasswordBasedSecretKeyRingProtector(passphraseProvider); - } - - public static PasswordBasedSecretKeyRingProtector forKey(PGPSecretKey key, Passphrase passphrase) { - return forKeyId(key.getPublicKey().getKeyID(), passphrase); - } - - public static PasswordBasedSecretKeyRingProtector forKeyId(long singleKeyId, Passphrase passphrase) { - SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() { - @Nullable - @Override - public Passphrase getPassphraseFor(long keyId) { - if (keyId == singleKeyId) { - return passphrase; - } - return null; - } - - @Override - public boolean hasPassphrase(long keyId) { - return keyId == singleKeyId; - } - }; - return new PasswordBasedSecretKeyRingProtector(passphraseProvider); - } - -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.kt new file mode 100644 index 00000000..1b8df815 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.kt @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPSecretKey +import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider +import org.pgpainless.util.Passphrase + +/** + * Provides [PBESecretKeyDecryptor] and [PBESecretKeyEncryptor] objects while getting the passphrases + * from a [SecretKeyPassphraseProvider] and using settings from an [KeyRingProtectionSettings]. + */ +class PasswordBasedSecretKeyRingProtector : BaseSecretKeyRingProtector { + + constructor(passphraseProvider: SecretKeyPassphraseProvider): super(passphraseProvider) + + /** + * Constructor. + * Passphrases for keys are sourced from the `passphraseProvider` and decryptors/encryptors are constructed + * following the settings given in `settings`. + * + * @param settings S2K settings etc. + * @param passphraseProvider provider which provides passphrases. + */ + constructor(settings: KeyRingProtectionSettings, + passphraseProvider: SecretKeyPassphraseProvider): super(passphraseProvider, settings) + + companion object { + @JvmStatic + fun forKey(keyRing: PGPKeyRing, passphrase: Passphrase): PasswordBasedSecretKeyRingProtector { + return object : SecretKeyPassphraseProvider { + + override fun getPassphraseFor(keyId: Long): Passphrase? { + return if (hasPassphrase(keyId)) passphrase else null + } + + override fun hasPassphrase(keyId: Long): Boolean { + return keyRing.getPublicKey(keyId) != null + } + }.let { PasswordBasedSecretKeyRingProtector(it) } + } + + @JvmStatic + fun forKey(key: PGPSecretKey, passphrase: Passphrase): PasswordBasedSecretKeyRingProtector = + forKeyId(key.publicKey.keyID, passphrase) + + @JvmStatic + fun forKeyId(singleKeyId: Long, passphrase: Passphrase): PasswordBasedSecretKeyRingProtector { + return object : SecretKeyPassphraseProvider { + override fun getPassphraseFor(keyId: Long): Passphrase? { + return if (hasPassphrase(keyId)) passphrase else null + } + + override fun hasPassphrase(keyId: Long): Boolean { + return keyId == singleKeyId + } + }.let { PasswordBasedSecretKeyRingProtector(it) } + } + } +} \ No newline at end of file From ee32d9e446187af2e8829ef2708325df52eb52a5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 18:28:29 +0200 Subject: [PATCH 063/155] Kotlin conversion: CachingSecretKeyRingProtector --- .../CachingSecretKeyRingProtector.java | 222 ------------------ .../CachingSecretKeyRingProtector.kt | 174 ++++++++++++++ 2 files changed, 174 insertions(+), 222 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java deleted file mode 100644 index 0b4fe084..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; -import org.pgpainless.util.Passphrase; - -/** - * Implementation of the {@link SecretKeyRingProtector} which holds a map of key ids and their passwords. - * In case the needed passphrase is not contained in the map, the {@code missingPassphraseCallback} will be consulted, - * and the passphrase is added to the map. - * - * If you need to unlock multiple {@link PGPKeyRing PGPKeyRings}, it is advised to use a separate - * {@link CachingSecretKeyRingProtector} instance for each ring. - */ -public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, SecretKeyPassphraseProvider { - - private final Map cache = new HashMap<>(); - private final SecretKeyRingProtector protector; - private final SecretKeyPassphraseProvider provider; - - public CachingSecretKeyRingProtector() { - this(null); - } - - public CachingSecretKeyRingProtector(@Nullable SecretKeyPassphraseProvider missingPassphraseCallback) { - this( - new HashMap<>(), - KeyRingProtectionSettings.secureDefaultSettings(), - missingPassphraseCallback - ); - } - - public CachingSecretKeyRingProtector(@Nonnull Map passphrases, - @Nonnull KeyRingProtectionSettings protectionSettings, - @Nullable SecretKeyPassphraseProvider missingPassphraseCallback) { - this.cache.putAll(passphrases); - this.protector = new PasswordBasedSecretKeyRingProtector(protectionSettings, this); - this.provider = missingPassphraseCallback; - } - - /** - * Add a passphrase to the cache. - * If the cache already contains a passphrase for the given key-id, a {@link IllegalArgumentException} is thrown. - * The reason for this is to prevent accidental override of passphrases when dealing with multiple key rings - * containing a key with the same key-id but different passphrases. - * - * If you can ensure that there will be no key-id clash, and you want to replace the passphrase, you can use - * {@link #replacePassphrase(long, Passphrase)} to replace the passphrase. - * - * @param keyId id of the key - * @param passphrase passphrase - */ - public void addPassphrase(long keyId, @Nonnull Passphrase passphrase) { - if (this.cache.containsKey(keyId)) { - throw new IllegalArgumentException("The cache already holds a passphrase for ID " + Long.toHexString(keyId) + ".\n" + - "If you want to replace the passphrase, use replacePassphrase(Long, Passphrase) instead."); - } - this.cache.put(keyId, passphrase); - } - - /** - * Replace the passphrase for the given key-id in the cache. - * - * @param keyId keyId - * @param passphrase passphrase - */ - public void replacePassphrase(long keyId, @Nonnull Passphrase passphrase) { - this.cache.put(keyId, passphrase); - } - - /** - * Remember the given passphrase for all keys in the given key ring. - * If for the key-id of any key on the key ring the cache already contains a passphrase, a - * {@link IllegalArgumentException} is thrown before any changes are committed to the cache. - * This is to prevent accidental passphrase override when dealing with multiple key rings containing - * keys with conflicting key-ids. - * - * If you can ensure that there will be no key-id clashes, and you want to replace the passphrases for the key ring, - * use {@link #replacePassphrase(PGPKeyRing, Passphrase)} instead. - * - * If you need to unlock multiple {@link PGPKeyRing PGPKeyRings}, it is advised to use a separate - * {@link CachingSecretKeyRingProtector} instance for each ring. - * - * @param keyRing key ring - * @param passphrase passphrase - */ - public void addPassphrase(@Nonnull PGPKeyRing keyRing, @Nonnull Passphrase passphrase) { - Iterator keys = keyRing.getPublicKeys(); - // check for existing passphrases before doing anything - while (keys.hasNext()) { - long keyId = keys.next().getKeyID(); - if (cache.containsKey(keyId)) { - throw new IllegalArgumentException("The cache already holds a passphrase for ID " + Long.toHexString(keyId) + ".\n" + - "If you want to replace the passphrase, use replacePassphrase(PGPKeyRing, Passphrase) instead."); - } - } - - // only then insert - keys = keyRing.getPublicKeys(); - while (keys.hasNext()) { - PGPPublicKey publicKey = keys.next(); - addPassphrase(publicKey, passphrase); - } - } - - /** - * Replace the cached passphrases for all keys in the key ring with the provided passphrase. - * - * @param keyRing key ring - * @param passphrase passphrase - */ - public void replacePassphrase(@Nonnull PGPKeyRing keyRing, @Nonnull Passphrase passphrase) { - Iterator keys = keyRing.getPublicKeys(); - while (keys.hasNext()) { - PGPPublicKey publicKey = keys.next(); - replacePassphrase(publicKey.getKeyID(), passphrase); - } - } - - /** - * Remember the given passphrase for the given (sub-)key. - * - * @param key key - * @param passphrase passphrase - */ - public void addPassphrase(@Nonnull PGPPublicKey key, @Nonnull Passphrase passphrase) { - addPassphrase(key.getKeyID(), passphrase); - } - - public void addPassphrase(@Nonnull OpenPgpFingerprint fingerprint, @Nonnull Passphrase passphrase) { - addPassphrase(fingerprint.getKeyId(), passphrase); - } - - /** - * Remove a passphrase from the cache. - * The passphrase will be cleared and then removed. - * - * @param keyId id of the key - */ - public void forgetPassphrase(long keyId) { - Passphrase passphrase = cache.remove(keyId); - if (passphrase != null) { - passphrase.clear(); - } - } - - /** - * Forget the passphrase to all keys in the provided key ring. - * - * @param keyRing key ring - */ - public void forgetPassphrase(@Nonnull PGPKeyRing keyRing) { - Iterator keys = keyRing.getPublicKeys(); - while (keys.hasNext()) { - PGPPublicKey publicKey = keys.next(); - forgetPassphrase(publicKey); - } - } - - /** - * Forget the passphrase of the given public key. - * - * @param key key - */ - public void forgetPassphrase(@Nonnull PGPPublicKey key) { - forgetPassphrase(key.getKeyID()); - } - - @Override - @Nullable - public Passphrase getPassphraseFor(long keyId) { - Passphrase passphrase = cache.get(keyId); - if (passphrase == null || !passphrase.isValid()) { - if (provider == null) { - return null; - } - passphrase = provider.getPassphraseFor(keyId); - if (passphrase != null) { - cache.put(keyId, passphrase); - } - } - return passphrase; - } - - @Override - public boolean hasPassphrase(long keyId) { - Passphrase passphrase = cache.get(keyId); - return passphrase != null && passphrase.isValid(); - } - - @Override - public boolean hasPassphraseFor(long keyId) { - return hasPassphrase(keyId); - } - - @Override - @Nullable - public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException { - return protector.getDecryptor(keyId); - } - - @Override - @Nullable - public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException { - return protector.getEncryptor(keyId); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt new file mode 100644 index 00000000..4451aa0f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider +import org.pgpainless.key.util.KeyIdUtil +import org.pgpainless.util.Passphrase + +/** + * Implementation of the [SecretKeyRingProtector] which holds a map of key ids and their passwords. + * In case the needed passphrase is not contained in the map, the `missingPassphraseCallback` will be consulted, + * and the passphrase is added to the map. + * + * If you need to unlock multiple [PGPKeyRing] instances, it is advised to use a separate + * [CachingSecretKeyRingProtector] instance for each ring. + */ +class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphraseProvider { + + private val cache: MutableMap + private val protector: SecretKeyRingProtector + private val provider: SecretKeyPassphraseProvider? + + constructor(): this(null) + + constructor(missingPassphraseCallback: SecretKeyPassphraseProvider?): this( + mapOf(), + KeyRingProtectionSettings.secureDefaultSettings(), + missingPassphraseCallback) + + constructor(passphrases: Map, + protectionSettings: KeyRingProtectionSettings, + missingPassphraseCallback: SecretKeyPassphraseProvider?) { + this.cache = passphrases.toMutableMap() + this.protector = PasswordBasedSecretKeyRingProtector(protectionSettings, this) + this.provider = missingPassphraseCallback + } + + /** + * Add a passphrase to the cache. + * If the cache already contains a passphrase for the given key-id, a [IllegalArgumentException] is thrown. + * The reason for this is to prevent accidental override of passphrases when dealing with multiple key rings + * containing a key with the same key-id but different passphrases. + * + * If you can ensure that there will be no key-id clash, and you want to replace the passphrase, you can use + * [replacePassphrase] to replace the passphrase. + * + * @param keyId id of the key + * @param passphrase passphrase + */ + fun addPassphrase(keyId: Long, passphrase: Passphrase) = apply { + require(!cache.containsKey(keyId)) { + "The cache already holds a passphrase for ID ${KeyIdUtil.formatKeyId(keyId)}.\n" + + "If you want to replace this passphrase, use replacePassphrase(Long, Passphrase) instead." + } + cache[keyId] = passphrase + } + + /** + * Replace the passphrase for the given key-id in the cache. + * + * @param keyId keyId + * @param passphrase passphrase + */ + fun replacePassphrase(keyId: Long, passphrase: Passphrase) = apply { + cache[keyId] = passphrase + } + + /** + * Remember the given passphrase for all keys in the given key ring. + * If for the key-id of any key on the key ring the cache already contains a passphrase, a + * [IllegalArgumentException] is thrown before any changes are committed to the cache. + * This is to prevent accidental passphrase override when dealing with multiple key rings containing + * keys with conflicting key-ids. + * + * If you can ensure that there will be no key-id clashes, and you want to replace the passphrases for the key ring, + * use [replacePassphrase] instead. + * + * If you need to unlock multiple [PGPKeyRing], it is advised to use a separate [CachingSecretKeyRingProtector] + * instance for each ring. + * + * @param keyRing key ring + * @param passphrase passphrase + */ + fun addPassphrase(keyRing: PGPKeyRing, passphrase: Passphrase) = apply { + // check for existing passphrases before doing anything + keyRing.publicKeys.forEach { + require(!cache.containsKey(it.keyID)) { + "The cache already holds a passphrase for the key with ID ${KeyIdUtil.formatKeyId(it.keyID)}.\n" + + "If you want to replace the passphrase, use replacePassphrase(PGPKeyRing, Passphrase) instead." + } + } + + // only then instert + keyRing.publicKeys.forEach { + cache[it.keyID] = passphrase + } + } + + /** + * Replace the cached passphrases for all keys in the key ring with the provided passphrase. + * + * @param keyRing key ring + * @param passphrase passphrase + */ + fun replacePassphrase(keyRing: PGPKeyRing, passphrase: Passphrase) = apply { + keyRing.publicKeys.forEach { cache[it.keyID] = passphrase } + } + + /** + * Remember the given passphrase for the given (sub-)key. + * + * @param key key + * @param passphrase passphrase + */ + fun addPassphrase(key: PGPPublicKey, passphrase: Passphrase) = + addPassphrase(key.keyID, passphrase) + + /** + * Remember the given passphrase for the key with the given fingerprint. + * + * @param fingerprint fingerprint + * @param passphrase passphrase + */ + fun addPassphrase(fingerprint: OpenPgpFingerprint, passphrase: Passphrase) = + addPassphrase(fingerprint.keyId, passphrase) + + /** + * Remove a passphrase from the cache. + * The passphrase will be cleared and then removed. + * + * @param keyId id of the key + */ + fun forgetPassphrase(keyId: Long) = apply { + cache.remove(keyId)?.clear() + } + + /** + * Forget the passphrase to all keys in the provided key ring. + * + * @param keyRing key ring + */ + fun forgetPassphrase(keyRing: PGPKeyRing) = apply { + keyRing.publicKeys.forEach { forgetPassphrase(it) } + } + + /** + * Forget the passphrase of the given public key. + * + * @param key key + */ + fun forgetPassphrase(key: PGPPublicKey) = apply { + forgetPassphrase(key.keyID) + } + + override fun getPassphraseFor(keyId: Long): Passphrase? { + return if (hasPassphrase(keyId)) + cache[keyId] + else + provider?.getPassphraseFor(keyId)?.also { cache[keyId] = it } + } + + override fun hasPassphrase(keyId: Long) = cache[keyId]?.isValid ?: false + + override fun hasPassphraseFor(keyId: Long) = hasPassphrase(keyId) + + override fun getDecryptor(keyId: Long) = protector.getDecryptor(keyId) + + override fun getEncryptor(keyId: Long) = protector.getEncryptor(keyId) +} \ No newline at end of file From 2547f1c687c5dc57ed27a55e56425bbb79b601f8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 18:28:48 +0200 Subject: [PATCH 064/155] Kotlin conversion: KeyRingProtectionSettings --- .../protection/KeyRingProtectionSettings.java | 99 ------------------- .../protection/KeyRingProtectionSettings.kt | 56 +++++++++++ 2 files changed, 56 insertions(+), 99 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/KeyRingProtectionSettings.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/KeyRingProtectionSettings.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/KeyRingProtectionSettings.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/KeyRingProtectionSettings.java deleted file mode 100644 index a93534ab..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/KeyRingProtectionSettings.java +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection; - -import javax.annotation.Nonnull; - -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; - -/** - * Secret key protection settings for iterated and salted S2K. - */ -public class KeyRingProtectionSettings { - - private final SymmetricKeyAlgorithm encryptionAlgorithm; - private final HashAlgorithm hashAlgorithm; - private final int s2kCount; - - /** - * Create a {@link KeyRingProtectionSettings} object using the given encryption algorithm, SHA1 and - * 65536 iterations. - * - * @param encryptionAlgorithm encryption algorithm - */ - public KeyRingProtectionSettings(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm) { - this(encryptionAlgorithm, HashAlgorithm.SHA1, 0x60); // Same s2kCount (encoded) as used in BC. - } - - /** - * Constructor for custom salted and iterated S2K protection settings. - * The salt gets randomly chosen by the library each time. - * - * Note, that the s2kCount is the already encoded single-octet number. - * - * @see Encoding Formula - * - * @param encryptionAlgorithm encryption algorithm - * @param hashAlgorithm hash algorithm - * @param s2kCount encoded s2k iteration count - */ - public KeyRingProtectionSettings(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm, @Nonnull HashAlgorithm hashAlgorithm, int s2kCount) { - this.encryptionAlgorithm = validateEncryptionAlgorithm(encryptionAlgorithm); - this.hashAlgorithm = hashAlgorithm; - if (s2kCount < 1) { - throw new IllegalArgumentException("s2kCount cannot be less than 1."); - } - this.s2kCount = s2kCount; - } - - private static SymmetricKeyAlgorithm validateEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) { - switch (encryptionAlgorithm) { - case NULL: - throw new IllegalArgumentException("Unencrypted is not allowed here!"); - default: - return encryptionAlgorithm; - } - } - - /** - * Secure default settings using {@link SymmetricKeyAlgorithm#AES_256}, {@link HashAlgorithm#SHA256} - * and an iteration count of 65536. - * - * @return secure protection settings - */ - public static KeyRingProtectionSettings secureDefaultSettings() { - return new KeyRingProtectionSettings(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA256, 0x60); - } - - /** - * Return the encryption algorithm. - * - * @return encryption algorithm - */ - public @Nonnull SymmetricKeyAlgorithm getEncryptionAlgorithm() { - return encryptionAlgorithm; - } - - /** - * Return the hash algorithm. - * - * @return hash algorithm - */ - public @Nonnull HashAlgorithm getHashAlgorithm() { - return hashAlgorithm; - } - - /** - * Return the (encoded!) s2k iteration count. - * - * @see Encoding Formula - * - * @return encoded s2k count - */ - public int getS2kCount() { - return s2kCount; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/KeyRingProtectionSettings.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/KeyRingProtectionSettings.kt new file mode 100644 index 00000000..6158d322 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/KeyRingProtectionSettings.kt @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection + +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.SymmetricKeyAlgorithm + +/** + * Secret key protection settings for iterated and salted S2K. + * The salt gets randomly chosen by the library each time. + * Note, that the s2kCount is the already encoded single-octet number. + * + * @see Encoding Formula + * + * @param encryptionAlgorithm encryption algorithm + * @param hashAlgorithm hash algorithm + * @param s2kCount encoded (!) s2k iteration count + */ +data class KeyRingProtectionSettings( + val encryptionAlgorithm: SymmetricKeyAlgorithm, + val hashAlgorithm: HashAlgorithm, + val s2kCount: Int +) { + + /** + * Create a [KeyRingProtectionSettings] object using the given encryption algorithm, [HashAlgorithm.SHA1] and + * 65536 iterations. + * It is okay to use SHA1 here, since we don't care about collisions. + * + * @param encryptionAlgorithm encryption algorithm + */ + constructor(encryptionAlgorithm: SymmetricKeyAlgorithm): this(encryptionAlgorithm, HashAlgorithm.SHA1, 0x60) + + init { + require(encryptionAlgorithm != SymmetricKeyAlgorithm.NULL) { + "Unencrypted is not allowed here!" + } + require(s2kCount > 0) { + "s2kCount cannot be less than 1." + } + } + + companion object { + + /** + * Secure default settings using [SymmetricKeyAlgorithm.AES_256], [HashAlgorithm.SHA256] + * and an iteration count of 65536. + * + * @return secure protection settings + */ + @JvmStatic + fun secureDefaultSettings() = KeyRingProtectionSettings(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA256, 0x60) + } +} \ No newline at end of file From 873db12125187d16161dc913686b81d59e38aa67 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 18:45:09 +0200 Subject: [PATCH 065/155] Kotlin conversion: UnlockSecretKey --- .../key/protection/UnlockSecretKey.java | 67 ------------------ .../OpenPgpMessageInputStream.kt | 2 +- .../key/protection/UnlockSecretKey.kt | 68 +++++++++++++++++++ 3 files changed, 69 insertions(+), 68 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java deleted file mode 100644 index 154a1aa1..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2021 Paul Schaub. -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.pgpainless.PGPainless; -import org.pgpainless.exception.KeyIntegrityException; -import org.pgpainless.exception.WrongPassphraseException; -import org.pgpainless.key.info.KeyInfo; -import org.pgpainless.key.util.PublicKeyParameterValidationUtil; -import org.pgpainless.util.Passphrase; - -public final class UnlockSecretKey { - - private UnlockSecretKey() { - - } - - public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) - throws PGPException, KeyIntegrityException { - - PBESecretKeyDecryptor decryptor = null; - if (KeyInfo.isEncrypted(secretKey)) { - decryptor = protector.getDecryptor(secretKey.getKeyID()); - } - PGPPrivateKey privateKey = unlockSecretKey(secretKey, decryptor); - return privateKey; - } - - public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, PBESecretKeyDecryptor decryptor) - throws PGPException { - PGPPrivateKey privateKey; - try { - privateKey = secretKey.extractPrivateKey(decryptor); - } catch (PGPException e) { - throw new WrongPassphraseException(secretKey.getKeyID(), e); - } - - if (privateKey == null) { - int s2kType = secretKey.getS2K().getType(); - if (s2kType >= 100 && s2kType <= 110) { - throw new PGPException("Cannot decrypt secret key" + Long.toHexString(secretKey.getKeyID()) + ": " + - "Unsupported private S2K usage type " + s2kType); - } - - throw new PGPException("Cannot decrypt secret key."); - } - - if (PGPainless.getPolicy().isEnableKeyParameterValidation()) { - PublicKeyParameterValidationUtil.verifyPublicKeyParameterIntegrity(privateKey, secretKey.getPublicKey()); - } - - return privateKey; - } - - public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase) - throws PGPException, KeyIntegrityException { - return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith( - passphrase == null ? Passphrase.emptyPassphrase() : passphrase, secretKey)); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index a255e90e..1f34ec23 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -350,7 +350,7 @@ class OpenPgpMessageInputStream( } LOGGER.debug("Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.") - val protector = options.getSecretKeyProtector(decryptionKeys) + val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { return true diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt new file mode 100644 index 00000000..533b33a7 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt @@ -0,0 +1,68 @@ +// Copyright 2023 Paul Schaub. +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection + +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPPrivateKey +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor +import org.pgpainless.PGPainless +import org.pgpainless.exception.KeyIntegrityException +import org.pgpainless.exception.WrongPassphraseException +import org.pgpainless.key.info.KeyInfo +import org.pgpainless.key.util.KeyIdUtil +import org.pgpainless.key.util.PublicKeyParameterValidationUtil +import org.pgpainless.util.Passphrase +import kotlin.jvm.Throws + +class UnlockSecretKey { + + companion object { + + @JvmStatic + @Throws(PGPException::class, KeyIntegrityException::class) + fun unlockSecretKey(secretKey: PGPSecretKey, protector: SecretKeyRingProtector): PGPPrivateKey { + return if (KeyInfo.isEncrypted(secretKey)) { + unlockSecretKey(secretKey, protector.getDecryptor(secretKey.keyID)) + } else { + unlockSecretKey(secretKey, null as PBESecretKeyDecryptor?) + } + } + + @JvmStatic + @Throws(PGPException::class) + fun unlockSecretKey(secretKey: PGPSecretKey, decryptor: PBESecretKeyDecryptor?): PGPPrivateKey { + val privateKey = try { + secretKey.extractPrivateKey(decryptor) + } catch (e : PGPException) { + throw WrongPassphraseException(secretKey.keyID, e) + } + + if (privateKey == null) { + if (secretKey.s2K.type in 100..110) { + throw PGPException("Cannot decrypt secret key ${KeyIdUtil.formatKeyId(secretKey.keyID)}: \n" + + "Unsupported private S2K type ${secretKey.s2K.type}") + } + throw PGPException("Cannot decrypt secret key.") + } + + if (PGPainless.getPolicy().isEnableKeyParameterValidation()) { + PublicKeyParameterValidationUtil.verifyPublicKeyParameterIntegrity(privateKey, secretKey.publicKey) + } + + return privateKey + } + + @JvmStatic + fun unlockSecretKey(secretKey: PGPSecretKey, passphrase: Passphrase?): PGPPrivateKey { + return if (passphrase == null) { + unlockSecretKey(secretKey, SecretKeyRingProtector.unprotectedKeys()) + } else { + unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey)) + } + } + } +} \ No newline at end of file From 5cb6d6e41d62e66af797176dfb155716eea2661c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 1 Sep 2023 13:15:22 +0200 Subject: [PATCH 066/155] Kotlin conversion: S2KUsageFix --- .../key/protection/fixes/S2KUsageFix.java | 92 ------------------- .../key/protection/fixes/package-info.java | 8 -- .../key/protection/fixes/S2KUsageFix.kt | 79 ++++++++++++++++ 3 files changed, 79 insertions(+), 100 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/fixes/S2KUsageFix.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/fixes/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/fixes/S2KUsageFix.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/fixes/S2KUsageFix.java deleted file mode 100644 index 24fe533a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/fixes/S2KUsageFix.java +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection.fixes; - -import org.bouncycastle.bcpg.SecretKeyPacket; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.PGPDigestCalculator; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.exception.WrongPassphraseException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnlockSecretKey; - -/** - * Repair class to fix keys which use S2K usage of value {@link SecretKeyPacket#USAGE_CHECKSUM}. - * The method {@link #replaceUsageChecksumWithUsageSha1(PGPSecretKeyRing, SecretKeyRingProtector)} ensures - * that such keys are encrypted using S2K usage {@link SecretKeyPacket#USAGE_SHA1} instead. - * - * @see Related PGPainless Bug Report - * @see Related PGPainless Feature Request - * @see Related upstream BC bug report - */ -public final class S2KUsageFix { - - private S2KUsageFix() { - - } - - /** - * Repair method for keys which use S2K usage
USAGE_CHECKSUM
which is deemed insecure. - * This method fixes the private keys by changing them to
USAGE_SHA1
instead. - * - * @param keys keys - * @param protector protector to unlock and re-lock affected private keys - * @return fixed key ring - * @throws PGPException in case of a PGP error. - */ - public static PGPSecretKeyRing replaceUsageChecksumWithUsageSha1(PGPSecretKeyRing keys, SecretKeyRingProtector protector) throws PGPException { - return replaceUsageChecksumWithUsageSha1(keys, protector, false); - } - - /** - * Repair method for keys which use S2K usage
USAGE_CHECKSUM
which is deemed insecure. - * This method fixes the private keys by changing them to
USAGE_SHA1
instead. - * - * @param keys keys - * @param protector protector to unlock and re-lock affected private keys - * @param skipKeysWithMissingPassphrase if set to true, missing subkey passphrases will cause the subkey to stay unaffected. - * @return fixed key ring - * @throws PGPException in case of a PGP error. - */ - public static PGPSecretKeyRing replaceUsageChecksumWithUsageSha1(PGPSecretKeyRing keys, - SecretKeyRingProtector protector, - boolean skipKeysWithMissingPassphrase) throws PGPException { - PGPDigestCalculator digestCalculator = ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1); - for (PGPSecretKey key : keys) { - // CHECKSUM is not recommended - if (key.getS2KUsage() != SecretKeyPacket.USAGE_CHECKSUM) { - continue; - } - - long keyId = key.getKeyID(); - PBESecretKeyEncryptor encryptor = protector.getEncryptor(keyId); - if (encryptor == null) { - if (skipKeysWithMissingPassphrase) { - continue; - } - throw new WrongPassphraseException("Missing passphrase for key with ID " + Long.toHexString(keyId)); - } - - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(key, protector); - // This constructor makes use of USAGE_SHA1 by default - PGPSecretKey fixedKey = new PGPSecretKey( - privateKey, - key.getPublicKey(), - digestCalculator, - key.isMasterKey(), - protector.getEncryptor(keyId) - ); - - // replace the original key with the fixed one - keys = PGPSecretKeyRing.insertSecretKey(keys, fixedKey); - } - return keys; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/fixes/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/fixes/package-info.java deleted file mode 100644 index 06c299b1..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/fixes/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Secret Key Protection Fixes. - */ -package org.pgpainless.key.protection.fixes; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt new file mode 100644 index 00000000..aeef0654 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection.fixes + +import org.bouncycastle.bcpg.SecretKeyPacket +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.exception.WrongPassphraseException +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey + +/** + * Repair class to fix keys which use S2K usage of value [SecretKeyPacket.USAGE_CHECKSUM]. + * The method [replaceUsageChecksumWithUsageSha1] ensures that such keys are encrypted using + * S2K usage [SecretKeyPacket.USAGE_SHA1] instead. + * + * @see Related PGPainless Bug Report + * @see Related PGPainless Feature Request + * @see Related upstream BC bug report + */ +class S2KUsageFix { + + companion object { + + /** + * Repair method for keys which use S2K usage
USAGE_CHECKSUM
which is deemed insecure. + * This method fixes the private keys by changing them to
USAGE_SHA1
instead. + * + * @param keys keys + * @param protector protector to unlock and re-lock affected private keys + * @param skipKeysWithMissingPassphrase if set to true, missing subkey passphrases will cause the subkey to stay unaffected. + * @return fixed key ring + * @throws PGPException in case of a PGP error. + */ + @JvmStatic + @JvmOverloads + fun replaceUsageChecksumWithUsageSha1( + keys: PGPSecretKeyRing, + protector: SecretKeyRingProtector, + skipKeysWithMissingPassphrase: Boolean = false + ): PGPSecretKeyRing { + val digestCalculator = ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1) + val keyList = mutableListOf() + for (key in keys) { + // CHECKSUM is not recommended + if (key.s2KUsage != SecretKeyPacket.USAGE_CHECKSUM) { + keyList.add(key) + continue + } + + val keyId = key.keyID + val encryptor = protector.getEncryptor(keyId) + if (encryptor == null) { + if (skipKeysWithMissingPassphrase) { + keyList.add(key) + continue + } + throw WrongPassphraseException("Missing passphrase for key with ID " + java.lang.Long.toHexString(keyId)) + } + + val privateKey = unlockSecretKey(key, protector) + // This constructor makes use of USAGE_SHA1 by default + val fixedKey = PGPSecretKey( + privateKey, + key.publicKey, + digestCalculator, + key.isMasterKey, + protector.getEncryptor(keyId) + ) + keyList.add(fixedKey) + } + return PGPSecretKeyRing(keyList) + } + } +} \ No newline at end of file From e3f51fbf564b786c3a7e601179f8b73cdc15c840 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 1 Sep 2023 13:43:04 +0200 Subject: [PATCH 067/155] Kotlin conversion: SecretKeyPassphraseProvider and subclasses This commit also adds a workaround to build.gradle which enables proper Java interop for Kotlin interfaces with default implementations --- build.gradle | 7 ++++ .../key/protection/package-info.java | 8 ---- .../MapBasedPassphraseProvider.java | 42 ------------------- .../SecretKeyPassphraseProvider.java | 40 ------------------ .../SolitaryPassphraseProvider.java | 33 --------------- .../passphrase_provider/package-info.java | 8 ---- .../MapBasedPassphraseProvider.kt | 21 ++++++++++ .../SecretKeyPassphraseProvider.kt | 39 +++++++++++++++++ .../SolitaryPassphraseProvider.kt | 17 ++++++++ 9 files changed, 84 insertions(+), 131 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/package-info.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.kt diff --git a/build.gradle b/build.gradle index 540037a8..57cc04c4 100644 --- a/build.gradle +++ b/build.gradle @@ -67,6 +67,13 @@ allprojects { fileMode = 0644 } + // Compatibility of default implementations in kotlin interfaces with Java implementations. + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + freeCompilerArgs += ["-Xjvm-default=all-compatibility"] + } + } + project.ext { rootConfigDir = new File(rootDir, 'config') gitCommit = getGitCommit() diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/package-info.java deleted file mode 100644 index b936025f..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to OpenPGP secret key password protection. - */ -package org.pgpainless.key.protection; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java deleted file mode 100644 index 3f0ee2f9..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection.passphrase_provider; - -import java.util.Map; -import javax.annotation.Nullable; - -import org.pgpainless.util.Passphrase; - -/** - * Implementation of the {@link SecretKeyPassphraseProvider} that holds a map of different {@link Passphrase passphrases}. - * It will return the right passphrase depending on the key-id. - * - * Note: This provider might return null! - * TODO: Make this null-safe and throw an exception instead? - */ -public class MapBasedPassphraseProvider implements SecretKeyPassphraseProvider { - - private final Map map; - - /** - * Create a new map based passphrase provider. - * - * @param passphraseMap map of key-ids and passphrases - */ - public MapBasedPassphraseProvider(Map passphraseMap) { - this.map = passphraseMap; - } - - @Nullable - @Override - public Passphrase getPassphraseFor(long keyId) { - return map.get(keyId); - } - - @Override - public boolean hasPassphrase(long keyId) { - return map.containsKey(keyId); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java deleted file mode 100644 index 91c2bc95..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection.passphrase_provider; - -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPSecretKey; -import org.pgpainless.util.Passphrase; - -/** - * Interface to allow the user to provide a {@link Passphrase} for an encrypted OpenPGP secret key. - */ -public interface SecretKeyPassphraseProvider { - - /** - * Return a passphrase for the given secret key. - * If no record is found, return null. - * Note: In case of an unprotected secret key, this method must may not return null, but a {@link Passphrase} with - * a content of null. - * - * @param secretKey secret key - * @return passphrase or null, if no passphrase record is found. - */ - @Nullable default Passphrase getPassphraseFor(PGPSecretKey secretKey) { - return getPassphraseFor(secretKey.getKeyID()); - } - /** - * Return a passphrase for the given key. If no record has been found, return null. - * Note: In case of an unprotected secret key, this method must may not return null, but a {@link Passphrase} with - * a content of null. - * - * @param keyId if of the secret key - * @return passphrase or null, if no passphrase record has been found. - */ - @Nullable Passphrase getPassphraseFor(long keyId); - - boolean hasPassphrase(long keyId); -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java deleted file mode 100644 index 9400229b..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection.passphrase_provider; - -import javax.annotation.Nullable; - -import org.pgpainless.util.Passphrase; - -/** - * Implementation of the {@link SecretKeyPassphraseProvider} that holds a single {@link Passphrase}. - */ -public class SolitaryPassphraseProvider implements SecretKeyPassphraseProvider { - - private final Passphrase passphrase; - - public SolitaryPassphraseProvider(Passphrase passphrase) { - this.passphrase = passphrase; - } - - @Nullable - @Override - public Passphrase getPassphraseFor(long keyId) { - // always return the same passphrase. - return passphrase; - } - - @Override - public boolean hasPassphrase(long keyId) { - return true; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/package-info.java deleted file mode 100644 index e70ad81f..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Passphrase Provider classes. - */ -package org.pgpainless.key.protection.passphrase_provider; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.kt new file mode 100644 index 00000000..22260126 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.kt @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection.passphrase_provider + +import org.pgpainless.util.Passphrase + +/** + * Implementation of the [SecretKeyPassphraseProvider] that holds a map of key-IDs and respective [Passphrase]. + * It will return the right passphrase depending on the key-id. + * + * Note: This provider might return null! + * TODO: Make this null-safe and throw an exception instead? + */ +class MapBasedPassphraseProvider(val map: Map) : SecretKeyPassphraseProvider { + + override fun getPassphraseFor(keyId: Long): Passphrase? = map[keyId] + + override fun hasPassphrase(keyId: Long): Boolean = map.containsKey(keyId) +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.kt new file mode 100644 index 00000000..aaf24b70 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.kt @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection.passphrase_provider + +import org.bouncycastle.openpgp.PGPSecretKey +import org.pgpainless.util.Passphrase + +/** + * Interface to allow the user to provide a [Passphrase] for an encrypted OpenPGP secret key. + */ +interface SecretKeyPassphraseProvider { + + /** + * Return a passphrase for the given secret key. + * If no record is found, return null. + * Note: In case of an unprotected secret key, this method must may not return null, but a [Passphrase] with + * a content of null. + * + * @param secretKey secret key + * @return passphrase or null, if no passphrase record is found. + */ + fun getPassphraseFor(secretKey: PGPSecretKey): Passphrase? { + return getPassphraseFor(secretKey.keyID) + } + + /** + * Return a passphrase for the given key. If no record has been found, return null. + * Note: In case of an unprotected secret key, this method must may not return null, but a [Passphrase] with + * a content of null. + * + * @param keyId if of the secret key + * @return passphrase or null, if no passphrase record has been found. + */ + fun getPassphraseFor(keyId: Long): Passphrase? + + fun hasPassphrase(keyId: Long): Boolean +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.kt new file mode 100644 index 00000000..46f77342 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.kt @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection.passphrase_provider + +import org.pgpainless.util.Passphrase + +/** + * Implementation of the [SecretKeyPassphraseProvider] that holds a single [Passphrase]. + */ +class SolitaryPassphraseProvider(val passphrase: Passphrase?) : SecretKeyPassphraseProvider { + + override fun getPassphraseFor(keyId: Long): Passphrase? = passphrase + + override fun hasPassphrase(keyId: Long): Boolean = true +} \ No newline at end of file From bbd956dbb7c7eb2796df5661682f0a9e8aeebed1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 1 Sep 2023 14:29:48 +0200 Subject: [PATCH 068/155] Kotlin conversion: KeyRingReader --- .../pgpainless/key/parsing/KeyRingReader.java | 415 ------------------ .../pgpainless/key/parsing/package-info.java | 8 - .../pgpainless/key/parsing/KeyRingReader.kt | 333 ++++++++++++++ 3 files changed, 333 insertions(+), 423 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/parsing/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/parsing/KeyRingReader.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java b/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java deleted file mode 100644 index 2074e730..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/parsing/KeyRingReader.java +++ /dev/null @@ -1,415 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.parsing; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPMarker; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.util.io.Streams; -import org.pgpainless.PGPainless; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.collection.PGPKeyRingCollection; -import org.pgpainless.util.ArmorUtils; - -public class KeyRingReader { - - public static final int MAX_ITERATIONS = 10000; - - @SuppressWarnings("CharsetObjectCanBeUsed") - public static final Charset UTF8 = Charset.forName("UTF-8"); - - /** - * Read a {@link PGPKeyRing} (either {@link PGPSecretKeyRing} or {@link PGPPublicKeyRing}) from the given - * {@link InputStream}. - * - * @param inputStream inputStream containing the OpenPGP key or certificate - * @return key ring - * @throws IOException in case of an IO error - */ - @Nullable - public PGPKeyRing keyRing(@Nonnull InputStream inputStream) - throws IOException { - return readKeyRing(inputStream); - } - - /** - * Read a {@link PGPKeyRing} (either {@link PGPSecretKeyRing} or {@link PGPPublicKeyRing}) from the given - * byte array. - * - * @param bytes byte array containing the OpenPGP key or certificate - * @return key ring - * @throws IOException in case of an IO error - */ - @Nullable - public PGPKeyRing keyRing(@Nonnull byte[] bytes) - throws IOException { - return keyRing(new ByteArrayInputStream(bytes)); - } - - /** - * Read a {@link PGPKeyRing} (either {@link PGPSecretKeyRing} or {@link PGPPublicKeyRing}) from the given - * ASCII armored string. - * - * @param asciiArmored ASCII armored OpenPGP key or certificate - * @return key ring - * @throws IOException in case of an IO error - */ - @Nullable - public PGPKeyRing keyRing(@Nonnull String asciiArmored) - throws IOException { - return keyRing(asciiArmored.getBytes(UTF8)); - } - - @Nullable - public PGPPublicKeyRing publicKeyRing(@Nonnull InputStream inputStream) - throws IOException { - return readPublicKeyRing(inputStream); - } - - @Nullable - public PGPPublicKeyRing publicKeyRing(@Nonnull byte[] bytes) - throws IOException { - return publicKeyRing(new ByteArrayInputStream(bytes)); - } - - @Nullable - public PGPPublicKeyRing publicKeyRing(@Nonnull String asciiArmored) - throws IOException { - return publicKeyRing(asciiArmored.getBytes(UTF8)); - } - - @Nonnull - public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull InputStream inputStream) - throws IOException { - return readPublicKeyRingCollection(inputStream); - } - - @Nonnull - public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull byte[] bytes) - throws IOException { - return publicKeyRingCollection(new ByteArrayInputStream(bytes)); - } - - @Nonnull - public PGPPublicKeyRingCollection publicKeyRingCollection(@Nonnull String asciiArmored) - throws IOException { - return publicKeyRingCollection(asciiArmored.getBytes(UTF8)); - } - - @Nullable - public PGPSecretKeyRing secretKeyRing(@Nonnull InputStream inputStream) - throws IOException { - return readSecretKeyRing(inputStream); - } - - @Nullable - public PGPSecretKeyRing secretKeyRing(@Nonnull byte[] bytes) - throws IOException { - return secretKeyRing(new ByteArrayInputStream(bytes)); - } - - @Nullable - public PGPSecretKeyRing secretKeyRing(@Nonnull String asciiArmored) - throws IOException { - return secretKeyRing(asciiArmored.getBytes(UTF8)); - } - - @Nonnull - public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull InputStream inputStream) - throws IOException { - return readSecretKeyRingCollection(inputStream); - } - - @Nonnull - public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull byte[] bytes) - throws IOException { - return secretKeyRingCollection(new ByteArrayInputStream(bytes)); - } - - @Nonnull - public PGPSecretKeyRingCollection secretKeyRingCollection(@Nonnull String asciiArmored) - throws IOException { - return secretKeyRingCollection(asciiArmored.getBytes(UTF8)); - } - - @Nonnull - public PGPKeyRingCollection keyRingCollection(@Nonnull InputStream inputStream, boolean isSilent) - throws IOException, PGPException { - return readKeyRingCollection(inputStream, isSilent); - } - - @Nonnull - public PGPKeyRingCollection keyRingCollection(@Nonnull byte[] bytes, boolean isSilent) - throws IOException, PGPException { - return keyRingCollection(new ByteArrayInputStream(bytes), isSilent); - } - - @Nonnull - public PGPKeyRingCollection keyRingCollection(@Nonnull String asciiArmored, boolean isSilent) - throws IOException, PGPException { - return keyRingCollection(asciiArmored.getBytes(UTF8), isSilent); - } - - /** - * Read a {@link PGPKeyRing} (either {@link PGPSecretKeyRing} or {@link PGPPublicKeyRing}) from the given - * {@link InputStream}. - * This method will attempt to read at most {@link #MAX_ITERATIONS} objects from the stream before aborting. - * The first {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} will be returned. - * - * @param inputStream inputStream containing the OpenPGP key or certificate - * @return key ring - * @throws IOException in case of an IO error - */ - @Nullable - public static PGPKeyRing readKeyRing(@Nonnull InputStream inputStream) - throws IOException { - return readKeyRing(inputStream, MAX_ITERATIONS); - } - - /** - * Read a {@link PGPKeyRing} (either {@link PGPSecretKeyRing} or {@link PGPPublicKeyRing}) from the given - * {@link InputStream}. - * This method will attempt to read at most
maxIterations
objects from the stream before aborting. - * The first {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} will be returned. - * - * @param inputStream inputStream containing the OpenPGP key or certificate - * @param maxIterations maximum number of objects that are read before the method will abort - * @return key ring - * @throws IOException in case of an IO error - */ - @Nullable - public static PGPKeyRing readKeyRing(@Nonnull InputStream inputStream, int maxIterations) - throws IOException { - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - ArmorUtils.getDecoderStream(inputStream)); - int i = 0; - Object next; - do { - next = objectFactory.nextObject(); - if (next == null) { - return null; - } - if (next instanceof PGPMarker) { - continue; - } - if (next instanceof PGPSecretKeyRing) { - return (PGPSecretKeyRing) next; - } - if (next instanceof PGPPublicKeyRing) { - return (PGPPublicKeyRing) next; - } - } while (++i < maxIterations); - - throw new IOException("Loop exceeded max iteration count."); - } - - @Nullable - public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream) - throws IOException { - return readPublicKeyRing(inputStream, MAX_ITERATIONS); - } - - /** - * Read a public key ring from the provided {@link InputStream}. - * If more than maxIterations PGP packets are encountered before a {@link PGPPublicKeyRing} is read, - * an {@link IOException} is thrown. - * - * @param inputStream input stream - * @param maxIterations max iterations before abort - * @return public key ring - * - * @throws IOException in case of an IO error or exceeding of max iterations - */ - @Nullable - public static PGPPublicKeyRing readPublicKeyRing(@Nonnull InputStream inputStream, int maxIterations) - throws IOException { - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - ArmorUtils.getDecoderStream(inputStream)); - int i = 0; - Object next; - do { - next = objectFactory.nextObject(); - if (next == null) { - return null; - } - if (next instanceof PGPMarker) { - continue; - } - if (next instanceof PGPPublicKeyRing) { - return (PGPPublicKeyRing) next; - } - } while (++i < maxIterations); - - throw new IOException("Loop exceeded max iteration count."); - } - - @Nonnull - public static PGPPublicKeyRingCollection readPublicKeyRingCollection(@Nonnull InputStream inputStream) - throws IOException { - return readPublicKeyRingCollection(inputStream, MAX_ITERATIONS); - } - - /** - * Read a public key ring collection from the provided {@link InputStream}. - * If more than maxIterations PGP packets are encountered before the stream is exhausted, - * an {@link IOException} is thrown. - * If the stream contain secret key packets, their public key parts are extracted and returned. - * - * @param inputStream input stream - * @param maxIterations max iterations before abort - * @return public key ring collection - * - * @throws IOException in case of an IO error or exceeding of max iterations - */ - @Nonnull - public static PGPPublicKeyRingCollection readPublicKeyRingCollection(@Nonnull InputStream inputStream, int maxIterations) - throws IOException { - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - ArmorUtils.getDecoderStream(inputStream)); - - List rings = new ArrayList<>(); - int i = 0; - Object next; - do { - next = objectFactory.nextObject(); - if (next == null) { - return new PGPPublicKeyRingCollection(rings); - } - if (next instanceof PGPMarker) { - continue; - } - if (next instanceof PGPPublicKeyRing) { - rings.add((PGPPublicKeyRing) next); - continue; - } - // Parse public keys from secret keys - if (next instanceof PGPSecretKeyRing) { - rings.add(PGPainless.extractCertificate((PGPSecretKeyRing) next)); - continue; - } - if (next instanceof PGPPublicKeyRingCollection) { - PGPPublicKeyRingCollection collection = (PGPPublicKeyRingCollection) next; - Iterator iterator = collection.getKeyRings(); - while (iterator.hasNext()) { - rings.add(iterator.next()); - } - } - } while (++i < maxIterations); - - throw new IOException("Loop exceeded max iteration count."); - } - - @Nullable - public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream) - throws IOException { - return readSecretKeyRing(inputStream, MAX_ITERATIONS); - } - - /** - * Read a secret key ring from the provided {@link InputStream}. - * If more than maxIterations PGP packets are encountered before a {@link PGPSecretKeyRing} is read, - * an {@link IOException} is thrown. - * - * @param inputStream input stream - * @param maxIterations max iterations before abort - * @return public key ring - * - * @throws IOException in case of an IO error or exceeding of max iterations - */ - @Nullable - public static PGPSecretKeyRing readSecretKeyRing(@Nonnull InputStream inputStream, int maxIterations) - throws IOException { - InputStream decoderStream = ArmorUtils.getDecoderStream(inputStream); - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream); - int i = 0; - Object next; - do { - next = objectFactory.nextObject(); - if (next == null) { - return null; - } - if (next instanceof PGPMarker) { - continue; - } - if (next instanceof PGPSecretKeyRing) { - Streams.drain(decoderStream); - return (PGPSecretKeyRing) next; - } - } while (++i < maxIterations); - - throw new IOException("Loop exceeded max iteration count."); - } - - @Nonnull - public static PGPSecretKeyRingCollection readSecretKeyRingCollection(@Nonnull InputStream inputStream) - throws IOException { - return readSecretKeyRingCollection(inputStream, MAX_ITERATIONS); - } - - /** - * Read a secret key ring collection from the provided {@link InputStream}. - * If more than maxIterations PGP packets are encountered before the stream is exhausted, - * an {@link IOException} is thrown. - * - * @param inputStream input stream - * @param maxIterations max iterations before abort - * @return secret key ring collection - * - * @throws IOException in case of an IO error or exceeding of max iterations - */ - @Nonnull - public static PGPSecretKeyRingCollection readSecretKeyRingCollection(@Nonnull InputStream inputStream, - int maxIterations) - throws IOException { - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - ArmorUtils.getDecoderStream(inputStream)); - - List rings = new ArrayList<>(); - int i = 0; - Object next; - do { - next = objectFactory.nextObject(); - if (next == null) { - return new PGPSecretKeyRingCollection(rings); - } - if (next instanceof PGPMarker) { - continue; - } - if (next instanceof PGPSecretKeyRing) { - rings.add((PGPSecretKeyRing) next); - } - if (next instanceof PGPSecretKeyRingCollection) { - PGPSecretKeyRingCollection collection = (PGPSecretKeyRingCollection) next; - Iterator iterator = collection.getKeyRings(); - while (iterator.hasNext()) { - rings.add(iterator.next()); - } - } - } while (++i < maxIterations); - - throw new IOException("Loop exceeded max iteration count."); - } - - @Nonnull - public static PGPKeyRingCollection readKeyRingCollection(@Nonnull InputStream inputStream, boolean isSilent) - throws IOException, PGPException { - return new PGPKeyRingCollection(inputStream, isSilent); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/parsing/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/parsing/package-info.java deleted file mode 100644 index 50030499..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/parsing/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to OpenPGP key reading. - */ -package org.pgpainless.key.parsing; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/parsing/KeyRingReader.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/parsing/KeyRingReader.kt new file mode 100644 index 00000000..388294d5 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/parsing/KeyRingReader.kt @@ -0,0 +1,333 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.parsing + +import org.bouncycastle.openpgp.* +import org.bouncycastle.util.io.Streams +import org.pgpainless.PGPainless +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.collection.PGPKeyRingCollection +import org.pgpainless.util.ArmorUtils +import java.io.IOException +import java.io.InputStream +import java.nio.charset.Charset +import kotlin.jvm.Throws + +class KeyRingReader { + + /** + * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given [InputStream]. + * + * @param inputStream inputStream containing the OpenPGP key or certificate + * @return key ring + * @throws IOException in case of an IO error + */ + @Throws(IOException::class) + fun keyRing(inputStream: InputStream): PGPKeyRing? = + readKeyRing(inputStream) + + /** + * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given byte array. + * + * @param bytes byte array containing the OpenPGP key or certificate + * @return key ring + * @throws IOException in case of an IO error + */ + @Throws(IOException::class) + fun keyRing(bytes: ByteArray): PGPKeyRing? = + keyRing(bytes.inputStream()) + + /** + * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given + * ASCII armored string. + * + * @param asciiArmored ASCII armored OpenPGP key or certificate + * @return key ring + * @throws IOException in case of an IO error + */ + @Throws(IOException::class) + fun keyRing(asciiArmored: String): PGPKeyRing? = + keyRing(asciiArmored.toByteArray(UTF8)) + + @Throws(IOException::class) + fun publicKeyRing(inputStream: InputStream): PGPPublicKeyRing? = + readPublicKeyRing(inputStream) + + @Throws(IOException::class) + fun publicKeyRing(bytes: ByteArray): PGPPublicKeyRing? = + publicKeyRing(bytes.inputStream()) + + @Throws(IOException::class) + fun publicKeyRing(asciiArmored: String): PGPPublicKeyRing? = + publicKeyRing(asciiArmored.toByteArray(UTF8)) + + @Throws(IOException::class) + fun publicKeyRingCollection(inputStream: InputStream): PGPPublicKeyRingCollection = + readPublicKeyRingCollection(inputStream) + + @Throws(IOException::class) + fun publicKeyRingCollection(bytes: ByteArray): PGPPublicKeyRingCollection = + publicKeyRingCollection(bytes.inputStream()) + + @Throws(IOException::class) + fun publicKeyRingCollection(asciiArmored: String): PGPPublicKeyRingCollection = + publicKeyRingCollection(asciiArmored.toByteArray(UTF8)) + + @Throws(IOException::class) + fun secretKeyRing(inputStream: InputStream): PGPSecretKeyRing? = + readSecretKeyRing(inputStream) + + @Throws(IOException::class) + fun secretKeyRing(bytes: ByteArray): PGPSecretKeyRing? = + secretKeyRing(bytes.inputStream()) + + @Throws(IOException::class) + fun secretKeyRing(asciiArmored: String): PGPSecretKeyRing? = + secretKeyRing(asciiArmored.toByteArray(UTF8)) + + @Throws(IOException::class) + fun secretKeyRingCollection(inputStream: InputStream): PGPSecretKeyRingCollection = + readSecretKeyRingCollection(inputStream) + + @Throws(IOException::class) + fun secretKeyRingCollection(bytes: ByteArray): PGPSecretKeyRingCollection = + secretKeyRingCollection(bytes.inputStream()) + + @Throws(IOException::class) + fun secretKeyRingCollection(asciiArmored: String): PGPSecretKeyRingCollection = + secretKeyRingCollection(asciiArmored.toByteArray(UTF8)) + + @Throws(IOException::class) + fun keyRingCollection(inptStream: InputStream, isSilent: Boolean): PGPKeyRingCollection = + readKeyRingCollection(inptStream, isSilent) + + @Throws(IOException::class) + fun keyRingCollection(bytes: ByteArray, isSilent: Boolean): PGPKeyRingCollection = + keyRingCollection(bytes.inputStream(), isSilent) + + @Throws(IOException::class) + fun keyRingCollection(asciiArmored: String, isSilent: Boolean): PGPKeyRingCollection = + keyRingCollection(asciiArmored.toByteArray(UTF8), isSilent) + + companion object { + private const val MAX_ITERATIONS = 10000 + + @JvmStatic + val UTF8: Charset = charset("UTF8") + + + /** + * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given [InputStream]. + * This method will attempt to read at most
maxIterations
objects from the stream before aborting. + * The first [PGPPublicKeyRing] or [PGPSecretKeyRing] will be returned. + * + * @param inputStream inputStream containing the OpenPGP key or certificate + * @param maxIterations maximum number of objects that are read before the method will abort + * @return key ring + * @throws IOException in case of an IO error + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun readKeyRing(inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS): PGPKeyRing? { + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( + ArmorUtils.getDecoderStream(inputStream)) + + try { + for ((i, next) in objectFactory.withIndex()) { + if (i >= maxIterations) { + throw IOException("Loop exceeded max iteration count.") + } + if (next is PGPMarker) { + continue + } + if (next is PGPSecretKeyRing) { + return next + } + if (next is PGPPublicKeyRing) { + return next + } + continue + } + } catch (e : PGPRuntimeOperationException) { + throw e.cause!! + } + return null + } + + /** + * Read a public key ring from the provided [InputStream]. + * If more than maxIterations PGP packets are encountered before a [PGPPublicKeyRing] is read, + * an [IOException] is thrown. + * + * @param inputStream input stream + * @param maxIterations max iterations before abort + * @return public key ring + * + * @throws IOException in case of an IO error or exceeding of max iterations + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun readPublicKeyRing(inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS): PGPPublicKeyRing? { + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( + ArmorUtils.getDecoderStream(inputStream)) + + try { + for ((i, next) in objectFactory.withIndex()) { + if (i >= maxIterations) { + throw IOException("Loop exceeded max iteration count.") + } + if (next is PGPMarker) { + continue + } + if (next is PGPPublicKeyRing) { + return next + } + continue + } + } catch (e : PGPRuntimeOperationException) { + throw e.cause!! + } + return null + } + + /** + * Read a public key ring collection from the provided [InputStream]. + * If more than maxIterations PGP packets are encountered before the stream is exhausted, + * an [IOException] is thrown. + * If the stream contain secret key packets, their public key parts are extracted and returned. + * + * @param inputStream input stream + * @param maxIterations max iterations before abort + * @return public key ring collection + * + * @throws IOException in case of an IO error or exceeding of max iterations + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun readPublicKeyRingCollection(inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS): PGPPublicKeyRingCollection { + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( + ArmorUtils.getDecoderStream(inputStream)) + val certificates = mutableListOf() + try { + for ((i, next) in objectFactory.withIndex()) { + if (i >= maxIterations) { + throw IOException("Loop exceeded max iteration count.") + } + if (next is PGPMarker) { + continue + } + if (next is PGPPublicKeyRing) { + certificates.add(next) + continue + } + if (next is PGPSecretKeyRing) { + certificates.add(PGPainless.extractCertificate(next)) + continue + } + if (next is PGPPublicKeyRingCollection) { + certificates.addAll(next) + continue + } + } + } catch (e : PGPRuntimeOperationException) { + throw e.cause!! + } + return PGPPublicKeyRingCollection(certificates) + } + + /** + * Read a secret key ring from the provided [InputStream]. + * If more than maxIterations PGP packets are encountered before a [PGPSecretKeyRing] is read, + * an [IOException] is thrown. + * + * @param inputStream input stream + * @param maxIterations max iterations before abort + * @return public key ring + * + * @throws IOException in case of an IO error or exceeding of max iterations + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun readSecretKeyRing(inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS): PGPSecretKeyRing? { + val decoderStream = ArmorUtils.getDecoderStream(inputStream) + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream) + + try { + for ((i, next) in objectFactory.withIndex()) { + if (i >= maxIterations) { + throw IOException("Loop exceeded max iteration count.") + } + if (next is PGPMarker) { + continue + } + if (next is PGPSecretKeyRing) { + Streams.drain(decoderStream) + return next + } + } + } catch (e : PGPRuntimeOperationException) { + throw e.cause!! + } + return null + } + + /** + * Read a secret key ring collection from the provided [InputStream]. + * If more than maxIterations PGP packets are encountered before the stream is exhausted, + * an [IOException] is thrown. + * + * @param inputStream input stream + * @param maxIterations max iterations before abort + * @return secret key ring collection + * + * @throws IOException in case of an IO error or exceeding of max iterations + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun readSecretKeyRingCollection(inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS): PGPSecretKeyRingCollection { + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( + ArmorUtils.getDecoderStream(inputStream)) + + val secretKeys = mutableListOf() + try { + for ((i, next) in objectFactory.withIndex()) { + if (i >= maxIterations) { + throw IOException("Loop exceeded max iteration count.") + } + if (next is PGPMarker) { + continue + } + if (next is PGPSecretKeyRing) { + secretKeys.add(next) + continue + } + if (next is PGPSecretKeyRingCollection) { + secretKeys.addAll(next) + continue + } + } + } catch (e : PGPRuntimeOperationException) { + throw e.cause!! + } + return PGPSecretKeyRingCollection(secretKeys) + } + + @JvmStatic + @Throws(IOException::class) + fun readKeyRingCollection(inputStream: InputStream, isSilent: Boolean): PGPKeyRingCollection = + PGPKeyRingCollection(inputStream, isSilent) + } + +} \ No newline at end of file From e9caa4af1f615c69196ab6aba8a584357f3797e6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 1 Sep 2023 18:23:12 +0200 Subject: [PATCH 069/155] Kotlin conversion: EncryptionBuilder + Interface --- .../encryption_signing/EncryptionBuilder.java | 77 ------------------- .../EncryptionBuilderInterface.java | 38 --------- .../encryption_signing/EncryptionBuilder.kt | 59 ++++++++++++++ .../EncryptionBuilderInterface.kt | 36 +++++++++ 4 files changed, 95 insertions(+), 115 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilderInterface.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilderInterface.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java deleted file mode 100644 index 9490551b..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPException; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator; -import org.pgpainless.key.SubkeyIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class EncryptionBuilder implements EncryptionBuilderInterface { - - private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionBuilder.class); - - private OutputStream outputStream; - - @Override - public WithOptions onOutputStream(@Nonnull OutputStream outputStream) { - this.outputStream = outputStream; - return new WithOptionsImpl(); - } - - class WithOptionsImpl implements WithOptions { - @Override - public EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException { - if (options == null) { - throw new NullPointerException("ProducerOptions cannot be null."); - } - return new EncryptionStream(outputStream, options); - } - } - - /** - * Negotiate the {@link SymmetricKeyAlgorithm} used for message encryption. - * - * @param encryptionOptions encryption options - * @return negotiated symmetric key algorithm - */ - public static SymmetricKeyAlgorithm negotiateSymmetricEncryptionAlgorithm(EncryptionOptions encryptionOptions) { - List> preferences = new ArrayList<>(); - for (SubkeyIdentifier key : encryptionOptions.getKeyViews().keySet()) { - preferences.add(encryptionOptions.getKeyViews().get(key).getPreferredSymmetricKeyAlgorithms()); - } - - SymmetricKeyAlgorithm algorithm = SymmetricKeyAlgorithmNegotiator - .byPopularity() - .negotiate( - PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy(), - encryptionOptions.getEncryptionAlgorithmOverride(), - preferences); - LOGGER.debug("Negotiation resulted in {} being the symmetric encryption algorithm of choice.", algorithm); - return algorithm; - } - - public static CompressionAlgorithm negotiateCompressionAlgorithm(ProducerOptions producerOptions) { - CompressionAlgorithm compressionAlgorithmOverride = producerOptions.getCompressionAlgorithmOverride(); - if (compressionAlgorithmOverride != null) { - return compressionAlgorithmOverride; - } - - // TODO: Negotiation - - return PGPainless.getPolicy().getCompressionAlgorithmPolicy().defaultCompressionAlgorithm(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilderInterface.java deleted file mode 100644 index c705c0b1..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilderInterface.java +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.io.IOException; -import java.io.OutputStream; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPException; - -public interface EncryptionBuilderInterface { - - /** - * Create a {@link EncryptionStream} on an {@link OutputStream} that contains the plain data that - * shall be encrypted and or signed. - * - * @param outputStream output stream of the plain data. - * @return api handle - */ - WithOptions onOutputStream(@Nonnull OutputStream outputStream); - - interface WithOptions { - - /** - * Create an {@link EncryptionStream} with the given options (recipients, signers, algorithms...). - * - * @param options options - * @return encryption stream - * - * @throws PGPException if something goes wrong during encryption stream preparation - * @throws IOException if something goes wrong during encryption stream preparation (writing headers) - */ - EncryptionStream withOptions(ProducerOptions options) throws PGPException, IOException; - - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt new file mode 100644 index 00000000..324fcf3b --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.pgpainless.PGPainless.Companion.getPolicy +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.OutputStream + +class EncryptionBuilder : EncryptionBuilderInterface { + override fun onOutputStream(outputStream: OutputStream): EncryptionBuilderInterface.WithOptions { + return WithOptionsImpl(outputStream) + } + + class WithOptionsImpl(val outputStream: OutputStream) : EncryptionBuilderInterface.WithOptions { + + override fun withOptions(options: ProducerOptions): EncryptionStream { + return EncryptionStream(outputStream, options) + } + } + + companion object { + + @JvmStatic + val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java) + + /** + * Negotiate the [SymmetricKeyAlgorithm] used for message encryption. + * + * @param encryptionOptions encryption options + * @return negotiated symmetric key algorithm + */ + @JvmStatic + fun negotiateSymmetricEncryptionAlgorithm(encryptionOptions: EncryptionOptions): SymmetricKeyAlgorithm { + val preferences = encryptionOptions.keyViews.values + .map { it.preferredSymmetricKeyAlgorithms } + .toList() + val algorithm = byPopularity().negotiate( + getPolicy().symmetricKeyEncryptionAlgorithmPolicy, + encryptionOptions.encryptionAlgorithmOverride, + preferences) + LOGGER.debug("Negotiation resulted in {} being the symmetric encryption algorithm of choice.", algorithm) + return algorithm + } + + @JvmStatic + fun negotiateCompressionAlgorithm(producerOptions: ProducerOptions): CompressionAlgorithm { + val compressionAlgorithmOverride = producerOptions.compressionAlgorithmOverride + return compressionAlgorithmOverride ?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm() + + // TODO: Negotiation + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilderInterface.kt new file mode 100644 index 00000000..2d42c68a --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilderInterface.kt @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.openpgp.PGPException +import java.io.IOException +import java.io.OutputStream + +fun interface EncryptionBuilderInterface { + + /** + * Create a [EncryptionStream] wrapping an [OutputStream]. Data that is piped through the + * [EncryptionStream] will be encrypted and/or signed. + * + * @param outputStream output stream which receives the encrypted / signed data. + * @return api handle + */ + fun onOutputStream(outputStream: OutputStream): WithOptions + + fun interface WithOptions { + + /** + * Create an [EncryptionStream] with the given options (recipients, signers, algorithms...). + * + * @param options options + * @return encryption stream + * + * @throws PGPException if something goes wrong during encryption stream preparation + * @throws IOException if something goes wrong during encryption stream preparation (writing headers) + */ + @Throws(PGPException::class, IOException::class) + fun withOptions(options: ProducerOptions): EncryptionStream + } +} \ No newline at end of file From f3ea9f62e18a4f5518e25ae8a37c25aeec180ea3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 3 Sep 2023 17:55:46 +0200 Subject: [PATCH 070/155] Kotlin conversion: EncryptionOptions --- .../encryption_signing/EncryptionOptions.java | 473 ------------------ .../encryption_signing/EncryptionOptions.kt | 294 +++++++++++ .../EncryptionOptionsTest.java | 9 +- 3 files changed, 300 insertions(+), 476 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java deleted file mode 100644 index bb937a1a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java +++ /dev/null @@ -1,473 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; -import org.pgpainless.algorithm.EncryptionPurpose; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.authentication.CertificateAuthenticity; -import org.pgpainless.authentication.CertificateAuthority; -import org.pgpainless.exception.KeyException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.key.info.KeyAccessor; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.util.Passphrase; - -/** - * Options for the encryption process. - * This class can be used to set encryption parameters, like encryption keys and passphrases, algorithms etc. - *

- * A typical use might look like follows: - *

- * {@code
- * EncryptionOptions opt = new EncryptionOptions();
- * opt.addRecipient(aliceKey, "Alice ");
- * opt.addPassphrase(Passphrase.fromPassword("AdditionalDecryptionPassphrase123"));
- * }
- * 
- *

- * To use a custom symmetric encryption algorithm, use {@link #overrideEncryptionAlgorithm(SymmetricKeyAlgorithm)}. - * This will cause PGPainless to use the provided algorithm for message encryption, instead of negotiating an algorithm - * by inspecting the provided recipient keys. - *

- * By default, PGPainless will encrypt to all suitable, encryption capable subkeys on each recipient's certificate. - * This behavior can be changed per recipient, e.g. by calling - *

- * {@code
- * opt.addRecipient(aliceKey, EncryptionOptions.encryptToFirstSubkey());
- * }
- * 
- * when adding the recipient key. - */ -public class EncryptionOptions { - - private final EncryptionPurpose purpose; - private final Set encryptionMethods = new LinkedHashSet<>(); - private final Set encryptionKeys = new LinkedHashSet<>(); - private final Map keyRingInfo = new HashMap<>(); - private final Map keyViews = new HashMap<>(); - private final EncryptionKeySelector encryptionKeySelector = encryptToAllCapableSubkeys(); - private boolean allowEncryptionWithMissingKeyFlags = false; - private Date evaluationDate = new Date(); - - private SymmetricKeyAlgorithm encryptionAlgorithmOverride = null; - - /** - * Encrypt to keys both carrying the key flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS} - * or {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}. - */ - public EncryptionOptions() { - this(EncryptionPurpose.ANY); - } - - public EncryptionOptions(@Nonnull EncryptionPurpose purpose) { - this.purpose = purpose; - } - - /** - * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys - * which carry either the {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS} or - * {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE} flag. - *

- * Use this if you are not sure. - * - * @return encryption options - */ - public static EncryptionOptions get() { - return new EncryptionOptions(); - } - - /** - * Override the evaluation date for recipient keys with the given date. - * - * @param evaluationDate new evaluation date - * @return this - */ - public EncryptionOptions setEvaluationDate(@Nonnull Date evaluationDate) { - this.evaluationDate = evaluationDate; - return this; - } - - /** - * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys - * which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}. - * - * @return encryption options - */ - public static EncryptionOptions encryptCommunications() { - return new EncryptionOptions(EncryptionPurpose.COMMUNICATIONS); - } - - /** - * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys - * which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}. - * - * @return encryption options - */ - public static EncryptionOptions encryptDataAtRest() { - return new EncryptionOptions(EncryptionPurpose.STORAGE); - } - - /** - * Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for - * identifiable bindings. - * Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients. - * @param userId userId - * @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address - * @param authority certificate authority - * @return encryption options - */ - public EncryptionOptions addAuthenticatableRecipients(String userId, boolean email, CertificateAuthority authority) { - return addAuthenticatableRecipients(userId, email, authority, 120); - } - - /** - * Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for - * identifiable bindings. - * Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients. - * @param userId userId - * @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address - * @param authority certificate authority - * @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated, - * 60 = partially authenticated...) - * @return encryption options - */ - public EncryptionOptions addAuthenticatableRecipients(String userId, boolean email, CertificateAuthority authority, int targetAmount) { - List identifiedCertificates = authority.lookupByUserId(userId, email, evaluationDate, targetAmount); - boolean foundAcceptable = false; - for (CertificateAuthenticity candidate : identifiedCertificates) { - if (candidate.isAuthenticated()) { - addRecipient(candidate.getCertificate()); - foundAcceptable = true; - } - } - if (!foundAcceptable) { - throw new IllegalArgumentException("Could not identify any trust-worthy certificates for '" + userId + "' and target trust amount " + targetAmount); - } - return this; - } - - /** - * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients. - * - * @param keys keys - * @return this - */ - public EncryptionOptions addRecipients(@Nonnull Iterable keys) { - if (!keys.iterator().hasNext()) { - throw new IllegalArgumentException("Set of recipient keys cannot be empty."); - } - for (PGPPublicKeyRing key : keys) { - addRecipient(key); - } - return this; - } - - /** - * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients. - * Per key ring, the selector is applied to select one or more encryption subkeys. - * - * @param keys keys - * @param selector encryption key selector - * @return this - */ - public EncryptionOptions addRecipients(@Nonnull Iterable keys, @Nonnull EncryptionKeySelector selector) { - if (!keys.iterator().hasNext()) { - throw new IllegalArgumentException("Set of recipient keys cannot be empty."); - } - for (PGPPublicKeyRing key : keys) { - addRecipient(key, selector); - } - return this; - } - - /** - * Add a recipient by providing a key and recipient user-id. - * The user-id is used to determine the recipients preferences (algorithms etc.). - * - * @param key key ring - * @param userId user id - * @return this - */ - public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key, @Nonnull CharSequence userId) { - return addRecipient(key, userId, encryptionKeySelector); - } - - /** - * Add a recipient by providing a key and recipient user-id, as well as a strategy for selecting one or multiple - * encryption capable subkeys from the key. - * - * @param key key - * @param userId user-id - * @param encryptionKeySelectionStrategy strategy to select one or more encryption subkeys to encrypt to - * @return this - */ - public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key, - @Nonnull CharSequence userId, - @Nonnull EncryptionKeySelector encryptionKeySelectionStrategy) { - KeyRingInfo info = new KeyRingInfo(key, evaluationDate); - - List encryptionSubkeys = encryptionKeySelectionStrategy - .selectEncryptionSubkeys(info.getEncryptionSubkeys(userId.toString(), purpose)); - if (encryptionSubkeys.isEmpty()) { - throw new KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key)); - } - - for (PGPPublicKey encryptionSubkey : encryptionSubkeys) { - SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID()); - keyRingInfo.put(keyId, info); - keyViews.put(keyId, new KeyAccessor.ViaUserId(info, keyId, userId.toString())); - addRecipientKey(key, encryptionSubkey, false); - } - - return this; - } - - /** - * Add a recipient by providing a key. - * - * @param key key ring - * @return this - */ - public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key) { - return addRecipient(key, encryptionKeySelector); - } - - /** - * Add a recipient by providing a key and an encryption key selection strategy. - * - * @param key key ring - * @param encryptionKeySelectionStrategy strategy used to select one or multiple encryption subkeys. - * @return this - */ - public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key, - @Nonnull EncryptionKeySelector encryptionKeySelectionStrategy) { - return addAsRecipient(key, encryptionKeySelectionStrategy, false); - } - - /** - * Add a certificate as hidden recipient. - * The recipients key-id will be obfuscated by setting a wildcard key ID. - * - * @param key recipient key - * @return this - */ - public EncryptionOptions addHiddenRecipient(@Nonnull PGPPublicKeyRing key) { - return addHiddenRecipient(key, encryptionKeySelector); - } - - /** - * Add a certificate as hidden recipient, using the provided {@link EncryptionKeySelector} to select recipient subkeys. - * The recipients key-ids will be obfuscated by setting a wildcard key ID instead. - * - * @param key recipient key - * @param encryptionKeySelectionStrategy strategy to select recipient (sub) keys. - * @return this - */ - public EncryptionOptions addHiddenRecipient(PGPPublicKeyRing key, EncryptionKeySelector encryptionKeySelectionStrategy) { - return addAsRecipient(key, encryptionKeySelectionStrategy, true); - } - - private EncryptionOptions addAsRecipient(PGPPublicKeyRing key, EncryptionKeySelector encryptionKeySelectionStrategy, boolean wildcardKeyId) { - KeyRingInfo info = new KeyRingInfo(key, evaluationDate); - - Date primaryKeyExpiration; - try { - primaryKeyExpiration = info.getPrimaryKeyExpirationDate(); - } catch (NoSuchElementException e) { - throw new KeyException.UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key)); - } - if (primaryKeyExpiration != null && primaryKeyExpiration.before(evaluationDate)) { - throw new KeyException.ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration); - } - - List encryptionSubkeys = encryptionKeySelectionStrategy - .selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose)); - - // There are some legacy keys around without key flags. - // If we allow encryption for those keys, we add valid keys without any key flags, if they are - // capable of encryption by means of their algorithm - if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) { - List validSubkeys = info.getValidSubkeys(); - for (PGPPublicKey validSubkey : validSubkeys) { - if (!validSubkey.isEncryptionKey()) { - continue; - } - // only add encryption keys with no key flags. - if (info.getKeyFlagsOf(validSubkey.getKeyID()).isEmpty()) { - encryptionSubkeys.add(validSubkey); - } - } - } - - if (encryptionSubkeys.isEmpty()) { - throw new KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key)); - } - - for (PGPPublicKey encryptionSubkey : encryptionSubkeys) { - SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID()); - keyRingInfo.put(keyId, info); - keyViews.put(keyId, new KeyAccessor.ViaKeyId(info, keyId)); - addRecipientKey(key, encryptionSubkey, wildcardKeyId); - } - - return this; - } - - private void addRecipientKey(@Nonnull PGPPublicKeyRing keyRing, - @Nonnull PGPPublicKey key, - boolean wildcardKeyId) { - encryptionKeys.add(new SubkeyIdentifier(keyRing, key.getKeyID())); - PublicKeyKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory - .getInstance().getPublicKeyKeyEncryptionMethodGenerator(key); - encryptionMethod.setUseWildcardKeyID(wildcardKeyId); - addEncryptionMethod(encryptionMethod); - } - - /** - * Add a symmetric passphrase which the message will be encrypted to. - * - * @param passphrase passphrase - * @return this - */ - public EncryptionOptions addPassphrase(@Nonnull Passphrase passphrase) { - if (passphrase.isEmpty()) { - throw new IllegalArgumentException("Passphrase must not be empty."); - } - PBEKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory - .getInstance().getPBEKeyEncryptionMethodGenerator(passphrase); - return addEncryptionMethod(encryptionMethod); - } - - /** - * Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message. - * Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase) - * or {@link PGPKeyEncryptionMethodGenerator} (public key). - * - * This method is intended for advanced users to allow encryption for specific subkeys. - * This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless. - * - * @param encryptionMethod encryption method - * @return this - */ - public EncryptionOptions addEncryptionMethod(@Nonnull PGPKeyEncryptionMethodGenerator encryptionMethod) { - encryptionMethods.add(encryptionMethod); - return this; - } - - Set getEncryptionMethods() { - return new HashSet<>(encryptionMethods); - } - - Map getKeyRingInfo() { - return new HashMap<>(keyRingInfo); - } - - Set getEncryptionKeyIdentifiers() { - return new HashSet<>(encryptionKeys); - } - - Map getKeyViews() { - return new HashMap<>(keyViews); - } - - SymmetricKeyAlgorithm getEncryptionAlgorithmOverride() { - return encryptionAlgorithmOverride; - } - - /** - * Override the used symmetric encryption algorithm. - * The symmetric encryption algorithm is used to encrypt the message itself, - * while the used symmetric key will be encrypted to all recipients using public key - * cryptography. - * - * If the algorithm is not overridden, a suitable algorithm will be negotiated. - * - * @param encryptionAlgorithm encryption algorithm override - * @return this - */ - public EncryptionOptions overrideEncryptionAlgorithm(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm) { - if (encryptionAlgorithm == SymmetricKeyAlgorithm.NULL) { - throw new IllegalArgumentException("Plaintext encryption can only be used to denote unencrypted secret keys."); - } - this.encryptionAlgorithmOverride = encryptionAlgorithm; - return this; - } - - /** - * If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will allow encryption - * for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag} subpacket. - * This is a workaround for dealing with legacy keys that have no key flags subpacket but rely on the key algorithm - * type to convey the subkeys use. - * - * @return this - */ - public EncryptionOptions setAllowEncryptionWithMissingKeyFlags() { - this.allowEncryptionWithMissingKeyFlags = true; - return this; - } - - /** - * Return

true
iff the user specified at least one encryption method, - *
false
otherwise. - * - * @return encryption methods is not empty - */ - public boolean hasEncryptionMethod() { - return !encryptionMethods.isEmpty(); - } - - public interface EncryptionKeySelector { - List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys); - } - - /** - * Only encrypt to the first valid encryption capable subkey we stumble upon. - * - * @return encryption key selector - */ - public static EncryptionKeySelector encryptToFirstSubkey() { - return new EncryptionKeySelector() { - @Override - public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { - return encryptionCapableKeys.isEmpty() ? Collections.emptyList() : Collections.singletonList(encryptionCapableKeys.get(0)); - } - }; - } - - /** - * Encrypt to any valid, encryption capable subkey on the key ring. - * - * @return encryption key selector - */ - public static EncryptionKeySelector encryptToAllCapableSubkeys() { - return new EncryptionKeySelector() { - @Override - public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { - return encryptionCapableKeys; - } - }; - } - - // TODO: Create encryptToBestSubkey() method -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt new file mode 100644 index 00000000..6a8c0125 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt @@ -0,0 +1,294 @@ +package org.pgpainless.encryption_signing + +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator +import org.pgpainless.algorithm.EncryptionPurpose +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.authentication.CertificateAuthority +import org.pgpainless.exception.KeyException +import org.pgpainless.exception.KeyException.* +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.key.info.KeyAccessor +import org.pgpainless.key.info.KeyRingInfo +import org.pgpainless.util.Passphrase +import java.util.* +import javax.annotation.Nonnull + + +class EncryptionOptions( + private val purpose: EncryptionPurpose +) { + private val _encryptionMethods: MutableSet = mutableSetOf() + private val _encryptionKeyIdentifiers: MutableSet = mutableSetOf() + private val _keyRingInfo: MutableMap = mutableMapOf() + private val _keyViews: MutableMap = mutableMapOf() + private val encryptionKeySelector: EncryptionKeySelector = encryptToAllCapableSubkeys() + + private var allowEncryptionWithMissingKeyFlags = false + private var evaluationDate = Date() + private var _encryptionAlgorithmOverride: SymmetricKeyAlgorithm? = null + + val encryptionMethods + get() = _encryptionMethods.toSet() + val encryptionKeyIdentifiers + get() = _encryptionKeyIdentifiers.toSet() + val keyRingInfo + get() = _keyRingInfo.toMap() + val keyViews + get() = _keyViews.toMap() + val encryptionAlgorithmOverride + get() = _encryptionAlgorithmOverride + + constructor(): this(EncryptionPurpose.ANY) + + /** + * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys + * which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}. + * + * @return encryption options + */ + fun setEvaluationDate(evaluationDate: Date) = apply { + this.evaluationDate = evaluationDate + } + + /** + * Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for + * identifiable bindings. + * Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients. + * @param userId userId + * @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address + * @param authority certificate authority + * @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated, + * 60 = partially authenticated...) + * @return encryption options + */ + @JvmOverloads + fun addAuthenticatableRecipients(userId: String, email: Boolean, authority: CertificateAuthority, targetAmount: Int = 120) = apply { + var foundAcceptable = false + authority.lookupByUserId(userId, email, evaluationDate, targetAmount) + .filter { it.isAuthenticated() } + .forEach { addRecipient(it.certificate) + .also { + foundAcceptable = true + } + } + require(foundAcceptable) { + "Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount." + } + } + + /** + * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients. + * + * @param keys keys + * @return this + */ + fun addRecipients(keys: Iterable) = apply { + keys.toList().let { + require(it.isNotEmpty()) { + "Set of recipient keys cannot be empty." + } + it.forEach { key -> addRecipient(key) } + } + } + + /** + * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients. + * Per key ring, the selector is applied to select one or more encryption subkeys. + * + * @param keys keys + * @param selector encryption key selector + * @return this + */ + fun addRecipients(keys: Iterable, selector: EncryptionKeySelector) = apply { + keys.toList().let { + require(it.isNotEmpty()) { + "Set of recipient keys cannot be empty." + } + it.forEach { key -> addRecipient(key, selector) } + } + } + + /** + * Add a recipient by providing a key. + * + * @param key key ring + * @return this + */ + fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector) + + /** + * Add a recipient by providing a key and recipient user-id. + * The user-id is used to determine the recipients preferences (algorithms etc.). + * + * @param key key ring + * @param userId user id + * @return this + */ + fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) = + addRecipient(key, userId, encryptionKeySelector) + + fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence, encryptionKeySelector: EncryptionKeySelector) = apply { + val info = KeyRingInfo(key, evaluationDate) + val subkeys = encryptionKeySelector.selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose)) + if (subkeys.isEmpty()) { + throw KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key)) + } + + for (subkey in subkeys) { + val keyId = SubkeyIdentifier(key, subkey.keyID) + (_keyRingInfo as MutableMap)[keyId] = info + (_keyViews as MutableMap)[keyId] = KeyAccessor.ViaUserId(info, keyId, userId.toString()) + addRecipientKey(key, subkey, false) + } + } + + fun addRecipient(key: PGPPublicKeyRing, encryptionKeySelector: EncryptionKeySelector) = apply { + addAsRecipient(key, encryptionKeySelector, false) + } + + @JvmOverloads + fun addHiddenRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector = encryptionKeySelector) = apply { + addAsRecipient(key, selector, true) + } + + private fun addAsRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector, wildcardKeyId: Boolean) = apply { + val info = KeyRingInfo(key, evaluationDate) + val primaryKeyExpiration = try { + info.primaryKeyExpirationDate + } catch (e: NoSuchElementException) { + throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key)) + } + + if (primaryKeyExpiration != null && primaryKeyExpiration < evaluationDate) { + throw ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration) + } + + var encryptionSubkeys = selector.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose)) + + // There are some legacy keys around without key flags. + // If we allow encryption for those keys, we add valid keys without any key flags, if they are + // capable of encryption by means of their algorithm + if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) { + encryptionSubkeys = info.validSubkeys + .filter { it.isEncryptionKey } + .filter { info.getKeyFlagsOf(it.keyID).isEmpty() } + } + + if (encryptionSubkeys.isEmpty()) { + throw UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key)) + } + + for (subkey in encryptionSubkeys) { + val keyId = SubkeyIdentifier(key, subkey.keyID) + (_keyRingInfo as MutableMap)[keyId] = info + (_keyViews as MutableMap)[keyId] = KeyAccessor.ViaKeyId(info, keyId) + addRecipientKey(key, subkey, wildcardKeyId) + } + } + + private fun addRecipientKey(certificate: PGPPublicKeyRing, + key: PGPPublicKey, + wildcardKeyId: Boolean) { + (_encryptionKeyIdentifiers as MutableSet).add(SubkeyIdentifier(certificate, key.keyID)) + addEncryptionMethod(ImplementationFactory.getInstance() + .getPublicKeyKeyEncryptionMethodGenerator(key) + .also { it.setUseWildcardKeyID(wildcardKeyId) }) + } + + /** + * Add a symmetric passphrase which the message will be encrypted to. + * + * @param passphrase passphrase + * @return this + */ + fun addPassphrase(passphrase: Passphrase) = apply { + require(!passphrase.isEmpty) { + "Passphrase MUST NOT be empty." + } + addEncryptionMethod(ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase)) + } + + /** + * Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message. + * Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase) + * or {@link PGPKeyEncryptionMethodGenerator} (public key). + * + * This method is intended for advanced users to allow encryption for specific subkeys. + * This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless. + * + * @param encryptionMethod encryption method + * @return this + */ + fun addEncryptionMethod(encryptionMethod: PGPKeyEncryptionMethodGenerator) = apply { + (_encryptionMethods as MutableSet).add(encryptionMethod) + } + + /** + * Override the used symmetric encryption algorithm. + * The symmetric encryption algorithm is used to encrypt the message itself, + * while the used symmetric key will be encrypted to all recipients using public key + * cryptography. + * + * If the algorithm is not overridden, a suitable algorithm will be negotiated. + * + * @param encryptionAlgorithm encryption algorithm override + * @return this + */ + fun overrideEncryptionAlgorithm(encryptionAlgorithm: SymmetricKeyAlgorithm) = apply { + require(encryptionAlgorithm != SymmetricKeyAlgorithm.NULL) { + "Encryption algorithm override cannot be NULL." + } + _encryptionAlgorithmOverride = encryptionAlgorithm + } + + /** + * If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will allow encryption + * for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag} subpacket. + * This is a workaround for dealing with legacy keys that have no key flags subpacket but rely on the key algorithm + * type to convey the subkeys use. + * + * @return this + */ + fun setAllowEncryptionWithMissingKeyFlags() = apply { + this.allowEncryptionWithMissingKeyFlags = true + } + + fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty() + + + fun interface EncryptionKeySelector { + fun selectEncryptionSubkeys(encryptionCapableKeys: List): List + } + + companion object { + @JvmStatic + fun get() = EncryptionOptions() + + @JvmStatic + fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS) + + @JvmStatic + fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE) + + /** + * Only encrypt to the first valid encryption capable subkey we stumble upon. + * + * @return encryption key selector + */ + @JvmStatic + fun encryptToFirstSubkey() = EncryptionKeySelector { encryptionCapableKeys -> + encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf() } + + /** + * Encrypt to any valid, encryption capable subkey on the key ring. + * + * @return encryption key selector + */ + @JvmStatic + fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys -> encryptionCapableKeys } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java index 7d2fa453..87dab34a 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java @@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -22,6 +23,7 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; @@ -122,7 +124,7 @@ public class EncryptionOptionsTest { EncryptionOptions options = new EncryptionOptions(); assertThrows(IllegalArgumentException.class, () -> options.addRecipients(Collections.emptyList())); assertThrows(IllegalArgumentException.class, () -> options.addRecipients(Collections.emptyList(), - encryptionCapableKeys -> encryptionCapableKeys)); + ArrayList::new)); } @Test @@ -150,8 +152,9 @@ public class EncryptionOptionsTest { assertThrows(KeyException.UnacceptableEncryptionKeyException.class, () -> options.addRecipient(publicKeys, new EncryptionOptions.EncryptionKeySelector() { + @NotNull @Override - public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { + public List selectEncryptionSubkeys(@NotNull List encryptionCapableKeys) { return Collections.emptyList(); } })); @@ -159,7 +162,7 @@ public class EncryptionOptionsTest { assertThrows(KeyException.UnacceptableEncryptionKeyException.class, () -> options.addRecipient(publicKeys, "test@pgpainless.org", new EncryptionOptions.EncryptionKeySelector() { @Override - public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { + public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { return Collections.emptyList(); } })); From 0e6a146594b15be22c46bf267ca2986d6eb7f770 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Sep 2023 13:44:04 +0200 Subject: [PATCH 071/155] Add missing license header --- .../org/pgpainless/encryption_signing/EncryptionOptions.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt index 6a8c0125..74d2b3ca 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.encryption_signing import org.bouncycastle.openpgp.PGPPublicKey From d075ed66373ba845fa48c6397c2b894c1e6bdb1a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Sep 2023 13:44:18 +0200 Subject: [PATCH 072/155] Kotlin conversion: PGPKeyRingCollection --- .../key/collection/PGPKeyRingCollection.java | 114 ------------------ .../key/collection/package-info.java | 8 -- .../key/collection/PGPKeyRingCollection.kt | 98 +++++++++++++++ .../collection/PGPKeyRingCollectionTest.java | 4 +- 4 files changed, 100 insertions(+), 124 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/collection/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/collection/PGPKeyRingCollection.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java deleted file mode 100644 index 6cf7102d..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub , 2021 Flowcrypt a.s. -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.collection; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPMarker; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.util.ArmorUtils; - -/** - * This class describes a logic of handling a collection of different {@link PGPKeyRing}. The logic was inspired by - * {@link PGPSecretKeyRingCollection} and {@link PGPPublicKeyRingCollection}. - */ -public class PGPKeyRingCollection { - - private final PGPSecretKeyRingCollection pgpSecretKeyRingCollection; - private final PGPPublicKeyRingCollection pgpPublicKeyRingCollection; - - public PGPKeyRingCollection(@Nonnull byte[] encoding, boolean isSilent) throws IOException, PGPException { - this(new ByteArrayInputStream(encoding), isSilent); - } - - /** - * Build a {@link PGPKeyRingCollection} from the passed in input stream. - * - * @param in input stream containing data - * @param isSilent flag indicating that unsupported objects will be ignored - * @throws IOException if a problem parsing the base stream occurs - * @throws PGPException if an object is encountered which isn't a {@link PGPSecretKeyRing} or {@link PGPPublicKeyRing} - */ - public PGPKeyRingCollection(@Nonnull InputStream in, boolean isSilent) throws IOException, PGPException { - // Double getDecoderStream because of #96 - InputStream decoderStream = ArmorUtils.getDecoderStream(in); - PGPObjectFactory pgpFact = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream); - Object obj; - - List secretKeyRings = new ArrayList<>(); - List publicKeyRings = new ArrayList<>(); - - while ((obj = pgpFact.nextObject()) != null) { - if (obj instanceof PGPMarker) { - // Skip marker packets - continue; - } - if (obj instanceof PGPSecretKeyRing) { - secretKeyRings.add((PGPSecretKeyRing) obj); - } else if (obj instanceof PGPPublicKeyRing) { - publicKeyRings.add((PGPPublicKeyRing) obj); - } else if (!isSilent) { - throw new PGPException(obj.getClass().getName() + " found where " + - PGPSecretKeyRing.class.getSimpleName() + " or " + - PGPPublicKeyRing.class.getSimpleName() + " expected"); - } - } - - pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(secretKeyRings); - pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(publicKeyRings); - } - - public PGPKeyRingCollection(@Nonnull Collection collection, boolean isSilent) - throws PGPException { - List secretKeyRings = new ArrayList<>(); - List publicKeyRings = new ArrayList<>(); - - for (PGPKeyRing pgpKeyRing : collection) { - if (pgpKeyRing instanceof PGPSecretKeyRing) { - secretKeyRings.add((PGPSecretKeyRing) pgpKeyRing); - } else if (pgpKeyRing instanceof PGPPublicKeyRing) { - publicKeyRings.add((PGPPublicKeyRing) pgpKeyRing); - } else if (!isSilent) { - throw new PGPException(pgpKeyRing.getClass().getName() + " found where " + - PGPSecretKeyRing.class.getSimpleName() + " or " + - PGPPublicKeyRing.class.getSimpleName() + " expected"); - } - } - - pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(secretKeyRings); - pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(publicKeyRings); - } - - public @Nonnull PGPSecretKeyRingCollection getPGPSecretKeyRingCollection() { - return pgpSecretKeyRingCollection; - } - - public @Nonnull PGPPublicKeyRingCollection getPgpPublicKeyRingCollection() { - return pgpPublicKeyRingCollection; - } - - /** - * Return the number of rings in this collection. - * - * @return total size of {@link PGPSecretKeyRingCollection} and {@link PGPPublicKeyRingCollection} - * in this collection - */ - public int size() { - return pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/collection/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/collection/package-info.java deleted file mode 100644 index b2f5b153..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/collection/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * OpenPGP key collections. - */ -package org.pgpainless.key.collection; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/collection/PGPKeyRingCollection.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/collection/PGPKeyRingCollection.kt new file mode 100644 index 00000000..63151a4c --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/collection/PGPKeyRingCollection.kt @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub , 2021 Flowcrypt a.s. +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.collection + +import org.bouncycastle.openpgp.* +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.util.ArmorUtils +import java.io.InputStream + +/** + * This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was inspired by + * [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection]. + */ +class PGPKeyRingCollection( + val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection, + val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection +) { + + constructor(encoding: ByteArray, isSilent: Boolean): this(encoding.inputStream(), isSilent) + + constructor(inputStream: InputStream, isSilent: Boolean): this(parse(inputStream, isSilent)) + + constructor(collection: Collection, isSilent: Boolean): this(segment(collection, isSilent)) + + private constructor(arguments: Pair): this(arguments.first, arguments.second) + + /** + * The number of rings in this collection. + * + * @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this collection + */ + val size: Int + get() = pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size() + + fun size() = size + + @Deprecated("Wrong case of PGP -> Pgp", ReplaceWith("getPgpSecretKeyRingCollection()")) + fun getPGPSecretKeyRingCollection() = pgpSecretKeyRingCollection + + companion object { + @JvmStatic + private fun parse(inputStream: InputStream, isSilent: Boolean): Pair { + val secretKeyRings = mutableListOf() + val certificates = mutableListOf() + // Double getDecoderStream because of #96 + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream)) + + for (obj in objectFactory) { + if (obj == null) { + break + } + + if (obj is PGPMarker) { + // Skip marker packets + continue + } + + if (obj is PGPSecretKeyRing) { + secretKeyRings.add(obj) + continue + } + + if (obj is PGPPublicKeyRing) { + certificates.add(obj) + continue + } + + if (!isSilent) { + throw PGPException("${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" + + " or ${PGPPublicKeyRing::class.java.simpleName} expected") + } + } + + return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates) + } + + @JvmStatic + private fun segment(collection: Collection, isSilent: Boolean): Pair { + val secretKeyRings = mutableListOf() + val certificates = mutableListOf() + + for (keyRing in collection) { + if (keyRing is PGPSecretKeyRing) { + secretKeyRings.add(keyRing) + } else if (keyRing is PGPPublicKeyRing) { + certificates.add(keyRing) + } else if (!isSilent) { + throw PGPException("${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" + + " or ${PGPPublicKeyRing::class.java.simpleName} expected") + } + } + + return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates) + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/collection/PGPKeyRingCollectionTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/collection/PGPKeyRingCollectionTest.java index 89b0fccf..fd5530ba 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/collection/PGPKeyRingCollectionTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/collection/PGPKeyRingCollectionTest.java @@ -52,7 +52,7 @@ public class PGPKeyRingCollectionTest { // silent = true -> No exception, but not keys either PGPKeyRingCollection collection = new PGPKeyRingCollection(bytes, true); assertEquals(0, collection.getPgpPublicKeyRingCollection().size()); - assertEquals(0, collection.getPGPSecretKeyRingCollection().size()); + assertEquals(0, collection.getPgpSecretKeyRingCollection().size()); } @Test @@ -63,7 +63,7 @@ public class PGPKeyRingCollectionTest { Collection keys = Arrays.asList(first, second, secondPub); PGPKeyRingCollection collection = new PGPKeyRingCollection(keys, true); - assertEquals(2, collection.getPGPSecretKeyRingCollection().size()); + assertEquals(2, collection.getPgpSecretKeyRingCollection().size()); assertEquals(1, collection.getPgpPublicKeyRingCollection().size()); } } From 44c22f9044231d5042b75df6aefb2da1d3d02af9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Sep 2023 14:30:50 +0200 Subject: [PATCH 073/155] Kotlin conversion: KeyIdUtil This PR also introduces LongExtensions.kt which provides extension methods to parse Long from Hex KeyIDs and to format Longs as Hex KeyIDs. --- .../org/pgpainless/key/util/KeyIdUtil.java | 37 ------------------- .../src/main/kotlin/_kotlin/LongExtensions.kt | 25 +++++++++++++ .../org/pgpainless/key/util/KeyIdUtil.kt | 36 ++++++++++++++++++ 3 files changed, 61 insertions(+), 37 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/util/KeyIdUtil.java create mode 100644 pgpainless-core/src/main/kotlin/_kotlin/LongExtensions.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyIdUtil.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyIdUtil.java deleted file mode 100644 index 78e03f71..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyIdUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.util; - -import java.math.BigInteger; -import java.util.regex.Pattern; - -public final class KeyIdUtil { - - private KeyIdUtil() { - - } - - private static final Pattern LONG_KEY_ID = Pattern.compile("^[0-9A-Fa-f]{16}$"); - - /** - * Convert a long key-id into a key-id. - * A long key-id is a 16 digit hex string. - * - * @param longKeyId 16-digit hexadecimal string - * @return key-id converted to {@link Long}. - */ - public static long fromLongKeyId(String longKeyId) { - if (!LONG_KEY_ID.matcher(longKeyId).matches()) { - throw new IllegalArgumentException("Provided long key-id does not match expected format. " + - "A long key-id consists of 16 hexadecimal characters."); - } - - return new BigInteger(longKeyId, 16).longValue(); - } - - public static String formatKeyId(long keyId) { - return String.format("%016X", keyId); - } -} diff --git a/pgpainless-core/src/main/kotlin/_kotlin/LongExtensions.kt b/pgpainless-core/src/main/kotlin/_kotlin/LongExtensions.kt new file mode 100644 index 00000000..b2c64e2e --- /dev/null +++ b/pgpainless-core/src/main/kotlin/_kotlin/LongExtensions.kt @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package _kotlin + +/** + * Format this Long as a 16 digit uppercase hex number. + */ +fun Long.hexKeyId(): String { + return String.format("%016X", this).uppercase() +} + +/** + * Parse a 16 digit hex number into a Long. + */ +fun Long.Companion.fromHexKeyId(hexKeyId: String): Long { + require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) { + "Provided long key-id does not match expected format. " + + "A long key-id consists of 16 hexadecimal characters." + } + // Calling toLong() only fails with a NumberFormatException. + // Therefore, we call toULong(16).toLong(), which seems to work. + return hexKeyId.toULong(16).toLong() +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt new file mode 100644 index 00000000..b6ff1789 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.util + +import _kotlin.fromHexKeyId +import _kotlin.hexKeyId + +class KeyIdUtil { + + companion object { + + /** + * Convert a long key-id into a key-id. + * A long key-id is a 16 digit hex string. + * + * @param longKeyId 16-digit hexadecimal string + * @return key-id converted to {@link Long}. + */ + @JvmStatic + @Deprecated("Superseded by Long extension method.", + ReplaceWith("Long.fromHexKeyId(longKeyId)")) + fun fromLongKeyId(longKeyId: String) = Long.fromHexKeyId(longKeyId) + + /** + * Format a long key-ID as upper-case hex string. + * @param keyId keyId + * @return hex encoded key ID + */ + @JvmStatic + @Deprecated("Superseded by Long extension method.", + ReplaceWith("keyId.hexKeyId()")) + fun formatKeyId(keyId: Long) = keyId.hexKeyId() + } +} \ No newline at end of file From ab42a7503f247522f0c99d1deff128827bcb8cfd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Sep 2023 14:37:18 +0200 Subject: [PATCH 074/155] Replace usage of KeyIdUtil.formatKeyId() in Kotlin classes with Long.hexKeyId() --- .../OpenPgpMessageInputStream.kt | 18 +++++++++--------- .../org/pgpainless/key/SubkeyIdentifier.kt | 6 +++--- .../CachingSecretKeyRingProtector.kt | 6 +++--- .../key/protection/UnlockSecretKey.kt | 4 ++-- .../subpackets/SignatureSubpacketsUtil.kt | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 1f34ec23..3c056cfa 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -4,6 +4,7 @@ package org.pgpainless.decryption_verification +import _kotlin.hexKeyId import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.UnsupportedPacketVersionException import org.bouncycastle.openpgp.* @@ -21,7 +22,6 @@ import org.pgpainless.exception.* import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.protection.UnlockSecretKey -import org.pgpainless.key.util.KeyIdUtil import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.policy.Policy import org.pgpainless.signature.SignatureUtils @@ -180,7 +180,7 @@ class OpenPgpMessageInputStream( private fun processOnePassSignature() { syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE) val ops = packetInputStream!!.readOnePassSignature() - LOGGER.debug("One-Pass-Signature Packet by key ${KeyIdUtil.formatKeyId(ops.keyID)} at depth ${layerMetadata.depth} encountered.") + LOGGER.debug("One-Pass-Signature Packet by key ${ops.keyID.hexKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.addOnePassSignature(ops) } @@ -197,11 +197,11 @@ class OpenPgpMessageInputStream( val keyId = SignatureUtils.determineIssuerKeyId(signature) if (isSigForOps) { - LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${layerMetadata.depth} encountered.") + LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${keyId.hexKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy) } else { - LOGGER.debug("Prepended Signature Packet by key ${KeyIdUtil.formatKeyId(keyId)} at depth ${layerMetadata.depth} encountered.") + LOGGER.debug("Prepended Signature Packet by key ${keyId.hexKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.addPrependedSignature(signature) } } @@ -282,10 +282,10 @@ class OpenPgpMessageInputStream( // try (known) secret keys for (pkesk in esks.pkesks) { val keyId = pkesk.keyID - LOGGER.debug("Encountered PKESK for recipient ${KeyIdUtil.formatKeyId(keyId)}") + LOGGER.debug("Encountered PKESK for recipient ${keyId.hexKeyId()}") val decryptionKeys = getDecryptionKey(keyId) if (decryptionKeys == null) { - LOGGER.debug("Skipping PKESK because no matching key ${KeyIdUtil.formatKeyId(keyId)} was provided.") + LOGGER.debug("Skipping PKESK because no matching key ${keyId.hexKeyId()} was provided.") continue } val secretKey = decryptionKeys.getSecretKey(keyId) @@ -618,7 +618,7 @@ class OpenPgpMessageInputStream( if (check != null) { detachedSignatures.add(check) } else { - LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.hexKeyId()} found.") detachedSignaturesWithMissingCert.add(SignatureVerification.Failure( SignatureVerification(signature, null), SignatureValidationException("Missing verification key."))) @@ -631,7 +631,7 @@ class OpenPgpMessageInputStream( if (check != null) { prependedSignatures.add(check) } else { - LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.hexKeyId()} found.") prependedSignaturesWithMissingCert.add(SignatureVerification.Failure( SignatureVerification(signature, null), SignatureValidationException("Missing verification key") @@ -693,7 +693,7 @@ class OpenPgpMessageInputStream( } if (!found) { - LOGGER.debug("No suitable certificate for verification of signature by key ${KeyIdUtil.formatKeyId(keyId)} found.") + LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.hexKeyId()} found.") inbandSignaturesWithMissingCert.add(SignatureVerification.Failure( SignatureVerification(signature, null), SignatureValidationException("Missing verification key.") diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt index 61b45874..c09c1ee5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt @@ -4,9 +4,9 @@ package org.pgpainless.key +import _kotlin.hexKeyId import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKey -import org.pgpainless.key.util.KeyIdUtil /** * Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring, @@ -22,7 +22,7 @@ class SubkeyIdentifier( constructor(keys: PGPKeyRing, keyId: Long): this( OpenPgpFingerprint.of(keys.publicKey), OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?: - throw NoSuchElementException("OpenPGP key does not contain subkey ${KeyIdUtil.formatKeyId(keyId)}"))) + throw NoSuchElementException("OpenPGP key does not contain subkey ${keyId.hexKeyId()}"))) constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint) val keyId = subkeyFingerprint.keyId @@ -42,7 +42,7 @@ class SubkeyIdentifier( return false } - return primaryKeyFingerprint.equals(other.primaryKeyFingerprint) && subkeyFingerprint.equals(other.subkeyFingerprint) + return primaryKeyFingerprint == other.primaryKeyFingerprint && subkeyFingerprint == other.subkeyFingerprint } override fun hashCode(): Int { diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt index 4451aa0f..7a2bcc26 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt @@ -4,11 +4,11 @@ package org.pgpainless.key.protection +import _kotlin.hexKeyId import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKey import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider -import org.pgpainless.key.util.KeyIdUtil import org.pgpainless.util.Passphrase /** @@ -54,7 +54,7 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras */ fun addPassphrase(keyId: Long, passphrase: Passphrase) = apply { require(!cache.containsKey(keyId)) { - "The cache already holds a passphrase for ID ${KeyIdUtil.formatKeyId(keyId)}.\n" + + "The cache already holds a passphrase for ID ${keyId.hexKeyId()}.\n" + "If you want to replace this passphrase, use replacePassphrase(Long, Passphrase) instead." } cache[keyId] = passphrase @@ -90,7 +90,7 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras // check for existing passphrases before doing anything keyRing.publicKeys.forEach { require(!cache.containsKey(it.keyID)) { - "The cache already holds a passphrase for the key with ID ${KeyIdUtil.formatKeyId(it.keyID)}.\n" + + "The cache already holds a passphrase for the key with ID ${it.keyID.hexKeyId()}.\n" + "If you want to replace the passphrase, use replacePassphrase(PGPKeyRing, Passphrase) instead." } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt index 533b33a7..7385b265 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt @@ -5,6 +5,7 @@ package org.pgpainless.key.protection +import _kotlin.hexKeyId import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPSecretKey @@ -13,7 +14,6 @@ import org.pgpainless.PGPainless import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.exception.WrongPassphraseException import org.pgpainless.key.info.KeyInfo -import org.pgpainless.key.util.KeyIdUtil import org.pgpainless.key.util.PublicKeyParameterValidationUtil import org.pgpainless.util.Passphrase import kotlin.jvm.Throws @@ -43,7 +43,7 @@ class UnlockSecretKey { if (privateKey == null) { if (secretKey.s2K.type in 100..110) { - throw PGPException("Cannot decrypt secret key ${KeyIdUtil.formatKeyId(secretKey.keyID)}: \n" + + throw PGPException("Cannot decrypt secret key ${secretKey.keyID.hexKeyId()}: \n" + "Unsupported private S2K type ${secretKey.s2K.type}") } throw PGPException("Cannot decrypt secret key.") diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt index 23fe716c..1b9b432a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt @@ -4,6 +4,7 @@ package org.pgpainless.signature.subpackets +import _kotlin.hexKeyId import org.bouncycastle.bcpg.sig.* import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature @@ -17,7 +18,6 @@ import org.pgpainless.key.OpenPgpV4Fingerprint import org.pgpainless.key.OpenPgpV5Fingerprint import org.pgpainless.key.OpenPgpV6Fingerprint import org.pgpainless.key.generation.type.KeyType -import org.pgpainless.key.util.KeyIdUtil import org.pgpainless.signature.SignatureUtils import java.util.* @@ -143,7 +143,7 @@ class SignatureSubpacketsUtil { @JvmStatic fun getKeyExpirationTimeAsDate(signature: PGPSignature, signingKey: PGPPublicKey): Date? = require(signature.keyID == signingKey.keyID) { - "Provided key (${KeyIdUtil.formatKeyId(signingKey.keyID)}) did not create the signature (${KeyIdUtil.formatKeyId(signature.keyID)})" + "Provided key (${signingKey.keyID.hexKeyId()}) did not create the signature (${signature.keyID.hexKeyId()})" }.run { getKeyExpirationTime(signature)?.let { SignatureUtils.datePlusSeconds(signingKey.creationTime, it.time) From dc064d17278f82b2bb94dd3b28bacb3bbeb6da5a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Sep 2023 16:32:01 +0200 Subject: [PATCH 075/155] Kotlin conversion: KeyRingUtils --- .../org/pgpainless/key/util/KeyRingUtils.java | 589 ------------------ .../extensions/PGPSecretKeyRingExtensions.kt | 11 + .../org/pgpainless/key/util/KeyRingUtils.kt | 484 ++++++++++++++ 3 files changed, 495 insertions(+), 589 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java create mode 100644 pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java deleted file mode 100644 index 91de3be4..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java +++ /dev/null @@ -1,589 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.S2K; -import org.bouncycastle.bcpg.SecretKeyPacket; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.util.Strings; -import org.pgpainless.PGPainless; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnlockSecretKey; -import org.pgpainless.key.protection.fixes.S2KUsageFix; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class KeyRingUtils { - - private KeyRingUtils() { - - } - - private static final Logger LOGGER = LoggerFactory.getLogger(KeyRingUtils.class); - - /** - * Return the primary {@link PGPSecretKey} from the provided {@link PGPSecretKeyRing}. - * If it has no primary secret key, throw a {@link NoSuchElementException}. - * - * @param secretKeys secret keys - * @return primary secret key - */ - @Nonnull - public static PGPSecretKey requirePrimarySecretKeyFrom(@Nonnull PGPSecretKeyRing secretKeys) { - PGPSecretKey primarySecretKey = getPrimarySecretKeyFrom(secretKeys); - if (primarySecretKey == null) { - throw new NoSuchElementException("Provided PGPSecretKeyRing has no primary secret key."); - } - return primarySecretKey; - } - - /** - * Return the primary {@link PGPSecretKey} from the provided {@link PGPSecretKeyRing} or null if it has none. - * - * @param secretKeys secret key ring - * @return primary secret key - */ - @Nullable - public static PGPSecretKey getPrimarySecretKeyFrom(@Nonnull PGPSecretKeyRing secretKeys) { - PGPSecretKey secretKey = secretKeys.getSecretKey(); - if (secretKey.isMasterKey()) { - return secretKey; - } - return null; - } - - /** - * Return the primary {@link PGPPublicKey} from the provided key ring. - * Throws a {@link NoSuchElementException} if the key ring has no primary public key. - * - * @param keyRing key ring - * @return primary public key - */ - @Nonnull - public static PGPPublicKey requirePrimaryPublicKeyFrom(@Nonnull PGPKeyRing keyRing) { - PGPPublicKey primaryPublicKey = getPrimaryPublicKeyFrom(keyRing); - if (primaryPublicKey == null) { - throw new NoSuchElementException("Provided PGPKeyRing has no primary public key."); - } - return primaryPublicKey; - } - - /** - * Return the primary {@link PGPPublicKey} from the provided key ring or null if it has none. - * - * @param keyRing key ring - * @return primary public key - */ - @Nullable - public static PGPPublicKey getPrimaryPublicKeyFrom(@Nonnull PGPKeyRing keyRing) { - PGPPublicKey primaryPublicKey = keyRing.getPublicKey(); - if (primaryPublicKey.isMasterKey()) { - return primaryPublicKey; - } - return null; - } - - /** - * Return the public key with the given subKeyId from the keyRing. - * If no such subkey exists, return null. - * @param keyRing key ring - * @param subKeyId subkey id - * @return subkey or null - */ - @Nullable - public static PGPPublicKey getPublicKeyFrom(@Nonnull PGPKeyRing keyRing, long subKeyId) { - return keyRing.getPublicKey(subKeyId); - } - - /** - * Require the public key with the given subKeyId from the keyRing. - * If no such subkey exists, throw an {@link NoSuchElementException}. - * - * @param keyRing key ring - * @param subKeyId subkey id - * @return subkey - */ - @Nonnull - public static PGPPublicKey requirePublicKeyFrom(@Nonnull PGPKeyRing keyRing, long subKeyId) { - PGPPublicKey publicKey = getPublicKeyFrom(keyRing, subKeyId); - if (publicKey == null) { - throw new NoSuchElementException("KeyRing does not contain public key with keyID " + Long.toHexString(subKeyId)); - } - return publicKey; - } - - /** - * Require the secret key with the given secret subKeyId from the secret keyRing. - * If no such subkey exists, throw an {@link NoSuchElementException}. - * - * @param keyRing secret key ring - * @param subKeyId subkey id - * @return secret subkey - */ - @Nonnull - public static PGPSecretKey requireSecretKeyFrom(@Nonnull PGPSecretKeyRing keyRing, long subKeyId) { - PGPSecretKey secretKey = keyRing.getSecretKey(subKeyId); - if (secretKey == null) { - throw new NoSuchElementException("KeyRing does not contain secret key with keyID " + Long.toHexString(subKeyId)); - } - return secretKey; - } - - @Nonnull - public static PGPPublicKeyRing publicKeys(@Nonnull PGPKeyRing keys) { - if (keys instanceof PGPPublicKeyRing) { - return (PGPPublicKeyRing) keys; - } else if (keys instanceof PGPSecretKeyRing) { - return publicKeyRingFrom((PGPSecretKeyRing) keys); - } else { - throw new IllegalArgumentException("Unknown keys class: " + keys.getClass().getName()); - } - } - - /** - * Extract a {@link PGPPublicKeyRing} containing all public keys from the provided {@link PGPSecretKeyRing}. - * - * @param secretKeys secret key ring - * @return public key ring - */ - @Nonnull - public static PGPPublicKeyRing publicKeyRingFrom(@Nonnull PGPSecretKeyRing secretKeys) { - List publicKeyList = new ArrayList<>(); - Iterator publicKeyIterator = secretKeys.getPublicKeys(); - while (publicKeyIterator.hasNext()) { - publicKeyList.add(publicKeyIterator.next()); - } - PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyList); - return publicKeyRing; - } - - /** - * Extract {@link PGPPublicKeyRing PGPPublicKeyRings} from all {@link PGPSecretKeyRing PGPSecretKeyRings} in - * the given {@link PGPSecretKeyRingCollection} and return them as a {@link PGPPublicKeyRingCollection}. - * - * @param secretKeyRings secret key ring collection - * @return public key ring collection - */ - @Nonnull - public static PGPPublicKeyRingCollection publicKeyRingCollectionFrom(@Nonnull PGPSecretKeyRingCollection secretKeyRings) { - List certificates = new ArrayList<>(); - for (PGPSecretKeyRing secretKey : secretKeyRings) { - certificates.add(PGPainless.extractCertificate(secretKey)); - } - return new PGPPublicKeyRingCollection(certificates); - } - - /** - * Unlock a {@link PGPSecretKey} and return the resulting {@link PGPPrivateKey}. - * - * @param secretKey secret key - * @param protector protector to unlock the secret key - * @return private key - * - * @throws PGPException if something goes wrong (e.g. wrong passphrase) - */ - @Nonnull - public static PGPPrivateKey unlockSecretKey(@Nonnull PGPSecretKey secretKey, @Nonnull SecretKeyRingProtector protector) - throws PGPException { - return UnlockSecretKey.unlockSecretKey(secretKey, protector); - } - - /** - * Create a new {@link PGPPublicKeyRingCollection} from an array of {@link PGPPublicKeyRing PGPPublicKeyRings}. - * - * @param rings array of public key rings - * @return key ring collection - */ - @Nonnull - public static PGPPublicKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPPublicKeyRing... rings) { - return new PGPPublicKeyRingCollection(Arrays.asList(rings)); - } - - /** - * Create a new {@link PGPSecretKeyRingCollection} from an array of {@link PGPSecretKeyRing PGPSecretKeyRings}. - * - * @param rings array of secret key rings - * @return secret key ring collection - */ - @Nonnull - public static PGPSecretKeyRingCollection keyRingsToKeyRingCollection(@Nonnull PGPSecretKeyRing... rings) { - return new PGPSecretKeyRingCollection(Arrays.asList(rings)); - } - - /** - * Return true, if the given {@link PGPPublicKeyRing} contains a {@link PGPPublicKey} for the given key id. - * - * @param ring public key ring - * @param keyId id of the key in question - * @return true if ring contains said key, false otherwise - */ - public static boolean keyRingContainsKeyWithId(@Nonnull PGPPublicKeyRing ring, - long keyId) { - return ring.getPublicKey(keyId) != null; - } - - /** - * Inject a key certification for the primary key into the given key ring. - * - * @param keyRing key ring - * @param certification key signature - * @return key ring with injected signature - * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} - */ - @Nonnull - public static T injectCertification(@Nonnull T keyRing, - @Nonnull PGPSignature certification) { - return injectCertification(keyRing, keyRing.getPublicKey(), certification); - } - - /** - * Inject a key certification for the given key into the given key ring. - * - * @param keyRing key ring - * @param certifiedKey signed public key - * @param certification key signature - * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} - * @return key ring with injected signature - * - * @throws NoSuchElementException in case that the signed key is not part of the key ring - */ - @Nonnull - public static T injectCertification(@Nonnull T keyRing, - @Nonnull PGPPublicKey certifiedKey, - @Nonnull PGPSignature certification) { - PGPSecretKeyRing secretKeys = null; - PGPPublicKeyRing publicKeys; - if (keyRing instanceof PGPSecretKeyRing) { - secretKeys = (PGPSecretKeyRing) keyRing; - publicKeys = PGPainless.extractCertificate(secretKeys); - } else { - publicKeys = (PGPPublicKeyRing) keyRing; - } - - certifiedKey = PGPPublicKey.addCertification(certifiedKey, certification); - List publicKeyList = new ArrayList<>(); - Iterator publicKeyIterator = publicKeys.iterator(); - boolean added = false; - while (publicKeyIterator.hasNext()) { - PGPPublicKey key = publicKeyIterator.next(); - if (key.getKeyID() == certifiedKey.getKeyID()) { - added = true; - publicKeyList.add(certifiedKey); - } else { - publicKeyList.add(key); - } - } - if (!added) { - throw new NoSuchElementException("Cannot find public key with id " + Long.toHexString(certifiedKey.getKeyID()) + " in the provided key ring."); - } - - publicKeys = new PGPPublicKeyRing(publicKeyList); - if (secretKeys == null) { - return (T) publicKeys; - } else { - secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys); - return (T) secretKeys; - } - } - - /** - * Inject a user-id certification into the given key ring. - * - * @param keyRing key ring - * @param userId signed user-id - * @param certification signature - * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} - * @return key ring with injected certification - */ - @Nonnull - public static T injectCertification(@Nonnull T keyRing, - @Nonnull String userId, - @Nonnull PGPSignature certification) { - PGPSecretKeyRing secretKeys = null; - PGPPublicKeyRing publicKeys; - if (keyRing instanceof PGPSecretKeyRing) { - secretKeys = (PGPSecretKeyRing) keyRing; - publicKeys = PGPainless.extractCertificate(secretKeys); - } else { - publicKeys = (PGPPublicKeyRing) keyRing; - } - - Iterator publicKeyIterator = publicKeys.iterator(); - PGPPublicKey primaryKey = publicKeyIterator.next(); - primaryKey = PGPPublicKey.addCertification(primaryKey, userId, certification); - - List publicKeyList = new ArrayList<>(); - publicKeyList.add(primaryKey); - while (publicKeyIterator.hasNext()) { - publicKeyList.add(publicKeyIterator.next()); - } - - publicKeys = new PGPPublicKeyRing(publicKeyList); - if (secretKeys == null) { - return (T) publicKeys; - } else { - secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys); - return (T) secretKeys; - } - } - - /** - * Inject a user-attribute vector certification into the given key ring. - * - * @param keyRing key ring - * @param userAttributes certified user attributes - * @param certification certification signature - * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} - * @return key ring with injected user-attribute certification - */ - @Nonnull - public static T injectCertification(@Nonnull T keyRing, - @Nonnull PGPUserAttributeSubpacketVector userAttributes, - @Nonnull PGPSignature certification) { - PGPSecretKeyRing secretKeys = null; - PGPPublicKeyRing publicKeys; - if (keyRing instanceof PGPSecretKeyRing) { - secretKeys = (PGPSecretKeyRing) keyRing; - publicKeys = PGPainless.extractCertificate(secretKeys); - } else { - publicKeys = (PGPPublicKeyRing) keyRing; - } - - Iterator publicKeyIterator = publicKeys.iterator(); - PGPPublicKey primaryKey = publicKeyIterator.next(); - primaryKey = PGPPublicKey.addCertification(primaryKey, userAttributes, certification); - - List publicKeyList = new ArrayList<>(); - publicKeyList.add(primaryKey); - while (publicKeyIterator.hasNext()) { - publicKeyList.add(publicKeyIterator.next()); - } - - publicKeys = new PGPPublicKeyRing(publicKeyList); - if (secretKeys == null) { - return (T) publicKeys; - } else { - secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys); - return (T) secretKeys; - } - } - - /** - * Inject a {@link PGPPublicKey} into the given key ring. - * - * @param keyRing key ring - * @param publicKey public key - * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} - * @return key ring with injected public key - */ - @Nonnull - public static T keysPlusPublicKey(@Nonnull T keyRing, - @Nonnull PGPPublicKey publicKey) { - PGPSecretKeyRing secretKeys = null; - PGPPublicKeyRing publicKeys; - if (keyRing instanceof PGPSecretKeyRing) { - secretKeys = (PGPSecretKeyRing) keyRing; - publicKeys = PGPainless.extractCertificate(secretKeys); - } else { - publicKeys = (PGPPublicKeyRing) keyRing; - } - - publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, publicKey); - if (secretKeys == null) { - return (T) publicKeys; - } else { - secretKeys = PGPSecretKeyRing.insertOrReplacePublicKey(secretKeys, publicKey); - return (T) secretKeys; - } - } - - /** - * Inject a {@link PGPSecretKey} into a {@link PGPSecretKeyRing}. - * - * @param secretKeys secret key ring - * @param secretKey secret key - * @return secret key ring with injected secret key - */ - @Nonnull - public static PGPSecretKeyRing keysPlusSecretKey(@Nonnull PGPSecretKeyRing secretKeys, - @Nonnull PGPSecretKey secretKey) { - return PGPSecretKeyRing.insertSecretKey(secretKeys, secretKey); - } - - /** - * Inject the given signature into the public part of the given secret key. - * @param secretKey secret key - * @param signature signature - * @return secret key with the signature injected in its public key - */ - @Nonnull - public static PGPSecretKey secretKeyPlusSignature(@Nonnull PGPSecretKey secretKey, - @Nonnull PGPSignature signature) { - PGPPublicKey publicKey = secretKey.getPublicKey(); - publicKey = PGPPublicKey.addCertification(publicKey, signature); - PGPSecretKey newSecretKey = PGPSecretKey.replacePublicKey(secretKey, publicKey); - return newSecretKey; - } - - /** - * Remove the secret key of the subkey identified by the given secret key id from the key ring. - * The public part stays attached to the key ring, so that it can still be used for encryption / verification of signatures. - * - * This method is intended to be used to remove secret primary keys from live keys when those are kept in offline storage. - * - * @param secretKeys secret key ring - * @param secretKeyId id of the secret key to remove - * @return secret key ring with removed secret key - * - * @throws IOException in case of an error during serialization / deserialization of the key - * @throws PGPException in case of a broken key - */ - @Nonnull - public static PGPSecretKeyRing stripSecretKey(@Nonnull PGPSecretKeyRing secretKeys, - long secretKeyId) - throws IOException, PGPException { - - if (secretKeys.getPublicKey().getKeyID() == secretKeyId) { - throw new IllegalArgumentException("Bouncy Castle currently cannot deal with stripped secret primary keys."); - } - - if (secretKeys.getSecretKey(secretKeyId) == null) { - throw new NoSuchElementException("PGPSecretKeyRing does not contain secret key " + Long.toHexString(secretKeyId)); - } - - // Since BCs constructors for secret key rings are mostly private, we need to encode the key ring how we want it - // and then parse it again. - ByteArrayOutputStream encoded = new ByteArrayOutputStream(); - for (PGPSecretKey secretKey : secretKeys) { - if (secretKey.getKeyID() == secretKeyId) { - // only encode the public part of the target key - secretKey.getPublicKey().encode(encoded); - } else { - // otherwise, encode secret + public key - secretKey.encode(encoded); - } - } - for (Iterator it = secretKeys.getExtraPublicKeys(); it.hasNext(); ) { - PGPPublicKey extra = it.next(); - extra.encode(encoded); - } - // Parse the key back into an object - return new PGPSecretKeyRing(encoded.toByteArray(), ImplementationFactory.getInstance().getKeyFingerprintCalculator()); - } - - /** - * Strip all user-ids, user-attributes and signatures from the given public key. - * - * @param bloatedKey public key - * @return stripped public key - * @throws PGPException if the packet is faulty or the required calculations fail - */ - public static PGPPublicKey getStrippedDownPublicKey(PGPPublicKey bloatedKey) throws PGPException { - return new PGPPublicKey(bloatedKey.getPublicKeyPacket(), ImplementationFactory.getInstance().getKeyFingerprintCalculator()); - } - - public static List getUserIdsIgnoringInvalidUTF8(PGPPublicKey key) { - List userIds = new ArrayList<>(); - Iterator it = key.getRawUserIDs(); - while (it.hasNext()) { - byte[] rawUserId = it.next(); - try { - userIds.add(Strings.fromUTF8ByteArray(rawUserId)); - } catch (IllegalArgumentException e) { - LOGGER.warn("Invalid UTF-8 user-ID encountered: " + new String(rawUserId)); - } - } - return userIds; - } - - public static PGPSecretKeyRing changePassphrase(Long keyId, - PGPSecretKeyRing secretKeys, - SecretKeyRingProtector oldProtector, - SecretKeyRingProtector newProtector) - throws PGPException { - List secretKeyList = new ArrayList<>(); - if (keyId == null) { - // change passphrase of whole key ring - Iterator secretKeyIterator = secretKeys.getSecretKeys(); - while (secretKeyIterator.hasNext()) { - PGPSecretKey secretKey = secretKeyIterator.next(); - secretKey = KeyRingUtils.reencryptPrivateKey(secretKey, oldProtector, newProtector); - secretKeyList.add(secretKey); - } - } else { - // change passphrase of selected subkey only - Iterator secretKeyIterator = secretKeys.getSecretKeys(); - while (secretKeyIterator.hasNext()) { - PGPSecretKey secretKey = secretKeyIterator.next(); - if (secretKey.getPublicKey().getKeyID() == keyId) { - // Re-encrypt only the selected subkey - secretKey = KeyRingUtils.reencryptPrivateKey(secretKey, oldProtector, newProtector); - } - secretKeyList.add(secretKey); - } - } - - PGPSecretKeyRing newRing = new PGPSecretKeyRing(secretKeyList); - newRing = s2kUsageFixIfNecessary(newRing, newProtector); - return newRing; - } - - - public static PGPSecretKey reencryptPrivateKey( - PGPSecretKey secretKey, - SecretKeyRingProtector oldProtector, - SecretKeyRingProtector newProtector) - throws PGPException { - S2K s2k = secretKey.getS2K(); - // If the key uses GNU_DUMMY_S2K, we leave it as is and skip this block - if (s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) { - long secretKeyId = secretKey.getKeyID(); - PBESecretKeyDecryptor decryptor = oldProtector.getDecryptor(secretKeyId); - PBESecretKeyEncryptor encryptor = newProtector.getEncryptor(secretKeyId); - secretKey = PGPSecretKey.copyWithNewPassword(secretKey, decryptor, encryptor); - } - return secretKey; - } - - - public static PGPSecretKeyRing s2kUsageFixIfNecessary(PGPSecretKeyRing secretKeys, SecretKeyRingProtector protector) - throws PGPException { - boolean hasS2KUsageChecksum = false; - for (PGPSecretKey secKey : secretKeys) { - if (secKey.getS2KUsage() == SecretKeyPacket.USAGE_CHECKSUM) { - hasS2KUsageChecksum = true; - break; - } - } - if (hasS2KUsageChecksum) { - secretKeys = S2KUsageFix.replaceUsageChecksumWithUsageSha1( - secretKeys, protector, true); - } - return secretKeys; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt new file mode 100644 index 00000000..3a9c2918 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle.extensions + +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPSecretKeyRing + +val PGPSecretKeyRing.certificate: PGPPublicKeyRing + get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList()) \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt new file mode 100644 index 00000000..bd0edf28 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt @@ -0,0 +1,484 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.util + +import _kotlin.hexKeyId +import org.bouncycastle.bcpg.S2K +import org.bouncycastle.bcpg.SecretKeyPacket +import org.bouncycastle.extensions.certificate +import org.bouncycastle.openpgp.* +import org.bouncycastle.util.Strings +import org.pgpainless.exception.MissingPassphraseException +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.protection.UnlockSecretKey +import org.pgpainless.key.protection.fixes.S2KUsageFix +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.ByteArrayOutputStream +import kotlin.jvm.Throws + +class KeyRingUtils { + + companion object { + + @JvmStatic + private val LOGGER: Logger = LoggerFactory.getLogger(KeyRingUtils::class.java) + + /** + * Return the primary {@link PGPSecretKey} from the provided {@link PGPSecretKeyRing}. + * If it has no primary secret key, throw a {@link NoSuchElementException}. + * + * @param secretKeys secret keys + * @return primary secret key + */ + @JvmStatic + fun requirePrimarySecretKeyFrom(secretKeys: PGPSecretKeyRing): PGPSecretKey { + return getPrimarySecretKeyFrom(secretKeys) + ?: throw NoSuchElementException("Provided PGPSecretKeyRing has no primary secret key.") + } + + /** + * Return the primary secret key from the given secret key ring. + * If the key ring only contains subkeys (e.g. if the primary secret key is stripped), + * this method may return null. + * + * @param secretKeys secret key ring + * @return primary secret key + */ + @JvmStatic + fun getPrimarySecretKeyFrom(secretKeys: PGPSecretKeyRing): PGPSecretKey? { + return secretKeys.secretKey.let { + if (it.isMasterKey) { + it + } else { + null + } + } + } + + /** + * Return the primary {@link PGPPublicKey} from the provided key ring. + * Throws a {@link NoSuchElementException} if the key ring has no primary public key. + * + * @param keyRing key ring + * @return primary public key + */ + @JvmStatic + fun requirePrimaryPublicKeyFrom(keyRing: PGPKeyRing): PGPPublicKey { + return getPrimaryPublicKey(keyRing) + ?: throw NoSuchElementException("Provided PGPKeyRing has no primary public key.") + } + + /** + * Return the primary {@link PGPPublicKey} from the provided key ring or null if it has none. + * + * @param keyRing key ring + * @return primary public key + */ + @JvmStatic + fun getPrimaryPublicKey(keyRing: PGPKeyRing): PGPPublicKey? { + return keyRing.publicKey.let { + if (it.isMasterKey) { + it + } else { + null + } + } + } + + /** + * Require the public key with the given subKeyId from the keyRing. + * If no such subkey exists, throw an {@link NoSuchElementException}. + * + * @param keyRing key ring + * @param subKeyId subkey id + * @return subkey + */ + @JvmStatic + fun requirePublicKeyFrom(keyRing: PGPKeyRing, subKeyId: Long): PGPPublicKey { + return keyRing.getPublicKey(subKeyId) + ?: throw NoSuchElementException("KeyRing does not contain public key with keyId ${subKeyId.hexKeyId()}.") + } + + /** + * Require the secret key with the given secret subKeyId from the secret keyRing. + * If no such subkey exists, throw an {@link NoSuchElementException}. + * + * @param keyRing secret key ring + * @param subKeyId subkey id + * @return secret subkey + */ + @JvmStatic + fun requireSecretKeyFrom(keyRing: PGPSecretKeyRing, subKeyId: Long): PGPSecretKey { + return keyRing.getSecretKey(subKeyId) + ?: throw NoSuchElementException("KeyRing does not contain secret key with keyID ${subKeyId.hexKeyId()}.") + } + + @JvmStatic + fun publicKeys(keys: PGPKeyRing): PGPPublicKeyRing { + return when (keys) { + is PGPPublicKeyRing -> keys + is PGPSecretKeyRing -> keys.certificate + else -> throw IllegalArgumentException("Unknown keys class: ${keys.javaClass.name}") + } + } + + /** + * Extract a {@link PGPPublicKeyRing} containing all public keys from the provided {@link PGPSecretKeyRing}. + * + * @param secretKeys secret key ring + * @return public key ring + */ + @JvmStatic + @Deprecated("Deprecated in favor of PGPSecretKeyRing extension method.", + ReplaceWith("secretKeys.certificate", "org.bouncycastle.extensions.certificate")) + fun publicKeyRingFrom(secretKeys: PGPSecretKeyRing): PGPPublicKeyRing { + return secretKeys.certificate + } + + /** + * Extract {@link PGPPublicKeyRing PGPPublicKeyRings} from all {@link PGPSecretKeyRing PGPSecretKeyRings} in + * the given {@link PGPSecretKeyRingCollection} and return them as a {@link PGPPublicKeyRingCollection}. + * + * @param secretKeyRings secret key ring collection + * @return public key ring collection + */ + @JvmStatic + fun publicKeyRingCollectionFrom(secretKeyRings: PGPSecretKeyRingCollection): PGPPublicKeyRingCollection { + return PGPPublicKeyRingCollection( + secretKeyRings.keyRings.asSequence() + .map { it.certificate } + .toList()) + } + + /** + * Unlock a {@link PGPSecretKey} and return the resulting {@link PGPPrivateKey}. + * + * @param secretKey secret key + * @param protector protector to unlock the secret key + * @return private key + * + * @throws PGPException if something goes wrong (e.g. wrong passphrase) + */ + @JvmStatic + fun unlockSecretKey(secretKey: PGPSecretKey, protector: SecretKeyRingProtector): PGPPrivateKey { + return UnlockSecretKey.unlockSecretKey(secretKey, protector) + } + + /** + * Create a new {@link PGPPublicKeyRingCollection} from an array of {@link PGPPublicKeyRing PGPPublicKeyRings}. + * + * @param certificates array of public key rings + * @return key ring collection + */ + @JvmStatic + fun keyRingsToKeyRingCollection(vararg certificates: PGPPublicKeyRing): PGPPublicKeyRingCollection { + return PGPPublicKeyRingCollection(certificates.toList()) + } + + /** + * Create a new {@link PGPSecretKeyRingCollection} from an array of {@link PGPSecretKeyRing PGPSecretKeyRings}. + * + * @param secretKeys array of secret key rings + * @return secret key ring collection + */ + @JvmStatic + fun keyRingsToKeyRingCollection(vararg secretKeys: PGPSecretKeyRing): PGPSecretKeyRingCollection { + return PGPSecretKeyRingCollection(secretKeys.toList()) + } + + /** + * Return true, if the given {@link PGPPublicKeyRing} contains a {@link PGPPublicKey} for the given key id. + * + * @param certificate public key ring + * @param keyId id of the key in question + * @return true if ring contains said key, false otherwise + */ + @JvmStatic + fun keyRingContainsKeyWithId(certificate: PGPPublicKeyRing, keyId: Long): Boolean { + return certificate.getPublicKey(keyId) != null + } + + /** + * Inject a key certification for the primary key into the given key ring. + * + * @param keyRing key ring + * @param certification key signature + * @return key ring with injected signature + * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} + */ + @JvmStatic + fun injectCertification(keyRing: T, certification: PGPSignature): T { + return injectCertification(keyRing, keyRing.publicKey, certification) + } + + /** + * Inject a key certification for the given key into the given key ring. + * + * @param keyRing key ring + * @param certifiedKey signed public key + * @param certification key signature + * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} + * @return key ring with injected signature + * + * @throws NoSuchElementException in case that the signed key is not part of the key ring + */ + @JvmStatic + fun injectCertification(keyRing: T, certifiedKey: PGPPublicKey, certification: PGPSignature): T { + val secretAndPublicKeys = secretAndPublicKeys(keyRing) + val secretKeys: PGPSecretKeyRing? = secretAndPublicKeys.first + var certificate: PGPPublicKeyRing = secretAndPublicKeys.second + + if (!keyRingContainsKeyWithId(certificate, certifiedKey.keyID)) { + throw NoSuchElementException("Cannot find public key with id ${certifiedKey.keyID.hexKeyId()} in the provided key ring.") + } + + certificate = PGPPublicKeyRing( + certificate.publicKeys.asSequence().map { + if (it.keyID == certifiedKey.keyID) { + PGPPublicKey.addCertification(it, certification) + } else { + it + } + }.toList()) + return if (secretKeys == null) { + certificate as T + } else { + PGPSecretKeyRing.replacePublicKeys(secretKeys, certificate) as T + } + } + + /** + * Inject a user-id certification into the given key ring. + * + * @param keyRing key ring + * @param userId signed user-id + * @param certification signature + * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} + * @return key ring with injected certification + */ + @JvmStatic + fun injectCertification(keyRing: T, userId: CharSequence, certification: PGPSignature): T { + val secretAndPublicKeys = secretAndPublicKeys(keyRing) + val secretKeys: PGPSecretKeyRing? = secretAndPublicKeys.first + var certificate: PGPPublicKeyRing = secretAndPublicKeys.second + + certificate = PGPPublicKeyRing( + listOf( + PGPPublicKey.addCertification(requirePrimaryPublicKeyFrom(certificate), + userId.toString(), certification) + ).plus(certificate.publicKeys.asSequence().drop(1))) + + return if (secretKeys == null) { + certificate as T + } else { + PGPSecretKeyRing.replacePublicKeys(secretKeys, certificate) as T + } + } + + /** + * Inject a user-attribute vector certification into the given key ring. + * + * @param keyRing key ring + * @param userAttributes certified user attributes + * @param certification certification signature + * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} + * @return key ring with injected user-attribute certification + */ + @JvmStatic + fun injectCertification(keyRing: T, userAttributes: PGPUserAttributeSubpacketVector, certification: PGPSignature): T { + val secretAndPublicKeys = secretAndPublicKeys(keyRing) + val secretKeys: PGPSecretKeyRing? = secretAndPublicKeys.first + var certificate: PGPPublicKeyRing = secretAndPublicKeys.second + + certificate = PGPPublicKeyRing( + listOf( + PGPPublicKey.addCertification(requirePrimaryPublicKeyFrom(certificate), + userAttributes, certification) + ).plus(certificate.publicKeys.asSequence().drop(1))) + + return if (secretKeys == null) { + certificate as T + } else { + PGPSecretKeyRing.replacePublicKeys(secretKeys, certificate) as T + } + } + + /** + * Inject a {@link PGPPublicKey} into the given key ring. + * + * @param keyRing key ring + * @param publicKey public key + * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} + * @return key ring with injected public key + */ + @JvmStatic + fun keysPlusPublicKey(keyRing: T, publicKey: PGPPublicKey): T { + val secretAndPublicKeys = secretAndPublicKeys(keyRing) + val secretKeys: PGPSecretKeyRing? = secretAndPublicKeys.first + var certificate: PGPPublicKeyRing = secretAndPublicKeys.second + + return if (secretKeys == null) { + PGPPublicKeyRing.insertPublicKey(certificate, publicKey) as T + } else { + PGPSecretKeyRing.insertOrReplacePublicKey(secretKeys, publicKey) as T + } + } + + @JvmStatic + private fun secretAndPublicKeys(keyRing: PGPKeyRing): Pair { + var secretKeys: PGPSecretKeyRing? = null + val certificate: PGPPublicKeyRing + when (keyRing) { + is PGPSecretKeyRing -> { + secretKeys = keyRing + certificate = secretKeys.certificate + } + is PGPPublicKeyRing -> { + certificate = keyRing + } + else -> throw IllegalArgumentException("keyRing is an unknown PGPKeyRing subclass: ${keyRing.javaClass.name}") + } + return secretKeys to certificate + } + + /** + * Inject a {@link PGPSecretKey} into a {@link PGPSecretKeyRing}. + * + * @param secretKeys secret key ring + * @param secretKey secret key + * @return secret key ring with injected secret key + */ + @JvmStatic + fun keysPlusSecretKey(secretKeys: PGPSecretKeyRing, secretKey: PGPSecretKey): PGPSecretKeyRing { + return PGPSecretKeyRing.insertSecretKey(secretKeys, secretKey) + } + + /** + * Inject the given signature into the public part of the given secret key. + * @param secretKey secret key + * @param signature signature + * @return secret key with the signature injected in its public key + */ + @JvmStatic + fun secretKeyPlusSignature(secretKey: PGPSecretKey, signature: PGPSignature): PGPSecretKey { + PGPPublicKey.addCertification(secretKey.publicKey, signature).let { + return PGPSecretKey.replacePublicKey(secretKey, it) + } + } + + /** + * Remove the secret key of the subkey identified by the given secret key id from the key ring. + * The public part stays attached to the key ring, so that it can still be used for encryption / verification of signatures. + * + * This method is intended to be used to remove secret primary keys from live keys when those are kept in offline storage. + * + * @param secretKeys secret key ring + * @param keyId id of the secret key to remove + * @return secret key ring with removed secret key + * + * @throws IOException in case of an error during serialization / deserialization of the key + * @throws PGPException in case of a broken key + */ + @JvmStatic + fun stripSecretKey(secretKeys: PGPSecretKeyRing, keyId: Long): PGPSecretKeyRing { + require(keyId != secretKeys.publicKey.keyID) { + "Bouncy Castle currently cannot deal with stripped primary secret keys." + } + if (secretKeys.getSecretKey(keyId) == null) { + throw NoSuchElementException("PGPSecretKeyRing does not contain secret key ${keyId.hexKeyId()}.") + } + + val out = ByteArrayOutputStream() + secretKeys.forEach { + if (it.keyID == keyId) { + // only encode the public key + it.publicKey.encode(out) + } else { + // else encode the whole secret + public key + it.encode(out) + } + } + secretKeys.extraPublicKeys.forEach { + it.encode(out) + } + return PGPSecretKeyRing(out.toByteArray(), ImplementationFactory.getInstance().keyFingerprintCalculator) + } + + /** + * Strip all user-ids, user-attributes and signatures from the given public key. + * + * @param bloatedKey public key + * @return stripped public key + * @throws PGPException if the packet is faulty or the required calculations fail + */ + @JvmStatic + fun getStrippedDownPublicKey(bloatedKey: PGPPublicKey): PGPPublicKey { + return PGPPublicKey(bloatedKey.publicKeyPacket, ImplementationFactory.getInstance().keyFingerprintCalculator) + } + + @JvmStatic + fun getUserIdsIgnoringInvalidUTF8(key: PGPPublicKey): List { + return buildList { + key.rawUserIDs.forEach { + try { + add(Strings.fromUTF8ByteArray(it)) + } catch (e : IllegalArgumentException) { + LOGGER.warn("Invalid UTF-8 user-ID encountered: ${String(it)}") + } + } + } + } + + @JvmStatic + @Throws(MissingPassphraseException::class, PGPException::class) + fun changePassphrase(keyId: Long?, + secretKeys: PGPSecretKeyRing, + oldProtector: SecretKeyRingProtector, + newProtector: SecretKeyRingProtector): PGPSecretKeyRing { + return if (keyId == null) { + PGPSecretKeyRing( + secretKeys.secretKeys.asSequence().map { + reencryptPrivateKey(it, oldProtector, newProtector) + }.toList() + ) + } else { + PGPSecretKeyRing( + secretKeys.secretKeys.asSequence().map { + if (it.keyID == keyId) { + reencryptPrivateKey(it, oldProtector, newProtector) + } else { + it + } + }.toList() + ) + }.let { s2kUsageFixIfNecessary(it, newProtector) } + } + + @JvmStatic + fun reencryptPrivateKey(secretKey: PGPSecretKey, + oldProtector: SecretKeyRingProtector, + newProtector: SecretKeyRingProtector): PGPSecretKey { + if (secretKey.s2K != null && secretKey.s2K.type == S2K.GNU_DUMMY_S2K) { + // If the key uses GNU_DUMMY_S2K we leave it as is + return secretKey + } + + return PGPSecretKey.copyWithNewPassword(secretKey, + oldProtector.getDecryptor(secretKey.keyID), + newProtector.getEncryptor(secretKey.keyID)) + } + + @JvmStatic + fun s2kUsageFixIfNecessary(secretKeys: PGPSecretKeyRing, protector: SecretKeyRingProtector): PGPSecretKeyRing { + if (secretKeys.secretKeys.asSequence().any { it.s2KUsage == SecretKeyPacket.USAGE_CHECKSUM }) { + return S2KUsageFix.replaceUsageChecksumWithUsageSha1(secretKeys, protector, true) + } + return secretKeys + } + + } +} \ No newline at end of file From 39e170064ce1ca7fb7891b450cc1c44dc656de95 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 4 Sep 2023 16:44:41 +0200 Subject: [PATCH 076/155] Kotlin conversion: NotationRegistry --- .../org/pgpainless/util/NotationRegistry.kt} | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) rename pgpainless-core/src/main/{java/org/pgpainless/util/NotationRegistry.java => kotlin/org/pgpainless/util/NotationRegistry.kt} (57%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java b/pgpainless-core/src/main/kotlin/org/pgpainless/util/NotationRegistry.kt similarity index 57% rename from pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java rename to pgpainless-core/src/main/kotlin/org/pgpainless/util/NotationRegistry.kt index 11aa7651..15dbe886 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/NotationRegistry.kt @@ -1,11 +1,8 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.util; - -import java.util.HashSet; -import java.util.Set; +package org.pgpainless.util /** * Registry for known notations. @@ -14,9 +11,12 @@ import java.util.Set; * * To add a notation name, call {@link #addKnownNotation(String)}. */ -public class NotationRegistry { +class NotationRegistry constructor(notations: Set = setOf()) { + private val knownNotations: MutableSet - private final Set knownNotations = new HashSet<>(); + init { + knownNotations = notations.toMutableSet() + } /** * Add a known notation name into the registry. @@ -24,11 +24,8 @@ public class NotationRegistry { * * @param notationName name of the notation */ - public void addKnownNotation(String notationName) { - if (notationName == null) { - throw new NullPointerException("Notation name MUST NOT be null."); - } - knownNotations.add(notationName); + fun addKnownNotation(notationName: String): NotationRegistry = apply { + knownNotations.add(notationName) } /** @@ -37,14 +34,12 @@ public class NotationRegistry { * @param notationName name of the notation * @return true if notation is known, false otherwise. */ - public boolean isKnownNotation(String notationName) { - return knownNotations.contains(notationName); - } + fun isKnownNotation(notationName: String): Boolean = knownNotations.contains(notationName) /** * Clear all known notations from the registry. */ - public void clear() { - knownNotations.clear(); + fun clear() { + knownNotations.clear() } -} +} \ No newline at end of file From d8df6c35d0cfe7bd7918aefca937f617b2ea8487 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 11:35:44 +0200 Subject: [PATCH 077/155] Rename heyKeyId -> openPgpKeyId --- .../{_kotlin => openpgp}/LongExtensions.kt | 10 +++++----- .../OpenPgpMessageInputStream.kt | 18 +++++++++--------- .../org/pgpainless/key/SubkeyIdentifier.kt | 4 ++-- .../CachingSecretKeyRingProtector.kt | 6 +++--- .../key/protection/UnlockSecretKey.kt | 4 ++-- .../org/pgpainless/key/util/KeyIdUtil.kt | 8 ++++---- .../org/pgpainless/key/util/KeyRingUtils.kt | 10 +++++----- .../subpackets/SignatureSubpacketsUtil.kt | 4 ++-- 8 files changed, 32 insertions(+), 32 deletions(-) rename pgpainless-core/src/main/kotlin/{_kotlin => openpgp}/LongExtensions.kt (69%) diff --git a/pgpainless-core/src/main/kotlin/_kotlin/LongExtensions.kt b/pgpainless-core/src/main/kotlin/openpgp/LongExtensions.kt similarity index 69% rename from pgpainless-core/src/main/kotlin/_kotlin/LongExtensions.kt rename to pgpainless-core/src/main/kotlin/openpgp/LongExtensions.kt index b2c64e2e..13f94943 100644 --- a/pgpainless-core/src/main/kotlin/_kotlin/LongExtensions.kt +++ b/pgpainless-core/src/main/kotlin/openpgp/LongExtensions.kt @@ -2,19 +2,19 @@ // // SPDX-License-Identifier: Apache-2.0 -package _kotlin +package openpgp /** - * Format this Long as a 16 digit uppercase hex number. + * Format this Long as an OpenPGP key-ID (16 digit uppercase hex number). */ -fun Long.hexKeyId(): String { +fun Long.openPgpKeyId(): String { return String.format("%016X", this).uppercase() } /** - * Parse a 16 digit hex number into a Long. + * Parse a Long form a 16 digit hex encoded OpenPgp key-ID. */ -fun Long.Companion.fromHexKeyId(hexKeyId: String): Long { +fun Long.Companion.fromOpenPgpKeyId(hexKeyId: String): Long { require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) { "Provided long key-id does not match expected format. " + "A long key-id consists of 16 hexadecimal characters." diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 3c056cfa..4b7b1f1f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -4,7 +4,7 @@ package org.pgpainless.decryption_verification -import _kotlin.hexKeyId +import openpgp.openPgpKeyId import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.UnsupportedPacketVersionException import org.bouncycastle.openpgp.* @@ -180,7 +180,7 @@ class OpenPgpMessageInputStream( private fun processOnePassSignature() { syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE) val ops = packetInputStream!!.readOnePassSignature() - LOGGER.debug("One-Pass-Signature Packet by key ${ops.keyID.hexKeyId()} at depth ${layerMetadata.depth} encountered.") + LOGGER.debug("One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.addOnePassSignature(ops) } @@ -197,11 +197,11 @@ class OpenPgpMessageInputStream( val keyId = SignatureUtils.determineIssuerKeyId(signature) if (isSigForOps) { - LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${keyId.hexKeyId()} at depth ${layerMetadata.depth} encountered.") + LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy) } else { - LOGGER.debug("Prepended Signature Packet by key ${keyId.hexKeyId()} at depth ${layerMetadata.depth} encountered.") + LOGGER.debug("Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.addPrependedSignature(signature) } } @@ -282,10 +282,10 @@ class OpenPgpMessageInputStream( // try (known) secret keys for (pkesk in esks.pkesks) { val keyId = pkesk.keyID - LOGGER.debug("Encountered PKESK for recipient ${keyId.hexKeyId()}") + LOGGER.debug("Encountered PKESK for recipient ${keyId.openPgpKeyId()}") val decryptionKeys = getDecryptionKey(keyId) if (decryptionKeys == null) { - LOGGER.debug("Skipping PKESK because no matching key ${keyId.hexKeyId()} was provided.") + LOGGER.debug("Skipping PKESK because no matching key ${keyId.openPgpKeyId()} was provided.") continue } val secretKey = decryptionKeys.getSecretKey(keyId) @@ -618,7 +618,7 @@ class OpenPgpMessageInputStream( if (check != null) { detachedSignatures.add(check) } else { - LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.hexKeyId()} found.") + LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") detachedSignaturesWithMissingCert.add(SignatureVerification.Failure( SignatureVerification(signature, null), SignatureValidationException("Missing verification key."))) @@ -631,7 +631,7 @@ class OpenPgpMessageInputStream( if (check != null) { prependedSignatures.add(check) } else { - LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.hexKeyId()} found.") + LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") prependedSignaturesWithMissingCert.add(SignatureVerification.Failure( SignatureVerification(signature, null), SignatureValidationException("Missing verification key") @@ -693,7 +693,7 @@ class OpenPgpMessageInputStream( } if (!found) { - LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.hexKeyId()} found.") + LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") inbandSignaturesWithMissingCert.add(SignatureVerification.Failure( SignatureVerification(signature, null), SignatureValidationException("Missing verification key.") diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt index c09c1ee5..ea36913c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt @@ -4,7 +4,7 @@ package org.pgpainless.key -import _kotlin.hexKeyId +import openpgp.openPgpKeyId import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKey @@ -22,7 +22,7 @@ class SubkeyIdentifier( constructor(keys: PGPKeyRing, keyId: Long): this( OpenPgpFingerprint.of(keys.publicKey), OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?: - throw NoSuchElementException("OpenPGP key does not contain subkey ${keyId.hexKeyId()}"))) + throw NoSuchElementException("OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}"))) constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint) val keyId = subkeyFingerprint.keyId diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt index 7a2bcc26..32f69f70 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt @@ -4,7 +4,7 @@ package org.pgpainless.key.protection -import _kotlin.hexKeyId +import openpgp.openPgpKeyId import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKey import org.pgpainless.key.OpenPgpFingerprint @@ -54,7 +54,7 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras */ fun addPassphrase(keyId: Long, passphrase: Passphrase) = apply { require(!cache.containsKey(keyId)) { - "The cache already holds a passphrase for ID ${keyId.hexKeyId()}.\n" + + "The cache already holds a passphrase for ID ${keyId.openPgpKeyId()}.\n" + "If you want to replace this passphrase, use replacePassphrase(Long, Passphrase) instead." } cache[keyId] = passphrase @@ -90,7 +90,7 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras // check for existing passphrases before doing anything keyRing.publicKeys.forEach { require(!cache.containsKey(it.keyID)) { - "The cache already holds a passphrase for the key with ID ${it.keyID.hexKeyId()}.\n" + + "The cache already holds a passphrase for the key with ID ${it.keyID.openPgpKeyId()}.\n" + "If you want to replace the passphrase, use replacePassphrase(PGPKeyRing, Passphrase) instead." } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt index 7385b265..1317ef05 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt @@ -5,7 +5,7 @@ package org.pgpainless.key.protection -import _kotlin.hexKeyId +import openpgp.openPgpKeyId import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPSecretKey @@ -43,7 +43,7 @@ class UnlockSecretKey { if (privateKey == null) { if (secretKey.s2K.type in 100..110) { - throw PGPException("Cannot decrypt secret key ${secretKey.keyID.hexKeyId()}: \n" + + throw PGPException("Cannot decrypt secret key ${secretKey.keyID.openPgpKeyId()}: \n" + "Unsupported private S2K type ${secretKey.s2K.type}") } throw PGPException("Cannot decrypt secret key.") diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt index b6ff1789..63b65672 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt @@ -4,8 +4,8 @@ package org.pgpainless.key.util -import _kotlin.fromHexKeyId -import _kotlin.hexKeyId +import openpgp.fromOpenPgpKeyId +import openpgp.openPgpKeyId class KeyIdUtil { @@ -21,7 +21,7 @@ class KeyIdUtil { @JvmStatic @Deprecated("Superseded by Long extension method.", ReplaceWith("Long.fromHexKeyId(longKeyId)")) - fun fromLongKeyId(longKeyId: String) = Long.fromHexKeyId(longKeyId) + fun fromLongKeyId(longKeyId: String) = Long.fromOpenPgpKeyId(longKeyId) /** * Format a long key-ID as upper-case hex string. @@ -31,6 +31,6 @@ class KeyIdUtil { @JvmStatic @Deprecated("Superseded by Long extension method.", ReplaceWith("keyId.hexKeyId()")) - fun formatKeyId(keyId: Long) = keyId.hexKeyId() + fun formatKeyId(keyId: Long) = keyId.openPgpKeyId() } } \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt index bd0edf28..d3c77ab6 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt @@ -4,7 +4,7 @@ package org.pgpainless.key.util -import _kotlin.hexKeyId +import openpgp.openPgpKeyId import org.bouncycastle.bcpg.S2K import org.bouncycastle.bcpg.SecretKeyPacket import org.bouncycastle.extensions.certificate @@ -100,7 +100,7 @@ class KeyRingUtils { @JvmStatic fun requirePublicKeyFrom(keyRing: PGPKeyRing, subKeyId: Long): PGPPublicKey { return keyRing.getPublicKey(subKeyId) - ?: throw NoSuchElementException("KeyRing does not contain public key with keyId ${subKeyId.hexKeyId()}.") + ?: throw NoSuchElementException("KeyRing does not contain public key with keyId ${subKeyId.openPgpKeyId()}.") } /** @@ -114,7 +114,7 @@ class KeyRingUtils { @JvmStatic fun requireSecretKeyFrom(keyRing: PGPSecretKeyRing, subKeyId: Long): PGPSecretKey { return keyRing.getSecretKey(subKeyId) - ?: throw NoSuchElementException("KeyRing does not contain secret key with keyID ${subKeyId.hexKeyId()}.") + ?: throw NoSuchElementException("KeyRing does not contain secret key with keyID ${subKeyId.openPgpKeyId()}.") } @JvmStatic @@ -233,7 +233,7 @@ class KeyRingUtils { var certificate: PGPPublicKeyRing = secretAndPublicKeys.second if (!keyRingContainsKeyWithId(certificate, certifiedKey.keyID)) { - throw NoSuchElementException("Cannot find public key with id ${certifiedKey.keyID.hexKeyId()} in the provided key ring.") + throw NoSuchElementException("Cannot find public key with id ${certifiedKey.keyID.openPgpKeyId()} in the provided key ring.") } certificate = PGPPublicKeyRing( @@ -389,7 +389,7 @@ class KeyRingUtils { "Bouncy Castle currently cannot deal with stripped primary secret keys." } if (secretKeys.getSecretKey(keyId) == null) { - throw NoSuchElementException("PGPSecretKeyRing does not contain secret key ${keyId.hexKeyId()}.") + throw NoSuchElementException("PGPSecretKeyRing does not contain secret key ${keyId.openPgpKeyId()}.") } val out = ByteArrayOutputStream() diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt index 1b9b432a..172ef32f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt @@ -4,7 +4,7 @@ package org.pgpainless.signature.subpackets -import _kotlin.hexKeyId +import openpgp.openPgpKeyId import org.bouncycastle.bcpg.sig.* import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature @@ -143,7 +143,7 @@ class SignatureSubpacketsUtil { @JvmStatic fun getKeyExpirationTimeAsDate(signature: PGPSignature, signingKey: PGPPublicKey): Date? = require(signature.keyID == signingKey.keyID) { - "Provided key (${signingKey.keyID.hexKeyId()}) did not create the signature (${signature.keyID.hexKeyId()})" + "Provided key (${signingKey.keyID.openPgpKeyId()}) did not create the signature (${signature.keyID.openPgpKeyId()})" }.run { getKeyExpirationTime(signature)?.let { SignatureUtils.datePlusSeconds(signingKey.creationTime, it.time) From 3115e13bc2a71cca3d72b5ea569417b3a4e76748 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 11:39:57 +0200 Subject: [PATCH 078/155] Kotlin conversion: CustomPublicKeyDataDecryptorFactory --- .../CustomPublicKeyDataDecryptorFactory.java | 27 ------------------- .../CustomPublicKeyDataDecryptorFactory.kt | 26 ++++++++++++++++++ 2 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.java deleted file mode 100644 index 91902cc7..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.pgpainless.key.SubkeyIdentifier; - -/** - * Custom {@link PublicKeyDataDecryptorFactory} which can enable customized implementations of message decryption - * using public keys. - * This class can for example be used to implement message encryption using hardware tokens like smartcards or - * TPMs. - * @see ConsumerOptions#addCustomDecryptorFactory(CustomPublicKeyDataDecryptorFactory) - */ -public interface CustomPublicKeyDataDecryptorFactory extends PublicKeyDataDecryptorFactory { - - /** - * Return the {@link SubkeyIdentifier} for which this particular {@link CustomPublicKeyDataDecryptorFactory} - * is intended. - * - * @return subkey identifier - */ - SubkeyIdentifier getSubkeyIdentifier(); - -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt new file mode 100644 index 00000000..b7f57da3 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory +import org.pgpainless.key.SubkeyIdentifier + +/** + * Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message decryption + * using public keys. + * This class can for example be used to implement message encryption using hardware tokens like smartcards or + * TPMs. + * @see [ConsumerOptions.addCustomDecryptorFactory] + */ +interface CustomPublicKeyDataDecryptorFactory : PublicKeyDataDecryptorFactory { + + /** + * Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] + * is intended. + * + * @return subkey identifier + */ + val subkeyIdentifier: SubkeyIdentifier +} \ No newline at end of file From b09979fa457e0221dc2e34ae911e7dbf4f0b208a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 11:49:39 +0200 Subject: [PATCH 079/155] Kotlin conversion: HardwareSecurity --- .../HardwareSecurity.java | 105 ------------------ .../HardwareSecurity.kt | 76 +++++++++++++ 2 files changed, 76 insertions(+), 105 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/HardwareSecurity.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/HardwareSecurity.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/HardwareSecurity.java deleted file mode 100644 index fc88ef33..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/HardwareSecurity.java +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import org.bouncycastle.bcpg.AEADEncDataPacket; -import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSessionKey; -import org.bouncycastle.openpgp.operator.PGPDataDecryptor; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; -import org.pgpainless.key.SubkeyIdentifier; - -/** - * Enable integration of hardware-backed OpenPGP keys. - */ -public class HardwareSecurity { - - public interface DecryptionCallback { - - /** - * Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with - * hardware security modules such as smartcards or TPMs. - * - * If decryption fails for some reason, a subclass of the {@link HardwareSecurityException} is thrown. - * - * @param keyId id of the key - * @param keyAlgorithm algorithm - * @param sessionKeyData encrypted session key - * - * @return decrypted session key - * @throws HardwareSecurityException exception - */ - byte[] decryptSessionKey(long keyId, int keyAlgorithm, byte[] sessionKeyData) - throws HardwareSecurityException; - - } - - /** - * Implementation of {@link PublicKeyDataDecryptorFactory} which delegates decryption of encrypted session keys - * to a {@link DecryptionCallback}. - * Users can provide such a callback to delegate decryption of messages to hardware security SDKs. - */ - public static class HardwareDataDecryptorFactory implements CustomPublicKeyDataDecryptorFactory { - - private final DecryptionCallback callback; - // luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument. - private final PublicKeyDataDecryptorFactory factory = - new BcPublicKeyDataDecryptorFactory(null); - private final SubkeyIdentifier subkey; - - /** - * Create a new {@link HardwareDataDecryptorFactory}. - * - * @param subkeyIdentifier identifier of the decryption subkey - * @param callback decryption callback - */ - public HardwareDataDecryptorFactory(SubkeyIdentifier subkeyIdentifier, DecryptionCallback callback) { - this.callback = callback; - this.subkey = subkeyIdentifier; - } - - @Override - public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) - throws PGPException { - try { - // delegate decryption to the callback - return callback.decryptSessionKey(subkey.getSubkeyId(), keyAlgorithm, secKeyData[0]); - } catch (HardwareSecurityException e) { - throw new PGPException("Hardware-backed decryption failed.", e); - } - } - - @Override - public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) - throws PGPException { - return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); - } - - @Override - public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey) - throws PGPException { - return factory.createDataDecryptor(aeadEncDataPacket, sessionKey); - } - - @Override - public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey) - throws PGPException { - return factory.createDataDecryptor(seipd, sessionKey); - } - - @Override - public SubkeyIdentifier getSubkeyIdentifier() { - return subkey; - } - } - - public static class HardwareSecurityException - extends Exception { - - } - -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt new file mode 100644 index 00000000..7d0d243a --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.bcpg.AEADEncDataPacket +import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPSessionKey +import org.bouncycastle.openpgp.operator.PGPDataDecryptor +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory +import org.pgpainless.key.SubkeyIdentifier +import kotlin.jvm.Throws + +/** + * Enable integration of hardware-backed OpenPGP keys. + */ +class HardwareSecurity { + + interface DecryptionCallback { + + /** + * Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with + * hardware security modules such as smartcards or TPMs. + * + * If decryption fails for some reason, a subclass of the [HardwareSecurityException] is thrown. + * + * @param keyId id of the key + * @param keyAlgorithm algorithm + * @param sessionKeyData encrypted session key + * + * @return decrypted session key + * @throws HardwareSecurityException exception + */ + @Throws(HardwareSecurityException::class) + fun decryptSessionKey(keyId: Long, keyAlgorithm: Int, sessionKeyData: ByteArray): ByteArray + } + + /** + * Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted session keys + * to a [DecryptionCallback]. + * Users can provide such a callback to delegate decryption of messages to hardware security SDKs. + */ + class HardwareDataDecryptorFactory( + override val subkeyIdentifier: SubkeyIdentifier, + private val callback: DecryptionCallback, + ) : CustomPublicKeyDataDecryptorFactory { + + // luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument. + private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null) + + override fun createDataDecryptor(withIntegrityPacket: Boolean, encAlgorithm: Int, key: ByteArray?): PGPDataDecryptor { + return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key) + } + + override fun createDataDecryptor(aeadEncDataPacket: AEADEncDataPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor { + return factory.createDataDecryptor(aeadEncDataPacket, sessionKey) + } + + override fun createDataDecryptor(seipd: SymmetricEncIntegrityPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor { + return factory.createDataDecryptor(seipd, sessionKey) + } + + override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array): ByteArray { + return try { + callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0]) + } catch (e : HardwareSecurityException) { + throw PGPException("Hardware-backed decryption failed.", e) + } + } + } + + class HardwareSecurityException : Exception() +} \ No newline at end of file From 4cfdcca2e015e53f2aea89b225324514b62e927e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 14:42:44 +0200 Subject: [PATCH 080/155] Kotlin conversion: MessageMetadata --- .../MessageMetadata.java | 869 ------------------ .../MessageMetadata.kt | 528 +++++++++++ .../OpenPgpMessageInputStream.kt | 21 +- .../SignatureVerification.kt | 13 +- .../MessageMetadataTest.java | 6 +- 5 files changed, 549 insertions(+), 888 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java deleted file mode 100644 index 1f7a5b03..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java +++ /dev/null @@ -1,869 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.StreamEncoding; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.authentication.CertificateAuthenticity; -import org.pgpainless.authentication.CertificateAuthority; -import org.pgpainless.exception.MalformedOpenPgpMessageException; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.util.SessionKey; - -/** - * View for extracting metadata about a {@link Message}. - */ -public class MessageMetadata { - - protected Message message; - - public MessageMetadata(@Nonnull Message message) { - this.message = message; - } - - public boolean isUsingCleartextSignatureFramework() { - return message.isCleartextSigned(); - } - - public boolean isEncrypted() { - SymmetricKeyAlgorithm algorithm = getEncryptionAlgorithm(); - return algorithm != null && algorithm != SymmetricKeyAlgorithm.NULL; - } - - public boolean isEncryptedFor(@Nonnull PGPKeyRing keys) { - Iterator encryptionLayers = getEncryptionLayers(); - while (encryptionLayers.hasNext()) { - EncryptedData encryptedData = encryptionLayers.next(); - for (long recipient : encryptedData.getRecipients()) { - PGPPublicKey key = keys.getPublicKey(recipient); - if (key != null) { - return true; - } - } - } - return false; - } - - /** - * Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId. - * - * @param userId userId - * @param email if true, treat the user-id as an email address and match all userIDs containing this address - * @param certificateAuthority certificate authority - * @return true, if we can authenticate a binding for a signing key with sufficient evidence - */ - public boolean isAuthenticatablySignedBy(String userId, boolean email, CertificateAuthority certificateAuthority) { - return isAuthenticatablySignedBy(userId, email, certificateAuthority, 120); - } - - /** - * Return true, if the message was verifiably signed by a certificate for which we can authenticate a binding to the given userId. - * - * @param userId userId - * @param email if true, treat the user-id as an email address and match all userIDs containing this address - * @param certificateAuthority certificate authority - * @param targetAmount target trust amount - * @return true, if we can authenticate a binding for a signing key with sufficient evidence - */ - public boolean isAuthenticatablySignedBy(String userId, boolean email, CertificateAuthority certificateAuthority, int targetAmount) { - for (SignatureVerification verification : getVerifiedSignatures()) { - CertificateAuthenticity authenticity = certificateAuthority.authenticateBinding( - verification.getSigningKey().getFingerprint(), userId, email, - verification.getSignature().getCreationTime(), targetAmount); - if (authenticity.isAuthenticated()) { - return true; - } - } - return false; - } - - /** - * Return a list containing all recipient keyIDs. - * - * @return list of recipients - */ - public List getRecipientKeyIds() { - List keyIds = new ArrayList<>(); - Iterator encLayers = getEncryptionLayers(); - while (encLayers.hasNext()) { - EncryptedData layer = encLayers.next(); - keyIds.addAll(layer.getRecipients()); - } - return keyIds; - } - - public @Nonnull Iterator getEncryptionLayers() { - return new LayerIterator(message) { - @Override - public boolean matches(Packet layer) { - return layer instanceof EncryptedData; - } - - @Override - public EncryptedData getProperty(Layer last) { - return (EncryptedData) last; - } - }; - } - - /** - * Return the {@link SymmetricKeyAlgorithm} of the outermost encrypted data packet, or null if message is - * unencrypted. - * - * @return encryption algorithm - */ - public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() { - return firstOrNull(getEncryptionAlgorithms()); - } - - /** - * Return an {@link Iterator} of {@link SymmetricKeyAlgorithm SymmetricKeyAlgorithms} encountered in the message. - * The first item returned by the iterator is the algorithm of the outermost encrypted data packet, the next item - * that of the next nested encrypted data packet and so on. - * The iterator might also be empty, in case of an unencrypted message. - * - * @return iterator of symmetric encryption algorithms - */ - public @Nonnull Iterator getEncryptionAlgorithms() { - return map(getEncryptionLayers(), encryptedData -> encryptedData.algorithm); - } - - public @Nonnull Iterator getCompressionLayers() { - return new LayerIterator(message) { - @Override - boolean matches(Packet layer) { - return layer instanceof CompressedData; - } - - @Override - CompressedData getProperty(Layer last) { - return (CompressedData) last; - } - }; - } - - /** - * Return the {@link CompressionAlgorithm} of the outermost compressed data packet, or null, if the message - * does not contain any compressed data packets. - * - * @return compression algorithm - */ - public @Nullable CompressionAlgorithm getCompressionAlgorithm() { - return firstOrNull(getCompressionAlgorithms()); - } - - /** - * Return an {@link Iterator} of {@link CompressionAlgorithm CompressionAlgorithms} encountered in the message. - * The first item returned by the iterator is the algorithm of the outermost compressed data packet, the next - * item that of the next nested compressed data packet and so on. - * The iterator might also be empty, in case of a message without any compressed data packets. - * - * @return iterator of compression algorithms - */ - public @Nonnull Iterator getCompressionAlgorithms() { - return map(getCompressionLayers(), compressionLayer -> compressionLayer.algorithm); - } - - /** - * Return the {@link SessionKey} of the outermost encrypted data packet. - * If the message was unencrypted, this method returns
null
. - * - * @return session key of the message - */ - public @Nullable SessionKey getSessionKey() { - return firstOrNull(getSessionKeys()); - } - - /** - * Return an {@link Iterator} of {@link SessionKey SessionKeys} for all encrypted data packets in the message. - * The first item returned by the iterator is the session key of the outermost encrypted data packet, - * the next item that of the next nested encrypted data packet and so on. - * The iterator might also be empty, in case of an unencrypted message. - * - * @return iterator of session keys - */ - public @Nonnull Iterator getSessionKeys() { - return map(getEncryptionLayers(), encryptedData -> encryptedData.sessionKey); - } - - public boolean isVerifiedSignedBy(@Nonnull PGPKeyRing keys) { - return isVerifiedInlineSignedBy(keys) || isVerifiedDetachedSignedBy(keys); - } - - /** - * Return true, if the message was verifiable signed by a certificate that either has the given fingerprint - * as primary key, or as the signing subkey. - * - * @param fingerprint fingerprint - * @return true if message was signed by a cert identified by the given fingerprint - */ - public boolean isVerifiedSignedBy(@Nonnull OpenPgpFingerprint fingerprint) { - List verifications = getVerifiedSignatures(); - for (SignatureVerification verification : verifications) { - if (verification.getSigningKey() == null) { - continue; - } - - if (fingerprint.equals(verification.getSigningKey().getPrimaryKeyFingerprint()) || - fingerprint.equals(verification.getSigningKey().getSubkeyFingerprint())) { - return true; - } - } - return false; - } - - public List getVerifiedSignatures() { - List allVerifiedSignatures = getVerifiedInlineSignatures(); - allVerifiedSignatures.addAll(getVerifiedDetachedSignatures()); - return allVerifiedSignatures; - } - - public boolean isVerifiedDetachedSignedBy(@Nonnull PGPKeyRing keys) { - return containsSignatureBy(getVerifiedDetachedSignatures(), keys); - } - - /** - * Return a list of all verified detached signatures. - * This list contains all acceptable, correct detached signatures. - * - * @return verified detached signatures - */ - public @Nonnull List getVerifiedDetachedSignatures() { - return message.getVerifiedDetachedSignatures(); - } - - /** - * Return a list of all rejected detached signatures. - * - * @return rejected detached signatures - */ - public @Nonnull List getRejectedDetachedSignatures() { - return message.getRejectedDetachedSignatures(); - } - - /** - * Return a list of all rejected signatures. - * - * @return rejected signatures - */ - public @Nonnull List getRejectedSignatures() { - List rejected = new ArrayList<>(); - rejected.addAll(getRejectedInlineSignatures()); - rejected.addAll(getRejectedDetachedSignatures()); - return rejected; - } - - public boolean hasRejectedSignatures() { - return !getRejectedSignatures().isEmpty(); - } - - /** - * Return true, if the message contains any (verified or rejected) signature. - * @return true if message has signature - */ - public boolean hasSignature() { - return isVerifiedSigned() || hasRejectedSignatures(); - } - - public boolean isVerifiedInlineSignedBy(@Nonnull PGPKeyRing keys) { - return containsSignatureBy(getVerifiedInlineSignatures(), keys); - } - - /** - * Return a list of all verified inline-signatures. - * This list contains all acceptable, correct signatures that were part of the message itself. - * - * @return verified inline signatures - */ - public @Nonnull List getVerifiedInlineSignatures() { - List verifications = new ArrayList<>(); - Iterator> verificationsByLayer = getVerifiedInlineSignaturesByLayer(); - while (verificationsByLayer.hasNext()) { - verifications.addAll(verificationsByLayer.next()); - } - return verifications; - } - - /** - * Return an {@link Iterator} of {@link List Lists} of verified inline-signatures of the message. - * Since signatures might occur in different layers within a message, this method can be used to gain more detailed - * insights into what signatures were encountered at what layers of the message structure. - * Each item of the {@link Iterator} represents a layer of the message and contains only signatures from - * this layer. - * An empty list means no (or no acceptable) signatures were encountered in that layer. - * - * @return iterator of lists of signatures by-layer. - */ - public @Nonnull Iterator> getVerifiedInlineSignaturesByLayer() { - return new LayerIterator>(message) { - @Override - boolean matches(Packet layer) { - return layer instanceof Layer; - } - - @Override - List getProperty(Layer last) { - List list = new ArrayList<>(); - list.addAll(last.getVerifiedOnePassSignatures()); - list.addAll(last.getVerifiedPrependedSignatures()); - return list; - } - }; - } - - /** - * Return a list of all rejected inline-signatures of the message. - * - * @return list of rejected inline-signatures - */ - public @Nonnull List getRejectedInlineSignatures() { - List rejected = new ArrayList<>(); - Iterator> rejectedByLayer = getRejectedInlineSignaturesByLayer(); - while (rejectedByLayer.hasNext()) { - rejected.addAll(rejectedByLayer.next()); - } - return rejected; - } - - /** - * Similar to {@link #getVerifiedInlineSignaturesByLayer()}, this method returns all rejected inline-signatures - * of the message, but organized by layer. - * - * @return rejected inline-signatures by-layer - */ - public @Nonnull Iterator> getRejectedInlineSignaturesByLayer() { - return new LayerIterator>(message) { - @Override - boolean matches(Packet layer) { - return layer instanceof Layer; - } - - @Override - List getProperty(Layer last) { - List list = new ArrayList<>(); - list.addAll(last.getRejectedOnePassSignatures()); - list.addAll(last.getRejectedPrependedSignatures()); - return list; - } - }; - } - - private static boolean containsSignatureBy(@Nonnull List verifications, - @Nonnull PGPKeyRing keys) { - for (SignatureVerification verification : verifications) { - SubkeyIdentifier issuer = verification.getSigningKey(); - if (issuer == null) { - // No issuer, shouldn't happen, but better be safe and skip... - continue; - } - - if (keys.getPublicKey().getKeyID() != issuer.getPrimaryKeyId()) { - // Wrong cert - continue; - } - - if (keys.getPublicKey(issuer.getSubkeyId()) != null) { - // Matching cert and signing key - return true; - } - } - return false; - } - - /** - * Return the value of the literal data packet's filename field. - * This value can be used to store a decrypted file under its original filename, - * but since this field is not necessarily part of the signed data of a message, usage of this field is - * discouraged. - * - * @return filename - * @see RFC4880 §5.9. Literal Data Packet - */ - public @Nullable String getFilename() { - LiteralData literalData = findLiteralData(); - if (literalData == null) { - return null; - } - return literalData.getFileName(); - } - - /** - * Returns true, if the filename of the literal data packet indicates that the data is intended for your eyes only. - * - * @return isForYourEyesOnly - */ - public boolean isForYourEyesOnly() { - return PGPLiteralData.CONSOLE.equals(getFilename()); - } - - /** - * Return the value of the literal data packets modification date field. - * This value can be used to restore the modification date of a decrypted file, - * but since this field is not necessarily part of the signed data, its use is discouraged. - * - * @return modification date - * @see RFC4880 §5.9. Literal Data Packet - */ - public @Nullable Date getModificationDate() { - LiteralData literalData = findLiteralData(); - if (literalData == null) { - return null; - } - return literalData.getModificationDate(); - } - - /** - * Return the value of the format field of the literal data packet. - * This value indicates what format (text, binary data, ...) the data has. - * Since this field is not necessarily part of the signed data of a message, its usage is discouraged. - * - * @return format - * @see RFC4880 §5.9. Literal Data Packet - */ - public @Nullable StreamEncoding getLiteralDataEncoding() { - LiteralData literalData = findLiteralData(); - if (literalData == null) { - return null; - } - return literalData.getFormat(); - } - - /** - * Find the {@link LiteralData} layer of an OpenPGP message. - * Usually, every message has a literal data packet, but for malformed messages this method might still - * return
null
. - * - * @return literal data - */ - private @Nullable LiteralData findLiteralData() { - Nested nested = message.getChild(); - if (nested == null) { - return null; - } - - while (nested != null && nested.hasNestedChild()) { - Layer layer = (Layer) nested; - nested = layer.getChild(); - } - return (LiteralData) nested; - } - - /** - * Return the {@link SubkeyIdentifier} of the decryption key that was used to decrypt the outermost encryption - * layer. - * If the message was unencrypted, this might return
null
. - * - * @return decryption key - */ - public SubkeyIdentifier getDecryptionKey() { - return firstOrNull(map(getEncryptionLayers(), encryptedData -> encryptedData.decryptionKey)); - } - - public boolean isVerifiedSigned() { - return !getVerifiedSignatures().isEmpty(); - } - - public interface Packet { - - } - public abstract static class Layer implements Packet { - public static final int MAX_LAYER_DEPTH = 16; - protected final int depth; - protected final List verifiedDetachedSignatures = new ArrayList<>(); - protected final List rejectedDetachedSignatures = new ArrayList<>(); - protected final List verifiedOnePassSignatures = new ArrayList<>(); - protected final List rejectedOnePassSignatures = new ArrayList<>(); - protected final List verifiedPrependedSignatures = new ArrayList<>(); - protected final List rejectedPrependedSignatures = new ArrayList<>(); - protected Nested child; - - public Layer(int depth) { - this.depth = depth; - if (depth > MAX_LAYER_DEPTH) { - throw new MalformedOpenPgpMessageException("Maximum packet nesting depth (" + MAX_LAYER_DEPTH + ") exceeded."); - } - } - - /** - * Return the nested child element of this layer. - * Might return
null
, if this layer does not have a child element - * (e.g. if this is a {@link LiteralData} packet). - * - * @return child element - */ - public @Nullable Nested getChild() { - return child; - } - - /** - * Set the nested child element for this layer. - * - * @param child child element - */ - void setChild(Nested child) { - this.child = child; - } - - /** - * Return a list of all verified detached signatures of this layer. - * - * @return all verified detached signatures of this layer - */ - public List getVerifiedDetachedSignatures() { - return new ArrayList<>(verifiedDetachedSignatures); - } - - /** - * Return a list of all rejected detached signatures of this layer. - * - * @return all rejected detached signatures of this layer - */ - public List getRejectedDetachedSignatures() { - return new ArrayList<>(rejectedDetachedSignatures); - } - - /** - * Add a verified detached signature for this layer. - * - * @param signatureVerification verified detached signature - */ - void addVerifiedDetachedSignature(SignatureVerification signatureVerification) { - verifiedDetachedSignatures.add(signatureVerification); - } - - /** - * Add a rejected detached signature for this layer. - * - * @param failure rejected detached signature - */ - void addRejectedDetachedSignature(SignatureVerification.Failure failure) { - rejectedDetachedSignatures.add(failure); - } - - /** - * Return a list of all verified one-pass-signatures of this layer. - * - * @return all verified one-pass-signatures of this layer - */ - public List getVerifiedOnePassSignatures() { - return new ArrayList<>(verifiedOnePassSignatures); - } - - /** - * Return a list of all rejected one-pass-signatures of this layer. - * - * @return all rejected one-pass-signatures of this layer - */ - public List getRejectedOnePassSignatures() { - return new ArrayList<>(rejectedOnePassSignatures); - } - - /** - * Add a verified one-pass-signature for this layer. - * - * @param verifiedOnePassSignature verified one-pass-signature for this layer - */ - void addVerifiedOnePassSignature(SignatureVerification verifiedOnePassSignature) { - this.verifiedOnePassSignatures.add(verifiedOnePassSignature); - } - - /** - * Add a rejected one-pass-signature for this layer. - * - * @param rejected rejected one-pass-signature for this layer - */ - void addRejectedOnePassSignature(SignatureVerification.Failure rejected) { - this.rejectedOnePassSignatures.add(rejected); - } - - /** - * Return a list of all verified prepended signatures of this layer. - * - * @return all verified prepended signatures of this layer - */ - public List getVerifiedPrependedSignatures() { - return new ArrayList<>(verifiedPrependedSignatures); - } - - /** - * Return a list of all rejected prepended signatures of this layer. - * - * @return all rejected prepended signatures of this layer - */ - public List getRejectedPrependedSignatures() { - return new ArrayList<>(rejectedPrependedSignatures); - } - - /** - * Add a verified prepended signature for this layer. - * - * @param verified verified prepended signature - */ - void addVerifiedPrependedSignature(SignatureVerification verified) { - this.verifiedPrependedSignatures.add(verified); - } - - /** - * Add a rejected prepended signature for this layer. - * - * @param rejected rejected prepended signature - */ - void addRejectedPrependedSignature(SignatureVerification.Failure rejected) { - this.rejectedPrependedSignatures.add(rejected); - } - - } - - public interface Nested extends Packet { - boolean hasNestedChild(); - } - - public static class Message extends Layer { - - protected boolean cleartextSigned; - - public Message() { - super(0); - } - - /** - * Returns true, is the message is a signed message using the cleartext signature framework. - * - * @return
true
if message is cleartext-signed,
false
otherwise - * @see RFC4880 §7. Cleartext Signature Framework - */ - public boolean isCleartextSigned() { - return cleartextSigned; - } - - } - - public static class LiteralData implements Nested { - protected String fileName; - protected Date modificationDate; - protected StreamEncoding format; - - public LiteralData() { - this("", new Date(0L), StreamEncoding.BINARY); - } - - public LiteralData(@Nonnull String fileName, - @Nonnull Date modificationDate, - @Nonnull StreamEncoding format) { - this.fileName = fileName; - this.modificationDate = modificationDate; - this.format = format; - } - - /** - * Return the value of the filename field. - * An empty String
""
indicates no filename. - * - * @return filename - */ - public @Nonnull String getFileName() { - return fileName; - } - - /** - * Return the value of the modification date field. - * A special date
{@code new Date(0L)}
indicates no modification date. - * - * @return modification date - */ - public @Nonnull Date getModificationDate() { - return modificationDate; - } - - /** - * Return the value of the format field. - * - * @return format - */ - public @Nonnull StreamEncoding getFormat() { - return format; - } - - @Override - public boolean hasNestedChild() { - // A literal data packet MUST NOT have a child element, as its content is the plaintext - return false; - } - } - - public static class CompressedData extends Layer implements Nested { - protected final CompressionAlgorithm algorithm; - - public CompressedData(@Nonnull CompressionAlgorithm zip, int depth) { - super(depth); - this.algorithm = zip; - } - - /** - * Return the {@link CompressionAlgorithm} used to compress the packet. - * @return compression algorithm - */ - public @Nonnull CompressionAlgorithm getAlgorithm() { - return algorithm; - } - - @Override - public boolean hasNestedChild() { - // A compressed data packet MUST have a child element - return true; - } - } - - public static class EncryptedData extends Layer implements Nested { - protected final SymmetricKeyAlgorithm algorithm; - protected SubkeyIdentifier decryptionKey; - protected SessionKey sessionKey; - protected List recipients; - - public EncryptedData(@Nonnull SymmetricKeyAlgorithm algorithm, int depth) { - super(depth); - this.algorithm = algorithm; - } - - /** - * Return the {@link SymmetricKeyAlgorithm} used to encrypt the packet. - * @return symmetric encryption algorithm - */ - public @Nonnull SymmetricKeyAlgorithm getAlgorithm() { - return algorithm; - } - - /** - * Return the {@link SessionKey} used to decrypt the packet. - * - * @return session key - */ - public @Nonnull SessionKey getSessionKey() { - return sessionKey; - } - - /** - * Return a list of all recipient key ids to which the packet was encrypted for. - * - * @return recipients - */ - public @Nonnull List getRecipients() { - if (recipients == null) { - return new ArrayList<>(); - } - return new ArrayList<>(recipients); - } - - @Override - public boolean hasNestedChild() { - // An encrypted data packet MUST have a child element - return true; - } - } - - - private abstract static class LayerIterator implements Iterator { - private Nested current; - Layer last = null; - Message parent; - - LayerIterator(@Nonnull Message message) { - super(); - this.parent = message; - this.current = message.getChild(); - if (matches(current)) { - last = (Layer) current; - } - } - - @Override - public boolean hasNext() { - if (parent != null && matches(parent)) { - return true; - } - if (last == null) { - findNext(); - } - return last != null; - } - - @Override - public O next() { - if (parent != null && matches(parent)) { - O property = getProperty(parent); - parent = null; - return property; - } - if (last == null) { - findNext(); - } - if (last != null) { - O property = getProperty(last); - last = null; - return property; - } - throw new NoSuchElementException(); - } - - private void findNext() { - while (current != null && current instanceof Layer) { - current = ((Layer) current).getChild(); - if (matches(current)) { - last = (Layer) current; - break; - } - } - } - - abstract boolean matches(Packet layer); - - abstract O getProperty(Layer last); - } - - private static Iterator map(Iterator from, Function mapping) { - return new Iterator() { - @Override - public boolean hasNext() { - return from.hasNext(); - } - - @Override - public B next() { - return mapping.apply(from.next()); - } - }; - } - - public interface Function { - B apply(A item); - } - - private static @Nullable A firstOrNull(Iterator iterator) { - if (iterator.hasNext()) { - return iterator.next(); - } - return null; - } - - private static @Nonnull A firstOr(Iterator iterator, A item) { - if (iterator.hasNext()) { - return iterator.next(); - } - return item; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt new file mode 100644 index 00000000..c4f31f4c --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt @@ -0,0 +1,528 @@ +package org.pgpainless.decryption_verification + +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPLiteralData +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.StreamEncoding +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.authentication.CertificateAuthority +import org.pgpainless.exception.MalformedOpenPgpMessageException +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.util.SessionKey +import java.util.* +import javax.annotation.Nonnull + +/** + * View for extracting metadata about a [Message]. + */ +class MessageMetadata( + val message: Message +) { + + // ################################################################################################################ + // ### Encryption ### + // ################################################################################################################ + + /** + * The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is unencrypted. + */ + val encryptionAlgorithm: SymmetricKeyAlgorithm? + get() = encryptionAlgorithms.let { + if (it.hasNext()) it.next() else null + } + + /** + * [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. + * The first item returned by the iterator is the algorithm of the outermost encrypted data packet, the next item + * that of the next nested encrypted data packet and so on. + * The iterator might also be empty, in case of an unencrypted message. + */ + val encryptionAlgorithms: Iterator + get() = encryptionLayers.asSequence().map { it.algorithm }.iterator() + + val isEncrypted: Boolean + get() = if (encryptionAlgorithm == null) false else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL + + fun isEncryptedFor(keys: PGPKeyRing): Boolean { + return encryptionLayers.asSequence().any { + it.recipients.any { keyId -> + keys.getPublicKey(keyId) != null + } + } + } + + /** + * [SessionKey] of the outermost encrypted data packet. + * If the message was unencrypted, this method returns `null`. + */ + val sessionKey: SessionKey? + get() = sessionKeys.asSequence().firstOrNull() + + /** + * [Iterator] of each [SessionKey] for all encrypted data packets in the message. + * The first item returned by the iterator is the session key of the outermost encrypted data packet, + * the next item that of the next nested encrypted data packet and so on. + * The iterator might also be empty, in case of an unencrypted message. + */ + val sessionKeys: Iterator + get() = encryptionLayers.asSequence().mapNotNull { it.sessionKey }.iterator() + + /** + * [SubkeyIdentifier] of the decryption key that was used to decrypt the outermost encryption + * layer. + * If the message was unencrypted or was decrypted using a passphrase, this field might be `null`. + */ + val decryptionKey: SubkeyIdentifier? + get() = encryptionLayers.asSequence() + .mapNotNull { it.decryptionKey } + .firstOrNull() + + /** + * List containing all recipient keyIDs. + */ + val recipientKeyIds: List + get() = encryptionLayers.asSequence() + .map { it.recipients.toMutableList() } + .reduce { all, keyIds -> all.addAll(keyIds); all } + .toList() + + val encryptionLayers: Iterator + get() = object : LayerIterator(message) { + override fun matches(layer: Packet) = layer is EncryptedData + override fun getProperty(last: Layer) = last as EncryptedData + } + + // ################################################################################################################ + // ### Compression ### + // ################################################################################################################ + + /** + * [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message + * does not contain any compressed data packets. + */ + val compressionAlgorithm: CompressionAlgorithm? = compressionAlgorithms.asSequence().firstOrNull() + + /** + * [Iterator] of each [CompressionAlgorithm] encountered in the message. + * The first item returned by the iterator is the algorithm of the outermost compressed data packet, the next + * item that of the next nested compressed data packet and so on. + * The iterator might also be empty, in case of a message without any compressed data packets. + */ + val compressionAlgorithms: Iterator + get() = compressionLayers.asSequence().map { it.algorithm }.iterator() + + val compressionLayers: Iterator + get() = object : LayerIterator(message) { + override fun matches(layer: Packet) = layer is CompressedData + override fun getProperty(last: Layer) = last as CompressedData + } + + // ################################################################################################################ + // ### Signatures ### + // ################################################################################################################ + + val isUsingCleartextSignatureFramework: Boolean + get() = message.cleartextSigned + + val verifiedSignatures: List + get() = verifiedInlineSignatures.plus(verifiedDetachedSignatures) + + /** + * List of all rejected signatures. + */ + val rejectedSignatures: List + get() = mutableListOf() + .plus(rejectedInlineSignatures) + .plus(rejectedDetachedSignatures) + .toList() + + /** + * List of all verified inline-signatures. + * This list contains all acceptable, correct signatures that were part of the message itself. + */ + val verifiedInlineSignatures: List = verifiedInlineSignaturesByLayer + .asSequence() + .map { it.toMutableList() } + .reduce { acc, signatureVerifications -> acc.addAll(signatureVerifications); acc } + .toList() + + /** + * [Iterator] of each [List] of verified inline-signatures of the message, separated by layer. + * Since signatures might occur in different layers within a message, this method can be used to gain more detailed + * insights into what signatures were encountered at what layers of the message structure. + * Each item of the [Iterator] represents a layer of the message and contains only signatures from + * this layer. + * An empty list means no (or no acceptable) signatures were encountered in that layer. + */ + val verifiedInlineSignaturesByLayer: Iterator> + get() = object : LayerIterator>(message) { + override fun matches(layer: Packet) = layer is Layer + + override fun getProperty(last: Layer): List { + return listOf() + .plus(last.verifiedOnePassSignatures) + .plus(last.verifiedPrependedSignatures) + } + + } + + /** + * List of all rejected inline-signatures of the message. + */ + val rejectedInlineSignatures: List = rejectedInlineSignaturesByLayer + .asSequence() + .map { it.toMutableList() } + .reduce { acc, failures -> acc.addAll(failures); acc} + .toList() + + /** + * Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected inline-signatures + * of the message, but organized by layer. + */ + val rejectedInlineSignaturesByLayer: Iterator> + get() = object : LayerIterator>(message) { + override fun matches(layer: Packet) = layer is Layer + + override fun getProperty(last: Layer): List = + mutableListOf() + .plus(last.rejectedOnePassSignatures) + .plus(last.rejectedPrependedSignatures) + } + + /** + * List of all verified detached signatures. + * This list contains all acceptable, correct detached signatures. + */ + val verifiedDetachedSignatures: List = message.verifiedDetachedSignatures + + /** + * List of all rejected detached signatures. + */ + val rejectedDetachedSignatures: List = message.rejectedDetachedSignatures + + /** + * True, if the message contains any (verified or rejected) signature, false if no signatures are present. + */ + val hasSignature: Boolean + get() = isVerifiedSigned() || hasRejectedSignatures() + + fun isVerifiedSigned(): Boolean = verifiedSignatures.isNotEmpty() + + fun hasRejectedSignatures(): Boolean = rejectedSignatures.isNotEmpty() + + /** + * Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId. + * + * @param userId userId + * @param email if true, treat the user-id as an email address and match all userIDs containing this address + * @param certificateAuthority certificate authority + * @param targetAmount targeted trust amount that needs to be reached by the binding to qualify as authenticated. + * defaults to 120. + * @return true, if we can authenticate a binding for a signing key with sufficient evidence + */ + @JvmOverloads + fun isAuthenticatablySignedBy(userId: String, email: Boolean, certificateAuthority: CertificateAuthority, targetAmount: Int = 120): Boolean { + return verifiedSignatures.any { + certificateAuthority.authenticateBinding( + it.signingKey.fingerprint, userId, email, it.signature.creationTime, targetAmount + ).authenticated + } + } + + /** + * Return rue, if the message was verifiable signed by a certificate that either has the given fingerprint + * as primary key, or as the signing subkey. + * + * @param fingerprint fingerprint + * @return true if message was signed by a cert identified by the given fingerprint + */ + fun isVerifiedSignedBy(fingerprint: OpenPgpFingerprint) = verifiedSignatures.any { + it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint + } + + fun isVerifiedSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedSignatures, keys) + + fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) = verifiedDetachedSignatures.any { + it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint + } + + fun isVerifiedDetachedSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedDetachedSignatures, keys) + + fun isVerifiedInlineSignedBy(fingerprint: OpenPgpFingerprint) = verifiedInlineSignatures.any { + it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint + } + + fun isVerifiedInlineSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedInlineSignatures, keys) + + private fun containsSignatureBy(signatures: List, keys: PGPKeyRing) = + signatures.any { + // Match certificate by primary key id + keys.publicKey.keyID == it.signingKey.primaryKeyId && + // match signing subkey + keys.getPublicKey(it.signingKey.subkeyId) != null + } + + // ################################################################################################################ + // ### Literal Data ### + // ################################################################################################################ + + /** + * Value of the literal data packet's filename field. + * This value can be used to store a decrypted file under its original filename, + * but since this field is not necessarily part of the signed data of a message, usage of this field is + * discouraged. + * + * @see RFC4880 §5.9. Literal Data Packet + */ + val filename: String? = findLiteralData()?.fileName + + /** + * True, if the sender signals an increased degree of confidentiality by setting the filename of the literal + * data packet to a special value that indicates that the data is intended for your eyes only. + */ + @Deprecated("Reliance on this signaling mechanism is discouraged.") + val isForYourEyesOnly: Boolean = PGPLiteralData.CONSOLE == filename + + /** + * Value of the literal data packets modification date field. + * This value can be used to restore the modification date of a decrypted file, + * but since this field is not necessarily part of the signed data, its use is discouraged. + * + * @see RFC4880 §5.9. Literal Data Packet + */ + val modificationDate: Date? = findLiteralData()?.modificationDate + + /** + * Value of the format field of the literal data packet. + * This value indicates what format (text, binary data, ...) the data has. + * Since this field is not necessarily part of the signed data of a message, its usage is discouraged. + * + * @see RFC4880 §5.9. Literal Data Packet + */ + val literalDataEncoding: StreamEncoding? = findLiteralData()?.format + + /** + * Find the [LiteralData] layer of an OpenPGP message. + * This method might return null, for example for a cleartext signed message without OpenPGP packets. + * + * @return literal data + */ + private fun findLiteralData(): LiteralData? { + // If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed message, + // we might not have a Literal Data packet. + var nested = message.child ?: return null + + while (nested.hasNestedChild()) { + val layer = nested as Layer + nested = checkNotNull(layer.child) { + // Otherwise, we MUST find a Literal Data packet, or else the message is malformed + "Malformed OpenPGP message. Cannot find Literal Data Packet" + } + } + return nested as LiteralData + } + + // ################################################################################################################ + // ### Message Structure ### + // ################################################################################################################ + + interface Packet + + interface Nested : Packet { + fun hasNestedChild(): Boolean + } + + abstract class Layer( + val depth: Int + ) : Packet { + + init { + if (depth > MAX_LAYER_DEPTH) { + throw MalformedOpenPgpMessageException("Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.") + } + } + + val verifiedDetachedSignatures: List = mutableListOf() + val rejectedDetachedSignatures: List = mutableListOf() + val verifiedOnePassSignatures: List = mutableListOf() + val rejectedOnePassSignatures: List = mutableListOf() + val verifiedPrependedSignatures: List = mutableListOf() + val rejectedPrependedSignatures: List = mutableListOf() + + /** + * Nested child element of this layer. + * Might be `null`, if this layer does not have a child element + * (e.g. if this is a [LiteralData] packet). + */ + var child: Nested? = null + + fun addVerifiedDetachedSignature(signature: SignatureVerification) = apply { + (verifiedDetachedSignatures as MutableList).add(signature) + } + + fun addRejectedDetachedSignature(failure: SignatureVerification.Failure) = apply { + (rejectedDetachedSignatures as MutableList).add(failure) + } + + fun addVerifiedOnePassSignature(signature: SignatureVerification) = apply { + (verifiedOnePassSignatures as MutableList).add(signature) + } + + fun addRejectedOnePassSignature(failure: SignatureVerification.Failure) = apply { + (rejectedOnePassSignatures as MutableList).add(failure) + } + + fun addVerifiedPrependedSignature(signature: SignatureVerification) = apply { + (verifiedPrependedSignatures as MutableList).add(signature) + } + + fun addRejectedPrependedSignature(failure: SignatureVerification.Failure) = apply { + (rejectedPrependedSignatures as MutableList).add(failure) + } + + companion object { + const val MAX_LAYER_DEPTH = 16 + } + } + + /** + * Outermost OpenPGP Message structure. + * + * @param cleartextSigned whether the message is using the Cleartext Signature Framework + * + * @see RFC4880 §7. Cleartext Signature Framework + */ + class Message(var cleartextSigned: Boolean = false) : Layer(0) { + fun setCleartextSigned() = apply { cleartextSigned = true } + } + + /** + * Literal Data Packet. + * + * @param fileName value of the filename field. An empty String represents no filename. + * @param modificationDate value of the modification date field. The special value `Date(0)` indicates no + * modification date. + * @param format value of the format field. + */ + class LiteralData( + val fileName: String = "", + val modificationDate: Date = Date(0L), + val format: StreamEncoding = StreamEncoding.BINARY + ) : Nested { + + // A literal data packet MUST NOT have a child element, as its content is the plaintext + override fun hasNestedChild() = false + } + + /** + * Compressed Data Packet. + * + * @param algorithm [CompressionAlgorithm] used to compress the packet. + * @param depth nesting depth at which this packet was encountered. + */ + class CompressedData( + val algorithm: CompressionAlgorithm, + depth: Int) : Layer(depth), Nested { + + // A compressed data packet MUST have a child element + override fun hasNestedChild() = true + } + + /** + * Encrypted Data. + * + * @param algorithm symmetric key algorithm used to encrypt the packet. + * @param depth nesting depth at which this packet was encountered. + */ + class EncryptedData( + val algorithm: SymmetricKeyAlgorithm, + depth: Int + ) : Layer(depth), Nested { + + /** + * [SessionKey] used to decrypt the packet. + */ + var sessionKey: SessionKey? = null + + /** + * List of all recipient key ids to which the packet was encrypted for. + */ + val recipients: List = mutableListOf() + + fun addRecipients(keyIds: List) = apply { + (recipients as MutableList).addAll(keyIds) + } + + /** + * Identifier of the subkey that was used to decrypt the packet (in case of a public key encrypted packet). + */ + var decryptionKey: SubkeyIdentifier? = null + + // An encrypted data packet MUST have a child element + override fun hasNestedChild() = true + + } + + /** + * Iterator that iterates the packet structure from outermost to innermost packet, emitting the results of + * a transformation ([getProperty]) on those packets that match ([matches]) a given criterion. + * + * @param message outermost structure object + */ + private abstract class LayerIterator(@Nonnull message: Message) : Iterator { + private var current: Nested? + var last: Layer? = null + var parent: Message? + + init { + parent = message + current = message.child + current?.let { + if (matches(it)) { + last = current as Layer + } + } + } + + override fun hasNext(): Boolean { + parent?.let { + if (matches(it)) { + return true + } + } + if (last == null) { + findNext() + } + return last != null + } + + override fun next(): O { + parent?.let { + if (matches(it)) { + return getProperty(it).also { parent = null } + } + } + if (last == null) { + findNext() + } + last?.let { + return getProperty(it).also { last = null } + } + throw NoSuchElementException() + } + + private fun findNext() { + while (current != null && current is Layer) { + current = (current as Layer).child + if (current != null && matches(current!!)) { + last = current as Layer + break + } + } + } + + abstract fun matches(layer: Packet): Boolean + abstract fun getProperty(last: Layer): O + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 4b7b1f1f..87b0847a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -156,9 +156,9 @@ class OpenPgpMessageInputStream( val literalData = packetInputStream!!.readLiteralData() // Extract Metadata - layerMetadata.setChild(LiteralData( + layerMetadata.child = LiteralData( literalData.fileName, literalData.modificationTime, - StreamEncoding.requireFromCode(literalData.format))) + StreamEncoding.requireFromCode(literalData.format)) nestedInputStream = literalData.inputStream } @@ -394,7 +394,7 @@ class OpenPgpMessageInputStream( throwIfUnacceptable(sessionKey.algorithm) val encryptedData = EncryptedData(sessionKey.algorithm, layerMetadata.depth + 1) encryptedData.sessionKey = sessionKey - encryptedData.recipients = esks.pkesks.map { it.keyID } + encryptedData.addRecipients(esks.pkesks.map { it.keyID }) LOGGER.debug("Successfully decrypted data with passphrase") val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options) nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) @@ -421,7 +421,7 @@ class OpenPgpMessageInputStream( layerMetadata.depth + 1) encryptedData.decryptionKey = decryptionKeyId encryptedData.sessionKey = sessionKey - encryptedData.recipients = esks.pkesks.plus(esks.anonPkesks).map { it.keyID } + encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyID }) LOGGER.debug("Successfully decrypted data with key $decryptionKeyId") val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options) nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) @@ -522,7 +522,7 @@ class OpenPgpMessageInputStream( private fun collectMetadata() { if (nestedInputStream is OpenPgpMessageInputStream) { val child = nestedInputStream as OpenPgpMessageInputStream - layerMetadata.setChild(child.layerMetadata as Nested) + layerMetadata.child = (child.layerMetadata as Nested) } } @@ -620,8 +620,7 @@ class OpenPgpMessageInputStream( } else { LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") detachedSignaturesWithMissingCert.add(SignatureVerification.Failure( - SignatureVerification(signature, null), - SignatureValidationException("Missing verification key."))) + signature, null, SignatureValidationException("Missing verification key."))) } } @@ -633,8 +632,7 @@ class OpenPgpMessageInputStream( } else { LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") prependedSignaturesWithMissingCert.add(SignatureVerification.Failure( - SignatureVerification(signature, null), - SignatureValidationException("Missing verification key") + signature, null, SignatureValidationException("Missing verification key") )) } } @@ -695,8 +693,7 @@ class OpenPgpMessageInputStream( if (!found) { LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") inbandSignaturesWithMissingCert.add(SignatureVerification.Failure( - SignatureVerification(signature, null), - SignatureValidationException("Missing verification key.") + signature, null, SignatureValidationException("Missing verification key.") )) } } @@ -890,7 +887,7 @@ class OpenPgpMessageInputStream( return if (openPgpIn.isAsciiArmored) { val armorIn = ArmoredInputStreamFactory.get(openPgpIn) if (armorIn.isClearText) { - (metadata as Message).cleartextSigned = true + (metadata as Message).setCleartextSigned() OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy) } else { // Simply consume dearmored OpenPGP message diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt index a188f6a5..8d229fb2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt @@ -22,12 +22,12 @@ import org.pgpainless.signature.SignatureUtils */ data class SignatureVerification( val signature: PGPSignature, - val signingKey: SubkeyIdentifier? + val signingKey: SubkeyIdentifier ) { override fun toString(): String { return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" + - " Key: ${signingKey?.toString() ?: "null"};" + " Key: $signingKey;" } /** @@ -38,11 +38,16 @@ data class SignatureVerification( * @param validationException exception that caused the verification to fail */ data class Failure( - val signatureVerification: SignatureVerification, + val signature: PGPSignature, + val signingKey: SubkeyIdentifier?, val validationException: SignatureValidationException ) { + + constructor(verification: SignatureVerification, validationException: SignatureValidationException): + this(verification.signature, verification.signingKey, validationException) + override fun toString(): String { - return "$signatureVerification Failure: ${validationException.message}" + return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}" } } } \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MessageMetadataTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MessageMetadataTest.java index 771cb8f1..2c29d62a 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MessageMetadataTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MessageMetadataTest.java @@ -28,9 +28,9 @@ public class MessageMetadataTest { // For the sake of testing though, this is okay. MessageMetadata.Message message = new MessageMetadata.Message(); - MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.depth + 1); - MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128, compressedData.depth + 1); - MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256, encryptedData.depth + 1); + MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.getDepth() + 1); + MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128, compressedData.getDepth() + 1); + MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256, encryptedData.getDepth() + 1); MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData(); message.setChild(compressedData); From a268cfd51acccf7ed3aa0d3844670a1d88bdc4f5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 14:45:45 +0200 Subject: [PATCH 081/155] Add missing license header --- .../org/pgpainless/decryption_verification/MessageMetadata.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt index c4f31f4c..8719cef7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.decryption_verification import org.bouncycastle.openpgp.PGPKeyRing From 1234c8800ab3bad1cd9fc7e6c699dabacbf499f1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 14:48:15 +0200 Subject: [PATCH 082/155] Kotlin conversion: MissingPublicKeyCallback --- .../MissingPublicKeyCallback.kt} | 19 +++++++------------ ...erifyWithMissingPublicKeyCallbackTest.java | 5 ++--- 2 files changed, 9 insertions(+), 15 deletions(-) rename pgpainless-core/src/main/{java/org/pgpainless/decryption_verification/MissingPublicKeyCallback.java => kotlin/org/pgpainless/decryption_verification/MissingPublicKeyCallback.kt} (67%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MissingPublicKeyCallback.java b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingPublicKeyCallback.kt similarity index 67% rename from pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MissingPublicKeyCallback.java rename to pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingPublicKeyCallback.kt index 9b6f4a1e..723530b7 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MissingPublicKeyCallback.java +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingPublicKeyCallback.kt @@ -1,33 +1,28 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.decryption_verification; +package org.pgpainless.decryption_verification -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.bouncycastle.openpgp.PGPPublicKeyRing -import org.bouncycastle.openpgp.PGPPublicKeyRing; - -public interface MissingPublicKeyCallback { +fun interface MissingPublicKeyCallback { /** * This method gets called if we encounter a signature made by a key which was not provided for signature verification. * If you cannot provide the requested key, it is safe to return null here. * PGPainless will then continue verification with the next signature. * - * Note: The key-id might belong to a subkey, so be aware that when looking up the {@link PGPPublicKeyRing}, + * Note: The key-id might belong to a subkey, so be aware that when looking up the [PGPPublicKeyRing], * you may not only search for the key-id on the key rings primary key! * * It would be super cool to provide the OpenPgp fingerprint here, but unfortunately one-pass-signatures * only contain the key id. * * @param keyId ID of the missing signing (sub)key - * * @return keyring containing the key or null * * @see RFC */ - @Nullable PGPPublicKeyRing onMissingPublicKeyEncountered(@Nonnull Long keyId); - -} + fun onMissingPublicKeyEncountered(keyId: Long): PGPPublicKeyRing? +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallbackTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallbackTest.java index 52877626..0d58e7dd 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallbackTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallbackTest.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; -import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; @@ -63,8 +62,8 @@ public class VerifyWithMissingPublicKeyCallbackTest { .addVerificationCert(unrelatedKeys) .setMissingCertificateCallback(new MissingPublicKeyCallback() { @Override - public PGPPublicKeyRing onMissingPublicKeyEncountered(@Nonnull Long keyId) { - assertEquals(signingKey.getKeyID(), (long) keyId, "Signing key-ID mismatch."); + public PGPPublicKeyRing onMissingPublicKeyEncountered(long keyId) { + assertEquals(signingKey.getKeyID(), keyId, "Signing key-ID mismatch."); return signingPubKeys; } })); From 6e653f3c920ec8853249f283e000ea2939c974bb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 14:50:31 +0200 Subject: [PATCH 083/155] Kotlin conversion: MissingKeyPassphraseStrategy --- .../MissingKeyPassphraseStrategy.java | 21 ------------------ .../MissingKeyPassphraseStrategy.kt | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.java deleted file mode 100644 index ed6a9c63..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.java +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -/** - * Strategy defining how missing secret key passphrases are handled. - */ -public enum MissingKeyPassphraseStrategy { - /** - * Try to interactively obtain key passphrases one-by-one via callbacks, - * eg {@link org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider}. - */ - INTERACTIVE, - /** - * Do not try to obtain passphrases interactively and instead throw a - * {@link org.pgpainless.exception.MissingPassphraseException} listing all keys with missing passphrases. - */ - THROW_EXCEPTION -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.kt new file mode 100644 index 00000000..f8bb448b --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.kt @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +/** + * Strategy defining how missing secret key passphrases are handled. + */ +enum class MissingKeyPassphraseStrategy { + /** + * Try to interactively obtain key passphrases one-by-one via callbacks, + * eg [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider]. + */ + INTERACTIVE, + + /** + * Do not try to obtain passphrases interactively and instead throw a + * [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing passphrases. + */ + THROW_EXCEPTION +} \ No newline at end of file From 5441993baf885ee26b6c3e23ab332e15b87b0d58 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 15:53:45 +0200 Subject: [PATCH 084/155] Kotlin conversion: SigningOptions --- .../encryption_signing/SigningOptions.java | 634 ------------------ .../encryption_signing/SigningOptions.kt | 451 +++++++++++++ 2 files changed, 451 insertions(+), 634 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java deleted file mode 100644 index 34d4bbcf..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java +++ /dev/null @@ -1,634 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.DocumentSignatureType; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; -import org.pgpainless.exception.KeyException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnlockSecretKey; -import org.pgpainless.policy.Policy; -import org.pgpainless.signature.subpackets.BaseSignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; - -public final class SigningOptions { - - /** - * A method of signing. - */ - public static final class SigningMethod { - private final PGPSignatureGenerator signatureGenerator; - private final boolean detached; - private final HashAlgorithm hashAlgorithm; - - private SigningMethod(@Nonnull PGPSignatureGenerator signatureGenerator, - boolean detached, - @Nonnull HashAlgorithm hashAlgorithm) { - this.signatureGenerator = signatureGenerator; - this.detached = detached; - this.hashAlgorithm = hashAlgorithm; - } - - /** - * Inline-signature method. - * The resulting signature will be written into the message itself, together with a one-pass-signature packet. - * - * @param signatureGenerator signature generator - * @param hashAlgorithm hash algorithm used to generate the signature - * @return inline signing method - */ - public static SigningMethod inlineSignature(@Nonnull PGPSignatureGenerator signatureGenerator, - @Nonnull HashAlgorithm hashAlgorithm) { - return new SigningMethod(signatureGenerator, false, hashAlgorithm); - } - - /** - * Detached signing method. - * The resulting signature will not be added to the message, and instead can be distributed separately - * to the signed message. - * - * @param signatureGenerator signature generator - * @param hashAlgorithm hash algorithm used to generate the signature - * @return detached signing method - */ - public static SigningMethod detachedSignature(@Nonnull PGPSignatureGenerator signatureGenerator, - @Nonnull HashAlgorithm hashAlgorithm) { - return new SigningMethod(signatureGenerator, true, hashAlgorithm); - } - - public boolean isDetached() { - return detached; - } - - public PGPSignatureGenerator getSignatureGenerator() { - return signatureGenerator; - } - - public HashAlgorithm getHashAlgorithm() { - return hashAlgorithm; - } - } - - private final Map signingMethods = new HashMap<>(); - private HashAlgorithm hashAlgorithmOverride; - private Date evaluationDate = new Date(); - - @Nonnull - public static SigningOptions get() { - return new SigningOptions(); - } - - /** - * Override the evaluation date for signing keys with the given date. - * - * @param evaluationDate new evaluation date - * @return this - */ - public SigningOptions setEvaluationDate(@Nonnull Date evaluationDate) { - this.evaluationDate = evaluationDate; - return this; - } - - /** - * Sign the message using an inline signature made by the provided signing key. - * - * @param signingKeyProtector protector to unlock the signing key - * @param signingKey key ring containing the signing key - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be unlocked or a signing method cannot be created - */ - @Nonnull - public SigningOptions addSignature(@Nonnull SecretKeyRingProtector signingKeyProtector, - @Nonnull PGPSecretKeyRing signingKey) - throws PGPException { - return addInlineSignature(signingKeyProtector, signingKey, DocumentSignatureType.BINARY_DOCUMENT); - } - - /** - * Add inline signatures with all secret key rings in the provided secret key ring collection. - * - * @param secrectKeyDecryptor decryptor to unlock the signing secret keys - * @param signingKeys collection of signing keys - * @param signatureType type of signature (binary, canonical text) - * @return this - * - * @throws KeyException if something is wrong with any of the keys - * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created - */ - @Nonnull - public SigningOptions addInlineSignatures(@Nonnull SecretKeyRingProtector secrectKeyDecryptor, - @Nonnull Iterable signingKeys, - @Nonnull DocumentSignatureType signatureType) - throws KeyException, PGPException { - for (PGPSecretKeyRing signingKey : signingKeys) { - addInlineSignature(secrectKeyDecryptor, signingKey, signatureType); - } - return this; - } - - /** - * Add an inline-signature. - * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use - * of one-pass-signature packets. - * - * @param secretKeyDecryptor decryptor to unlock the signing secret key - * @param secretKey signing key - * @param signatureType type of signature (binary, canonical text) - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be unlocked or the signing method cannot be created - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nonnull DocumentSignatureType signatureType) - throws KeyException, PGPException { - return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType); - } - - /** - * Add an inline-signature. - * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use - * of one-pass-signature packets. - *

- * This method uses the passed in user-id to select user-specific hash algorithms. - * - * @param secretKeyDecryptor decryptor to unlock the signing secret key - * @param secretKey signing key - * @param userId user-id of the signer - * @param signatureType signature type (binary, canonical text) - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be unlocked or the signing method cannot be created - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nullable CharSequence userId, - @Nonnull DocumentSignatureType signatureType) - throws KeyException, PGPException { - return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null); - } - - /** - * Add an inline-signature. - * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use - * of one-pass-signature packets. - *

- * This method uses the passed in user-id to select user-specific hash algorithms. - * - * @param secretKeyDecryptor decryptor to unlock the signing secret key - * @param secretKey signing key - * @param userId user-id of the signer - * @param signatureType signature type (binary, canonical text) - * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature - * @return this - * - * @throws KeyException if the key is invalid - * @throws PGPException if the key cannot be unlocked or the signing method cannot be created - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nullable CharSequence userId, - @Nonnull DocumentSignatureType signatureType, - @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) - throws KeyException, PGPException { - KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate); - if (userId != null && !keyRingInfo.isUserIdValid(userId)) { - throw new KeyException.UnboundUserIdException( - OpenPgpFingerprint.of(secretKey), - userId.toString(), - keyRingInfo.getLatestUserIdCertification(userId), - keyRingInfo.getUserIdRevocation(userId) - ); - } - - List signingPubKeys = keyRingInfo.getSigningSubkeys(); - if (signingPubKeys.isEmpty()) { - throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey)); - } - - for (PGPPublicKey signingPubKey : signingPubKeys) { - PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); - if (signingSecKey == null) { - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID()); - } - PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); - Set hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId) - : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); - addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false); - } - - return this; - } - - /** - * Create a binary inline signature using the signing key with the given keyId. - * - * @param secretKeyDecryptor decryptor to unlock the secret key - * @param secretKey secret key ring - * @param keyId keyId of the signing (sub-)key - * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - long keyId) throws PGPException { - return addInlineSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null); - } - - - /** - * Create an inline signature using the signing key with the given keyId. - * - * @param secretKeyDecryptor decryptor to unlock the secret key - * @param secretKey secret key ring - * @param keyId keyId of the signing (sub-)key - * @param signatureType signature type - * @param subpacketsCallback callback to modify the signatures subpackets - * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - long keyId, - @Nonnull DocumentSignatureType signatureType, - @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException { - KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate); - - List signingPubKeys = keyRingInfo.getSigningSubkeys(); - if (signingPubKeys.isEmpty()) { - throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey)); - } - - for (PGPPublicKey signingPubKey : signingPubKeys) { - if (signingPubKey.getKeyID() == keyId) { - - PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); - if (signingSecKey == null) { - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID()); - } - PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); - Set hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); - addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false); - return this; - } - } - - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId); - } - - /** - * Add detached signatures with all key rings from the provided secret key ring collection. - * - * @param secretKeyDecryptor decryptor to unlock the secret signing keys - * @param signingKeys collection of signing key rings - * @param signatureType type of the signature (binary, canonical text) - * @return this - * - * @throws KeyException if something is wrong with any of the keys - * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created - */ - @Nonnull - public SigningOptions addDetachedSignatures(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull Iterable signingKeys, - @Nonnull DocumentSignatureType signatureType) - throws PGPException { - for (PGPSecretKeyRing signingKey : signingKeys) { - addDetachedSignature(secretKeyDecryptor, signingKey, signatureType); - } - return this; - } - - /** - * Create a detached signature. - * The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}. - * - * @param secretKeyDecryptor decryptor to unlock the secret signing key - * @param signingKey signing key - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing signingKey) - throws PGPException { - return addDetachedSignature(secretKeyDecryptor, signingKey, DocumentSignatureType.BINARY_DOCUMENT); - } - - /** - * Create a detached signature. - * Detached signatures are not being added into the PGP message itself. - * Instead, they can be distributed separately to the message. - * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). - * - * @param secretKeyDecryptor decryptor to unlock the secret signing key - * @param secretKey signing key - * @param signatureType type of data that is signed (binary, canonical text) - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nonnull DocumentSignatureType signatureType) - throws PGPException { - return addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType); - } - - /** - * Create a detached signature. - * Detached signatures are not being added into the PGP message itself. - * Instead, they can be distributed separately to the message. - * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). - *

- * This method uses the passed in user-id to select user-specific hash algorithms. - * - * @param secretKeyDecryptor decryptor to unlock the secret signing key - * @param secretKey signing key - * @param userId user-id - * @param signatureType type of data that is signed (binary, canonical text) - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nullable CharSequence userId, - @Nonnull DocumentSignatureType signatureType) - throws PGPException { - return addDetachedSignature(secretKeyDecryptor, secretKey, userId, signatureType, null); - } - - /** - * Create a detached signature. - * Detached signatures are not being added into the PGP message itself. - * Instead, they can be distributed separately to the message. - * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). - *

- * This method uses the passed in user-id to select user-specific hash algorithms. - * - * @param secretKeyDecryptor decryptor to unlock the secret signing key - * @param secretKey signing key - * @param userId user-id - * @param signatureType type of data that is signed (binary, canonical text) - * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nullable CharSequence userId, - @Nonnull DocumentSignatureType signatureType, - @Nullable BaseSignatureSubpackets.Callback subpacketCallback) - throws PGPException { - KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate); - if (userId != null && !keyRingInfo.isUserIdValid(userId)) { - throw new KeyException.UnboundUserIdException( - OpenPgpFingerprint.of(secretKey), - userId.toString(), - keyRingInfo.getLatestUserIdCertification(userId), - keyRingInfo.getUserIdRevocation(userId) - ); - } - - List signingPubKeys = keyRingInfo.getSigningSubkeys(); - if (signingPubKeys.isEmpty()) { - throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey)); - } - - for (PGPPublicKey signingPubKey : signingPubKeys) { - PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); - if (signingSecKey == null) { - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID()); - } - PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); - Set hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId) - : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); - addSigningMethod(secretKey, signingSubkey, subpacketCallback, hashAlgorithm, signatureType, true); - } - - return this; - } - - /** - * Create a detached binary signature using the signing key with the given keyId. - * - * @param secretKeyDecryptor decryptor to unlock the secret key - * @param secretKey secret key ring - * @param keyId keyId of the signing (sub-)key - * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - long keyId) throws PGPException { - return addDetachedSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null); - } - - /** - * Create a detached signature using the signing key with the given keyId. - * - * @param secretKeyDecryptor decryptor to unlock the secret key - * @param secretKey secret key ring - * @param keyId keyId of the signing (sub-)key - * @param signatureType signature type - * @param subpacketsCallback callback to modify the signatures subpackets - * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - long keyId, - @Nonnull DocumentSignatureType signatureType, - @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException { - KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate); - - List signingPubKeys = keyRingInfo.getSigningSubkeys(); - if (signingPubKeys.isEmpty()) { - throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey)); - } - - for (PGPPublicKey signingPubKey : signingPubKeys) { - if (signingPubKey.getKeyID() == keyId) { - - PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); - if (signingSecKey == null) { - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID()); - } - PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); - Set hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); - addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, true); - return this; - } - } - - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId); - } - - private void addSigningMethod(@Nonnull PGPSecretKeyRing secretKey, - @Nonnull PGPPrivateKey signingSubkey, - @Nullable BaseSignatureSubpackets.Callback subpacketCallback, - @Nonnull HashAlgorithm hashAlgorithm, - @Nonnull DocumentSignatureType signatureType, - boolean detached) - throws PGPException { - SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID()); - PGPSecretKey signingSecretKey = secretKey.getSecretKey(signingSubkey.getKeyID()); - PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(signingSecretKey.getPublicKey().getAlgorithm()); - int bitStrength = signingSecretKey.getPublicKey().getBitStrength(); - if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) { - throw new KeyException.UnacceptableSigningKeyException( - new KeyException.PublicKeyAlgorithmPolicyException( - OpenPgpFingerprint.of(secretKey), signingSecretKey.getKeyID(), publicKeyAlgorithm, bitStrength)); - } - - PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType); - - // Subpackets - SignatureSubpackets hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.getPublicKey()); - SignatureSubpackets unhashedSubpackets = SignatureSubpackets.createEmptySubpackets(); - if (subpacketCallback != null) { - subpacketCallback.modifyHashedSubpackets(hashedSubpackets); - subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets); - } - generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets)); - generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets)); - - SigningMethod signingMethod = detached ? - SigningMethod.detachedSignature(generator, hashAlgorithm) : - SigningMethod.inlineSignature(generator, hashAlgorithm); - signingMethods.put(signingKeyIdentifier, signingMethod); - } - - /** - * Negotiate, which hash algorithm to use. - *

- * This method gives the highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}. - * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm. - * Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is - * used as a fallback. - * - * @param preferences preferences - * @param policy policy - * @return selected hash algorithm - */ - @Nonnull - private HashAlgorithm negotiateHashAlgorithm(@Nonnull Set preferences, - @Nonnull Policy policy) { - if (hashAlgorithmOverride != null) { - return hashAlgorithmOverride; - } - - return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy) - .negotiateHashAlgorithm(preferences); - } - - @Nonnull - private PGPSignatureGenerator createSignatureGenerator(@Nonnull PGPPrivateKey privateKey, - @Nonnull HashAlgorithm hashAlgorithm, - @Nonnull DocumentSignatureType signatureType) - throws PGPException { - int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm(); - PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance() - .getPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm.getAlgorithmId()); - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder); - signatureGenerator.init(signatureType.getSignatureType().getCode(), privateKey); - - return signatureGenerator; - } - - /** - * Return a map of key-ids and signing methods. - * For internal use. - * - * @return signing methods - */ - @Nonnull - Map getSigningMethods() { - return Collections.unmodifiableMap(signingMethods); - } - - /** - * Override hash algorithm negotiation by dictating which hash algorithm needs to be used. - * If no override has been set, an accetable algorithm will be negotiated instead. - *

- * Note: To override the hash algorithm for signing, call this method *before* calling - * {@link #addInlineSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)} or - * {@link #addDetachedSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)}. - * - * @param hashAlgorithmOverride override hash algorithm - * @return this - */ - @Nonnull - public SigningOptions overrideHashAlgorithm(@Nonnull HashAlgorithm hashAlgorithmOverride) { - this.hashAlgorithmOverride = hashAlgorithmOverride; - return this; - } - - /** - * Return the hash algorithm override (or null if no override is set). - * - * @return hash algorithm override - */ - @Nullable - public HashAlgorithm getHashAlgorithmOverride() { - return hashAlgorithmOverride; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt new file mode 100644 index 00000000..b9208ed5 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt @@ -0,0 +1,451 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.openpgp.* +import org.pgpainless.PGPainless.Companion.getPolicy +import org.pgpainless.PGPainless.Companion.inspectKeyRing +import org.pgpainless.algorithm.DocumentSignatureType +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.PublicKeyAlgorithm.Companion.requireFromId +import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator.Companion.negotiateSignatureHashAlgorithm +import org.pgpainless.exception.KeyException +import org.pgpainless.exception.KeyException.* +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.OpenPgpFingerprint.Companion.of +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey +import org.pgpainless.policy.Policy +import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback +import org.pgpainless.signature.subpackets.SignatureSubpackets +import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper +import java.util.* + +class SigningOptions { + + val signingMethods: Map = mutableMapOf() + private var _hashAlgorithmOverride: HashAlgorithm? = null + private var _evaluationDate: Date = Date() + + val hashAlgorithmOverride: HashAlgorithm? + get() = _hashAlgorithmOverride + + /** + * Override hash algorithm negotiation by dictating which hash algorithm needs to be used. + * If no override has been set, an acceptable algorithm will be negotiated instead. + * Note: To override the hash algorithm for signing, call this method *before* calling + * [addInlineSignature] or [addDetachedSignature]. + * + * @param hashAlgorithmOverride override hash algorithm + * @return this + */ + fun overrideHashAlgorithm(hashAlgorithmOverride: HashAlgorithm) = apply { + _hashAlgorithmOverride = hashAlgorithmOverride + } + + val evaluationDate: Date + get() = _evaluationDate + + /** + * Override the evaluation date for signing keys with the given date. + * + * @param evaluationDate new evaluation date + * @return this + */ + fun setEvaluationDate(evaluationDate: Date) = apply { + _evaluationDate = evaluationDate + } + + /** + * Sign the message using an inline signature made by the provided signing key. + * + * @param signingKeyProtector protector to unlock the signing key + * @param signingKey key ring containing the signing key + * @return this + * + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be unlocked or a signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = apply { + addInlineSignature(signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT) + } + + /** + * Add inline signatures with all secret key rings in the provided secret key ring collection. + * + * @param signingKeyProtector decryptor to unlock the signing secret keys + * @param signingKeys collection of signing keys + * @param signatureType type of signature (binary, canonical text) + * @return this + * + * @throws KeyException if something is wrong with any of the keys + * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + fun addInlineSignatures(signingKeyProtector: SecretKeyRingProtector, + signingKeys: Iterable, + signatureType: DocumentSignatureType) = apply { + signingKeys.forEach { + addInlineSignature(signingKeyProtector, it, null, signatureType) + } + } + + + /** + * Add an inline-signature. + * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use + * of one-pass-signature packets. + * + * @param signingKeyProtector decryptor to unlock the signing secret key + * @param signingKey signing key + * @param signatureType type of signature (binary, canonical text) + * @return this + * + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be unlocked or the signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + signatureType: DocumentSignatureType) = apply { + addInlineSignature(signingKeyProtector, signingKey, null, signatureType) + } + + /** + * Add an inline-signature. + * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use + * of one-pass-signature packets. + *

+ * This method uses the passed in user-id to select user-specific hash algorithms. + * + * @param signingKeyProtector decryptor to unlock the signing secret key + * @param signingKey signing key + * @param userId user-id of the signer + * @param signatureType signature type (binary, canonical text) + * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature + * @return this + * + * @throws KeyException if the key is invalid + * @throws PGPException if the key cannot be unlocked or the signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + @JvmOverloads + fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + userId: CharSequence? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + if (userId != null && !keyRingInfo.isUserIdValid(userId)) { + throw UnboundUserIdException( + of(signingKey), + userId.toString(), + keyRingInfo.getLatestUserIdCertification(userId), + keyRingInfo.getUserIdRevocation(userId) + ) + } + + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey)) + } + + for (signingPubKey in signingPubKeys) { + val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) + val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = + if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) + else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) + } + } + + /** + * Create an inline signature using the signing key with the given keyId. + * + * @param signingKeyProtector decryptor to unlock the secret key + * @param signingKey secret key ring + * @param keyId keyId of the signing (sub-)key + * @param signatureType signature type + * @param subpacketsCallback callback to modify the signatures subpackets + * @return builder + * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. + * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys + * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key + */ + @Throws(KeyException::class, PGPException::class) + @JvmOverloads + fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + keyId: Long, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey)) + } + + for (signingPubKey in signingPubKeys) { + if (signingPubKey.keyID != keyId) { + continue + } + + val signingSecKey = signingKey.getSecretKey(signingPubKey.keyID) + ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) + val signingSubkey = unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) + return this + } + throw MissingSecretKeyException(of(signingKey), keyId) + } + + /** + * Add detached signatures with all key rings from the provided secret key ring collection. + * + * @param signingKeyProtector decryptor to unlock the secret signing keys + * @param signingKeys collection of signing key rings + * @param signatureType type of the signature (binary, canonical text) + * @return this + * + * @throws KeyException if something is wrong with any of the keys + * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + fun addDetachedSignatures(signingKeyProtector: SecretKeyRingProtector, + signingKeys: Iterable, + signatureType: DocumentSignatureType) = apply { + signingKeys.forEach { + addDetachedSignature(signingKeyProtector, it, null, signatureType) + } + } + + /** + * Create a detached signature. + * Detached signatures are not being added into the PGP message itself. + * Instead, they can be distributed separately to the message. + * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). + * + * @param signingKeyProtector decryptor to unlock the secret signing key + * @param signingKey signing key + * @param signatureType type of data that is signed (binary, canonical text) + * @return this + * + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created + */ + @Throws(KeyException::class, PGPException::class) + fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + signatureType: DocumentSignatureType) = apply { + addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) + } + + /** + * Create a detached signature. + * Detached signatures are not being added into the PGP message itself. + * Instead, they can be distributed separately to the message. + * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). + *

+ * This method uses the passed in user-id to select user-specific hash algorithms. + * + * @param signingKeyProtector decryptor to unlock the secret signing key + * @param signingKey signing key + * @param userId user-id + * @param signatureType type of data that is signed (binary, canonical text) + * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature + * @return this + * + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created + */ + @JvmOverloads + @Throws(KeyException::class, PGPException::class) + fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + userId: String? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketCallback: Callback? = null) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + if (userId != null && !keyRingInfo.isUserIdValid(userId)) { + throw UnboundUserIdException( + of(signingKey), + userId.toString(), + keyRingInfo.getLatestUserIdCertification(userId), + keyRingInfo.getUserIdRevocation(userId) + ) + } + + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey)) + } + + for (signingPubKey in signingPubKeys) { + val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) + val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = + if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) + else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback) + } + } + + /** + * Create a detached signature using the signing key with the given keyId. + * + * @param signingKeyProtector decryptor to unlock the secret key + * @param signingKey secret key ring + * @param keyId keyId of the signing (sub-)key + * @param signatureType signature type + * @param subpacketsCallback callback to modify the signatures subpackets + * @return builder + * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. + * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys + * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key + */ + @Throws(KeyException::class, PGPException::class) + @JvmOverloads + fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + keyId: Long, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey)) + } + + for (signingPubKey in signingPubKeys) { + if (signingPubKey.keyID == keyId) { + val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) + val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketsCallback) + return this + } + } + + throw MissingSecretKeyException(of(signingKey), keyId) + } + + private fun addSigningMethod(signingKey: PGPSecretKeyRing, + signingSubkey: PGPPrivateKey, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType, + detached: Boolean, + subpacketCallback: Callback? = null) { + val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID) + val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID) + val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm) + val bitStrength = signingSecretKey.publicKey.bitStrength + if (!getPolicy().publicKeyAlgorithmPolicy.isAcceptable(publicKeyAlgorithm, bitStrength)) { + throw UnacceptableSigningKeyException( + PublicKeyAlgorithmPolicyException( + of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength)) + } + + val generator: PGPSignatureGenerator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType) + + // Subpackets + val hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey) + val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets() + if (subpacketCallback != null) { + subpacketCallback.modifyHashedSubpackets(hashedSubpackets) + subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets) + } + generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets)) + generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets)) + + val signingMethod = + if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm) + else SigningMethod.inlineSignature(generator, hashAlgorithm) + (signingMethods as MutableMap)[signingKeyIdentifier] = signingMethod + } + + /** + * Negotiate, which hash algorithm to use. + * + * + * This method gives the highest priority to the algorithm override, which can be set via [.overrideHashAlgorithm]. + * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm. + * Lastly, should no acceptable algorithm be found, the [Policies][Policy] default signature hash algorithm is + * used as a fallback. + * + * @param preferences preferences + * @param policy policy + * @return selected hash algorithm + */ + private fun negotiateHashAlgorithm(preferences: Set, + policy: Policy): HashAlgorithm { + return _hashAlgorithmOverride ?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences) + } + + @Throws(PGPException::class) + private fun createSignatureGenerator(privateKey: PGPPrivateKey, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType): PGPSignatureGenerator { + return ImplementationFactory.getInstance() + .getPGPContentSignerBuilder(privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId) + .let { csb -> + PGPSignatureGenerator(csb).also { it.init(signatureType.signatureType.code, privateKey) } + } + } + + + companion object { + @JvmStatic + fun get() = SigningOptions() + } + + /** + * A method of signing. + */ + class SigningMethod private constructor( + val signatureGenerator: PGPSignatureGenerator, + val isDetached: Boolean, + val hashAlgorithm: HashAlgorithm + ) { + companion object { + + /** + * Inline-signature method. + * The resulting signature will be written into the message itself, together with a one-pass-signature packet. + * + * @param signatureGenerator signature generator + * @param hashAlgorithm hash algorithm used to generate the signature + * @return inline signing method + */ + @JvmStatic + fun inlineSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) = + SigningMethod(signatureGenerator, false, hashAlgorithm) + + /** + * Detached signing method. + * The resulting signature will not be added to the message, and instead can be distributed separately + * to the signed message. + * + * @param signatureGenerator signature generator + * @param hashAlgorithm hash algorithm used to generate the signature + * @return detached signing method + */ + @JvmStatic + fun detachedSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) = + SigningMethod(signatureGenerator, true, hashAlgorithm) + } + } +} \ No newline at end of file From a4cd96596744db616066a70cdcd94e1052fb3598 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 16:22:51 +0200 Subject: [PATCH 085/155] Kotlin conversion: ProducerOptions --- .../encryption_signing/ProducerOptions.java | 355 ------------------ .../encryption_signing/ProducerOptions.kt | 291 ++++++++++++++ 2 files changed, 291 insertions(+), 355 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/ProducerOptions.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/ProducerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/ProducerOptions.java deleted file mode 100644 index 00fbb10a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/ProducerOptions.java +++ /dev/null @@ -1,355 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.util.Date; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPLiteralData; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.StreamEncoding; - -public final class ProducerOptions { - - private final EncryptionOptions encryptionOptions; - private final SigningOptions signingOptions; - private String fileName = ""; - private Date modificationDate = PGPLiteralData.NOW; - private StreamEncoding encodingField = StreamEncoding.BINARY; - private boolean applyCRLFEncoding = false; - private boolean cleartextSigned = false; - private boolean hideArmorHeaders = false; - - private CompressionAlgorithm compressionAlgorithmOverride = PGPainless.getPolicy().getCompressionAlgorithmPolicy() - .defaultCompressionAlgorithm(); - private boolean asciiArmor = true; - private String comment = null; - private String version = null; - - private ProducerOptions(EncryptionOptions encryptionOptions, SigningOptions signingOptions) { - this.encryptionOptions = encryptionOptions; - this.signingOptions = signingOptions; - } - - /** - * Sign and encrypt some data. - * - * @param encryptionOptions encryption options - * @param signingOptions signing options - * @return builder - */ - public static ProducerOptions signAndEncrypt(EncryptionOptions encryptionOptions, - SigningOptions signingOptions) { - throwIfNull(encryptionOptions); - throwIfNull(signingOptions); - return new ProducerOptions(encryptionOptions, signingOptions); - } - - /** - * Sign some data without encryption. - * - * @param signingOptions signing options - * @return builder - */ - public static ProducerOptions sign(SigningOptions signingOptions) { - throwIfNull(signingOptions); - return new ProducerOptions(null, signingOptions); - } - - /** - * Encrypt some data without signing. - * - * @param encryptionOptions encryption options - * @return builder - */ - public static ProducerOptions encrypt(EncryptionOptions encryptionOptions) { - throwIfNull(encryptionOptions); - return new ProducerOptions(encryptionOptions, null); - } - - /** - * Only wrap the data in an OpenPGP packet. - * No encryption or signing will be applied. - * - * @return builder - */ - public static ProducerOptions noEncryptionNoSigning() { - return new ProducerOptions(null, null); - } - - private static void throwIfNull(EncryptionOptions encryptionOptions) { - if (encryptionOptions == null) { - throw new NullPointerException("EncryptionOptions cannot be null."); - } - } - - private static void throwIfNull(SigningOptions signingOptions) { - if (signingOptions == null) { - throw new NullPointerException("SigningOptions cannot be null."); - } - } - - /** - * Specify, whether the result of the encryption/signing operation shall be ascii armored. - * The default value is true. - * - * @param asciiArmor ascii armor - * @return builder - */ - public ProducerOptions setAsciiArmor(boolean asciiArmor) { - if (cleartextSigned && !asciiArmor) { - throw new IllegalArgumentException("Cleartext signing is enabled. Cannot disable ASCII armoring."); - } - this.asciiArmor = asciiArmor; - return this; - } - - /** - * Return true if the output of the encryption/signing operation shall be ascii armored. - * - * @return ascii armored - */ - public boolean isAsciiArmor() { - return asciiArmor; - } - - /** - * Set the comment header in ASCII armored output. - * The default value is null, which means no comment header is added. - * Multiline comments are possible using '\\n'. - *
- * Note: If a default header comment is set using {@link org.pgpainless.util.ArmoredOutputStreamFactory#setComment(String)}, - * then both comments will be written to the produced ASCII armor. - * - * @param comment comment header text - * @return builder - */ - public ProducerOptions setComment(String comment) { - this.comment = comment; - return this; - } - - /** - * Set the version header in ASCII armored output. - * The default value is null, which means no version header is added. - *
- * Note: If the value is non-null, then this method overrides the default version header set using - * {@link org.pgpainless.util.ArmoredOutputStreamFactory#setVersionInfo(String)}. - * - * @param version version header, or null for no version info. - * @return builder - */ - public ProducerOptions setVersion(String version) { - this.version = version; - return this; - } - - /** - * Return comment set for header in ascii armored output. - * - * @return comment - */ - public String getComment() { - return comment; - } - - /** - * Return the version info header in ascii armored output. - * - * @return version info - */ - public String getVersion() { - return version; - } - - /** - * Return whether a comment was set (!= null). - * - * @return true if commend is set - */ - public boolean hasComment() { - return comment != null; - } - - /** - * Return whether a version header was set (!= null). - * - * @return true if version header is set - */ - public boolean hasVersion() { - return version != null; - } - - public ProducerOptions setCleartextSigned() { - if (signingOptions == null) { - throw new IllegalArgumentException("Signing Options cannot be null if cleartext signing is enabled."); - } - if (encryptionOptions != null) { - throw new IllegalArgumentException("Cannot encode encrypted message as Cleartext Signed."); - } - for (SigningOptions.SigningMethod method : signingOptions.getSigningMethods().values()) { - if (!method.isDetached()) { - throw new IllegalArgumentException("For cleartext signed message, all signatures must be added as detached signatures."); - } - } - cleartextSigned = true; - asciiArmor = true; - compressionAlgorithmOverride = CompressionAlgorithm.UNCOMPRESSED; - return this; - } - - public boolean isCleartextSigned() { - return cleartextSigned; - } - - /** - * Set the name of the encrypted file. - * Note: This option cannot be used simultaneously with {@link #setForYourEyesOnly()}. - * - * @param fileName name of the encrypted file - * @return this - */ - public ProducerOptions setFileName(@Nonnull String fileName) { - this.fileName = fileName; - return this; - } - - /** - * Return the encrypted files name. - * - * @return file name - */ - public String getFileName() { - return fileName; - } - - /** - * Mark the encrypted message as for-your-eyes-only by setting a special file name. - * Note: Therefore this method cannot be used simultaneously with {@link #setFileName(String)}. - * - * @return this - * @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this special filename in - * newly generated literal data packets - */ - @Deprecated - public ProducerOptions setForYourEyesOnly() { - this.fileName = PGPLiteralData.CONSOLE; - return this; - } - - /** - * Set the modification date of the encrypted file. - * - * @param modificationDate Modification date of the encrypted file. - * @return this - */ - public ProducerOptions setModificationDate(@Nonnull Date modificationDate) { - this.modificationDate = modificationDate; - return this; - } - - /** - * Return the modification date of the encrypted file. - * - * @return modification date - */ - public Date getModificationDate() { - return modificationDate; - } - - /** - * Set format metadata field of the literal data packet. - * Defaults to {@link StreamEncoding#BINARY}. - *
- * This does not change the encoding of the wrapped data itself. - * To apply CR/LF encoding to your input data before processing, use {@link #applyCRLFEncoding()} instead. - * - * @see RFC4880 §5.9. Literal Data Packet - * - * @param encoding encoding - * @return this - * - * @deprecated options other than the default value of {@link StreamEncoding#BINARY} are discouraged. - */ - @Deprecated - public ProducerOptions setEncoding(@Nonnull StreamEncoding encoding) { - this.encodingField = encoding; - return this; - } - - public StreamEncoding getEncoding() { - return encodingField; - } - - /** - * Apply special encoding of line endings to the input data. - * By default, this is disabled, which means that the data is not altered. - *
- * Enabling it will change the line endings to CR/LF. - * Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will result in - * the identity "decrypt(encrypt(data)) == data == verify(sign(data))". - * - * @return this - */ - public ProducerOptions applyCRLFEncoding() { - this.applyCRLFEncoding = true; - return this; - } - - /** - * Return the input encoding that will be applied before signing / encryption. - * - * @return input encoding - */ - public boolean isApplyCRLFEncoding() { - return applyCRLFEncoding; - } - - /** - * Override which compression algorithm shall be used. - * - * @param compressionAlgorithm compression algorithm override - * @return builder - */ - public ProducerOptions overrideCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) { - if (compressionAlgorithm == null) { - throw new NullPointerException("Compression algorithm cannot be null."); - } - this.compressionAlgorithmOverride = compressionAlgorithm; - return this; - } - - public CompressionAlgorithm getCompressionAlgorithmOverride() { - return compressionAlgorithmOverride; - } - - public @Nullable EncryptionOptions getEncryptionOptions() { - return encryptionOptions; - } - - public @Nullable SigningOptions getSigningOptions() { - return signingOptions; - } - - public boolean isHideArmorHeaders() { - return hideArmorHeaders; - } - - /** - * If set to

true
, armor headers like version or comments will be omitted from armored output. - * By default, armor headers are not hidden. - * Note: If comments are added via {@link #setComment(String)}, those are not omitted, even if - * {@link #hideArmorHeaders} is set to
true
. - * - * @param hideArmorHeaders true or false - * @return this - */ - public ProducerOptions setHideArmorHeaders(boolean hideArmorHeaders) { - this.hideArmorHeaders = hideArmorHeaders; - return this; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt new file mode 100644 index 00000000..bdc153e9 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt @@ -0,0 +1,291 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.openpgp.PGPLiteralData +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.StreamEncoding +import java.util.* + +class ProducerOptions private constructor( + val encryptionOptions: EncryptionOptions?, + val signingOptions: SigningOptions?) { + + private var _fileName: String = "" + private var _modificationDate: Date = PGPLiteralData.NOW + private var encodingField: StreamEncoding = StreamEncoding.BINARY + private var applyCRLFEncoding = false + private var cleartextSigned = false + private var _hideArmorHeaders = false + + private var _compressionAlgorithmOverride: CompressionAlgorithm = PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm + private var asciiArmor = true + private var _comment: String? = null + private var _version: String? = null + + /** + * Specify, whether the result of the encryption/signing operation shall be ascii armored. + * The default value is true. + * + * @param asciiArmor ascii armor + * @return builder + */ + fun setAsciiArmor(asciiArmor: Boolean) = apply { + require(!(cleartextSigned && !asciiArmor)) { + "Cleartext signing is enabled. Cannot disable ASCII armoring." + } + this.asciiArmor = asciiArmor + } + + /** + * Return true if the output of the encryption/signing operation shall be ascii armored. + * + * @return ascii armored + */ + val isAsciiArmor: Boolean + get() = asciiArmor + + /** + * Set the comment header in ASCII armored output. + * The default value is null, which means no comment header is added. + * Multiline comments are possible using '\\n'. + *
+ * Note: If a default header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment], + * then both comments will be written to the produced ASCII armor. + * + * @param comment comment header text + * @return builder + */ + fun setComment(comment: String?) = apply { + _comment = comment + } + + /** + * Return comment set for header in ascii armored output. + * + * @return comment + */ + val comment: String? + get() = _comment + + /** + * Return whether a comment was set (!= null). + * + * @return true if commend is set + */ + fun hasComment() = _comment != null + + /** + * Set the version header in ASCII armored output. + * The default value is null, which means no version header is added. + *
+ * Note: If the value is non-null, then this method overrides the default version header set using + * [org.pgpainless.util.ArmoredOutputStreamFactory.setVersionInfo]. + * + * @param version version header, or null for no version info. + * @return builder + */ + fun setVersion(version: String?) = apply { + _version = version + } + + /** + * Return the version info header in ascii armored output. + * + * @return version info + */ + val version: String? + get() = _version + + /** + * Return whether a version header was set (!= null). + * + * @return true if version header is set + */ + fun hasVersion() = version != null + + fun setCleartextSigned() = apply { + require(signingOptions != null) { + "Signing Options cannot be null if cleartext signing is enabled." + } + require(encryptionOptions == null) { + "Cannot encode encrypted message as Cleartext Signed." + } + require(signingOptions.signingMethods.values.all { it.isDetached }) { + "For cleartext signed messages, all signatures must be added as detached signatures." + } + + cleartextSigned = true + asciiArmor = true + _compressionAlgorithmOverride = CompressionAlgorithm.UNCOMPRESSED + } + + val isCleartextSigned: Boolean + get() = cleartextSigned + + /** + * Set the name of the encrypted file. + * Note: This option cannot be used simultaneously with [setForYourEyesOnly]. + * + * @param fileName name of the encrypted file + * @return this + */ + fun setFileName(fileName: String) = apply { + _fileName = fileName + } + + /** + * Return the encrypted files name. + * + * @return file name + */ + val fileName: String + get() = _fileName + + /** + * Mark the encrypted message as for-your-eyes-only by setting a special file name. + * Note: Therefore this method cannot be used simultaneously with [setFileName]. + * + * @return this + * @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this special filename in + * newly generated literal data packets + */ + @Deprecated("Signaling using special file name is discouraged.") + fun setForYourEyesOnly() = apply { + _fileName = PGPLiteralData.CONSOLE + } + + /** + * Set the modification date of the encrypted file. + * + * @param modificationDate Modification date of the encrypted file. + * @return this + */ + fun setModificationDate(modificationDate: Date) = apply { + _modificationDate = modificationDate + } + + /** + * Return the modification date of the encrypted file. + * + * @return modification date + */ + val modificationDate: Date + get() = _modificationDate + + /** + * Set format metadata field of the literal data packet. + * Defaults to [StreamEncoding.BINARY]. + *
+ * This does not change the encoding of the wrapped data itself. + * To apply CR/LF encoding to your input data before processing, use [applyCRLFEncoding] instead. + * + * @see RFC4880 §5.9. Literal Data Packet + * + * @param encoding encoding + * @return this + * + * @deprecated options other than the default value of {@link StreamEncoding#BINARY} are discouraged. + */ + @Deprecated("Options other than BINARY are discouraged.") + fun setEncoding(encoding: StreamEncoding) = apply { + encodingField = encoding + } + + val encoding: StreamEncoding + get() = encodingField + + /** + * Apply special encoding of line endings to the input data. + * By default, this is disabled, which means that the data is not altered. + *
+ * Enabling it will change the line endings to CR/LF. + * Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will result in + * the identity "decrypt(encrypt(data)) == data == verify(sign(data))". + * + * @return this + */ + fun applyCRLFEncoding() = apply { + applyCRLFEncoding = true + } + + /** + * Return the input encoding that will be applied before signing / encryption. + * + * @return input encoding + */ + val isApplyCRLFEncoding: Boolean + get() = applyCRLFEncoding + + /** + * Override which compression algorithm shall be used. + * + * @param compressionAlgorithm compression algorithm override + * @return builder + */ + fun overrideCompressionAlgorithm(compressionAlgorithm: CompressionAlgorithm) = apply { + _compressionAlgorithmOverride = compressionAlgorithm + } + + val compressionAlgorithmOverride: CompressionAlgorithm + get() = _compressionAlgorithmOverride + + val isHideArmorHeaders: Boolean + get() = _hideArmorHeaders + + /** + * If set to `true`, armor headers like version or comments will be omitted from armored output. + * By default, armor headers are not hidden. + * Note: If comments are added via [setComment], those are not omitted, even if + * [hideArmorHeaders] is set to `true`. + * + * @param hideArmorHeaders true or false + * @return this + */ + fun setHideArmorHeaders(hideArmorHeaders: Boolean) = apply { + _hideArmorHeaders = hideArmorHeaders + } + + companion object { + /** + * Sign and encrypt some data. + * + * @param encryptionOptions encryption options + * @param signingOptions signing options + * @return builder + */ + @JvmStatic + fun signAndEncrypt(encryptionOptions: EncryptionOptions, signingOptions: SigningOptions) = + ProducerOptions(encryptionOptions, signingOptions) + + /** + * Sign some data without encryption. + * + * @param signingOptions signing options + * @return builder + */ + @JvmStatic + fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions) + + /** + * Encrypt some data without signing. + * + * @param encryptionOptions encryption options + * @return builder + */ + @JvmStatic + fun encrypt(encryptionOptions: EncryptionOptions) = ProducerOptions(encryptionOptions, null) + + /** + * Only wrap the data in an OpenPGP packet. + * No encryption or signing will be applied. + * + * @return builder + */ + @JvmStatic + fun noEncryptionNoSigning() = ProducerOptions(null, null) + } +} \ No newline at end of file From 6a23016104d402241d3701f4a340a41312d2b954 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 16:39:03 +0200 Subject: [PATCH 086/155] Kotlin conversion: EncryptionResult --- .../encryption_signing/EncryptionResult.java | 209 ------------------ .../encryption_signing/EncryptionResult.kt | 103 +++++++++ 2 files changed, 103 insertions(+), 209 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionResult.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionResult.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionResult.java deleted file mode 100644 index 4112092c..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionResult.java +++ /dev/null @@ -1,209 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.StreamEncoding; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.util.MultiMap; - -public final class EncryptionResult { - - private final SymmetricKeyAlgorithm encryptionAlgorithm; - private final CompressionAlgorithm compressionAlgorithm; - - private final MultiMap detachedSignatures; - private final Set recipients; - private final String fileName; - private final Date modificationDate; - private final StreamEncoding fileEncoding; - - private EncryptionResult(SymmetricKeyAlgorithm encryptionAlgorithm, - CompressionAlgorithm compressionAlgorithm, - MultiMap detachedSignatures, - Set recipients, - String fileName, - Date modificationDate, - StreamEncoding encoding) { - this.encryptionAlgorithm = encryptionAlgorithm; - this.compressionAlgorithm = compressionAlgorithm; - this.detachedSignatures = detachedSignatures; - this.recipients = Collections.unmodifiableSet(recipients); - this.fileName = fileName; - this.modificationDate = modificationDate; - this.fileEncoding = encoding; - } - - /** - * Return the symmetric encryption algorithm used to encrypt the message. - * - * @return symmetric encryption algorithm - * */ - public SymmetricKeyAlgorithm getEncryptionAlgorithm() { - return encryptionAlgorithm; - } - - /** - * Return the compression algorithm that was used to compress the message before encryption/signing. - * - * @return compression algorithm - */ - public CompressionAlgorithm getCompressionAlgorithm() { - return compressionAlgorithm; - } - - /** - * Return a {@link MultiMap} of key identifiers and detached signatures that were generated for the message. - * Each key of the map represents a signing key, which has one or more detached signatures associated with it. - * - * @return detached signatures - */ - public MultiMap getDetachedSignatures() { - return detachedSignatures; - } - - /** - * Return the set of recipient encryption keys. - * - * @return recipients - */ - public Set getRecipients() { - return recipients; - } - - /** - * Return the file name of the encrypted/signed data. - * - * @return filename - */ - public String getFileName() { - return fileName; - } - - /** - * Return the modification date of the encrypted/signed file. - * - * @return modification date - */ - public Date getModificationDate() { - return modificationDate; - } - - /** - * Return the encoding format of the encrypted/signed data. - * - * @return encoding format - */ - public StreamEncoding getFileEncoding() { - return fileEncoding; - } - - /** - * Return true, if the message is marked as for-your-eyes-only. - * This is typically done by setting the filename "_CONSOLE". - * - * @return is message for your eyes only? - */ - public boolean isForYourEyesOnly() { - return PGPLiteralData.CONSOLE.equals(getFileName()); - } - - /** - * Returns true, if the message was encrypted for at least one subkey of the given certificate. - * - * @param certificate certificate - * @return true if encrypted for 1+ subkeys, false otherwise. - */ - public boolean isEncryptedFor(PGPPublicKeyRing certificate) { - for (SubkeyIdentifier recipient : recipients) { - if (certificate.getPublicKey().getKeyID() != recipient.getPrimaryKeyId()) { - continue; - } - - if (certificate.getPublicKey(recipient.getSubkeyId()) != null) { - return true; - } - } - return false; - } - - /** - * Create a builder for the encryption result class. - * - * @return builder - */ - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private SymmetricKeyAlgorithm encryptionAlgorithm; - private CompressionAlgorithm compressionAlgorithm; - - private final MultiMap detachedSignatures = new MultiMap<>(); - private final Set recipients = new HashSet<>(); - private String fileName = ""; - private Date modificationDate = new Date(0L); // NOW - private StreamEncoding encoding = StreamEncoding.BINARY; - - public Builder setEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) { - this.encryptionAlgorithm = encryptionAlgorithm; - return this; - } - - public Builder setCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) { - this.compressionAlgorithm = compressionAlgorithm; - return this; - } - - public Builder addRecipient(SubkeyIdentifier recipient) { - this.recipients.add(recipient); - return this; - } - - public Builder addDetachedSignature(SubkeyIdentifier signingSubkeyIdentifier, PGPSignature detachedSignature) { - this.detachedSignatures.put(signingSubkeyIdentifier, detachedSignature); - return this; - } - - public Builder setFileName(@Nonnull String fileName) { - this.fileName = fileName; - return this; - } - - public Builder setModificationDate(@Nonnull Date modificationDate) { - this.modificationDate = modificationDate; - return this; - } - - public Builder setFileEncoding(StreamEncoding fileEncoding) { - this.encoding = fileEncoding; - return this; - } - - public EncryptionResult build() { - if (encryptionAlgorithm == null) { - throw new IllegalStateException("Encryption algorithm not set."); - } - if (compressionAlgorithm == null) { - throw new IllegalStateException("Compression algorithm not set."); - } - - return new EncryptionResult(encryptionAlgorithm, compressionAlgorithm, detachedSignatures, recipients, - fileName, modificationDate, encoding); - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt new file mode 100644 index 00000000..fd9febc7 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.openpgp.PGPLiteralData +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.StreamEncoding +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.util.MultiMap +import java.util.* + +data class EncryptionResult( + val encryptionAlgorithm: SymmetricKeyAlgorithm, + val compressionAlgorithm: CompressionAlgorithm, + val detachedSignatures: MultiMap, + val recipients: Set, + val fileName: String, + val modificationDate: Date, + val fileEncoding: StreamEncoding +) { + + /** + * Return true, if the message is marked as for-your-eyes-only. + * This is typically done by setting the filename "_CONSOLE". + * + * @return is message for your eyes only? + */ + val isForYourEyesOnly: Boolean + get() = PGPLiteralData.CONSOLE == fileName + + /** + * Returns true, if the message was encrypted for at least one subkey of the given certificate. + * + * @param certificate certificate + * @return true if encrypted for 1+ subkeys, false otherwise. + */ + fun isEncryptedFor(certificate: PGPPublicKeyRing) = recipients.any { + certificate.publicKey.keyID == it.primaryKeyId && + certificate.getPublicKey(it.subkeyId) != null + } + + companion object { + /** + * Create a builder for the encryption result class. + * + * @return builder + */ + @JvmStatic + fun builder() = Builder() + } + + class Builder { + var _encryptionAlgorithm: SymmetricKeyAlgorithm? = null + var _compressionAlgorithm: CompressionAlgorithm? = null + + val detachedSignatures: MultiMap = MultiMap() + val recipients: Set = mutableSetOf() + private var _fileName = "" + private var _modificationDate = Date(0) + private var _encoding = StreamEncoding.BINARY + + fun setEncryptionAlgorithm(encryptionAlgorithm: SymmetricKeyAlgorithm) = apply { + _encryptionAlgorithm = encryptionAlgorithm + } + + fun setCompressionAlgorithm(compressionAlgorithm: CompressionAlgorithm) = apply { + _compressionAlgorithm = compressionAlgorithm + } + + fun setFileName(fileName: String) = apply { + _fileName = fileName + } + + fun setModificationDate(modificationDate: Date) = apply { + _modificationDate = modificationDate + } + + fun setFileEncoding(encoding: StreamEncoding) = apply { + _encoding = encoding + } + + fun addRecipient(recipient: SubkeyIdentifier) = apply { + (recipients as MutableSet).add(recipient) + } + + fun addDetachedSignature(signingSubkeyIdentifier: SubkeyIdentifier, detachedSignature: PGPSignature) = apply { + detachedSignatures.put(signingSubkeyIdentifier, detachedSignature) + } + + fun build(): EncryptionResult { + checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." } + checkNotNull(_compressionAlgorithm) { "Compression algorithm not set." } + + return EncryptionResult(_encryptionAlgorithm!!, _compressionAlgorithm!!, detachedSignatures, recipients, + _fileName, _modificationDate, _encoding) + } + } +} \ No newline at end of file From e8fef1f1f3c79ba1466b2c50b728abd16a15d5b5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 6 Sep 2023 16:49:28 +0200 Subject: [PATCH 087/155] Add PGPKeyRingExtensions class and make use of it --- .../extensions/PGPKeyRingExtensions.kt | 15 +++++++ .../MessageMetadata.kt | 40 ++++++++----------- .../encryption_signing/EncryptionResult.kt | 6 +-- .../org/pgpainless/key/SubkeyIdentifier.kt | 3 ++ 4 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt new file mode 100644 index 00000000..43669bd1 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle.extensions + +import org.bouncycastle.openpgp.PGPKeyRing +import org.pgpainless.key.SubkeyIdentifier + +/** + * Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. + */ +fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean = + this.publicKey.keyID == subkeyIdentifier.primaryKeyId && + this.getPublicKey(subkeyIdentifier.subkeyId) != null \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt index 8719cef7..3eb49e5d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt @@ -4,6 +4,7 @@ package org.pgpainless.decryption_verification +import org.bouncycastle.extensions.matches import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPLiteralData import org.pgpainless.algorithm.CompressionAlgorithm @@ -227,10 +228,9 @@ class MessageMetadata( */ @JvmOverloads fun isAuthenticatablySignedBy(userId: String, email: Boolean, certificateAuthority: CertificateAuthority, targetAmount: Int = 120): Boolean { - return verifiedSignatures.any { - certificateAuthority.authenticateBinding( - it.signingKey.fingerprint, userId, email, it.signature.creationTime, targetAmount - ).authenticated + return verifiedSignatures.any { certificateAuthority + .authenticateBinding(it.signingKey.fingerprint, userId, email, it.signature.creationTime, targetAmount) + .authenticated } } @@ -241,31 +241,23 @@ class MessageMetadata( * @param fingerprint fingerprint * @return true if message was signed by a cert identified by the given fingerprint */ - fun isVerifiedSignedBy(fingerprint: OpenPgpFingerprint) = verifiedSignatures.any { - it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint - } + fun isVerifiedSignedBy(fingerprint: OpenPgpFingerprint) = + verifiedSignatures.any { it.signingKey.matches(fingerprint) } - fun isVerifiedSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedSignatures, keys) + fun isVerifiedSignedBy(keys: PGPKeyRing) = + verifiedSignatures.any { keys.matches(it.signingKey) } - fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) = verifiedDetachedSignatures.any { - it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint - } + fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) = + verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) } - fun isVerifiedDetachedSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedDetachedSignatures, keys) + fun isVerifiedDetachedSignedBy(keys: PGPKeyRing) = + verifiedDetachedSignatures.any { keys.matches(it.signingKey) } - fun isVerifiedInlineSignedBy(fingerprint: OpenPgpFingerprint) = verifiedInlineSignatures.any { - it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint - } + fun isVerifiedInlineSignedBy(fingerprint: OpenPgpFingerprint) = + verifiedInlineSignatures.any { it.signingKey.matches(fingerprint) } - fun isVerifiedInlineSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedInlineSignatures, keys) - - private fun containsSignatureBy(signatures: List, keys: PGPKeyRing) = - signatures.any { - // Match certificate by primary key id - keys.publicKey.keyID == it.signingKey.primaryKeyId && - // match signing subkey - keys.getPublicKey(it.signingKey.subkeyId) != null - } + fun isVerifiedInlineSignedBy(keys: PGPKeyRing) = + verifiedInlineSignatures.any { keys.matches(it.signingKey) } // ################################################################################################################ // ### Literal Data ### diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt index fd9febc7..0e9a40c9 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt @@ -4,6 +4,7 @@ package org.pgpainless.encryption_signing +import org.bouncycastle.extensions.matches import org.bouncycastle.openpgp.PGPLiteralData import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSignature @@ -39,10 +40,7 @@ data class EncryptionResult( * @param certificate certificate * @return true if encrypted for 1+ subkeys, false otherwise. */ - fun isEncryptedFor(certificate: PGPPublicKeyRing) = recipients.any { - certificate.publicKey.keyID == it.primaryKeyId && - certificate.getPublicKey(it.subkeyId) != null - } + fun isEncryptedFor(certificate: PGPPublicKeyRing) = recipients.any { certificate.matches(it) } companion object { /** diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt index ea36913c..58e7719c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt @@ -31,6 +31,9 @@ class SubkeyIdentifier( val subkeyId = subkeyFingerprint.keyId val primaryKeyId = primaryKeyFingerprint.keyId + fun matches(fingerprint: OpenPgpFingerprint) = + primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint + override fun equals(other: Any?): Boolean { if (other == null) { return false From 0b071ff8e1f70be1a0f0f532c72d2b3fb7aff242 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 14:29:17 +0200 Subject: [PATCH 088/155] Kotlin conversion: CachingBcPublicKeyDataDecryptorFactory --- ...achingBcPublicKeyDataDecryptorFactory.java | 95 ------------------- .../java/org/bouncycastle/package-info.java | 8 -- .../CachingBcPublicKeyDataDecryptorFactory.kt | 50 ++++++++++ 3 files changed, 50 insertions(+), 103 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.java delete mode 100644 pgpainless-core/src/main/java/org/bouncycastle/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt diff --git a/pgpainless-core/src/main/java/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.java b/pgpainless-core/src/main/java/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.java deleted file mode 100644 index 510b0938..00000000 --- a/pgpainless-core/src/main/java/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.bouncycastle; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; -import org.bouncycastle.util.encoders.Base64; -import org.bouncycastle.util.encoders.Hex; -import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactory; -import org.pgpainless.key.SubkeyIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; -import java.util.Map; - -/** - * Implementation of the {@link PublicKeyDataDecryptorFactory} which caches decrypted session keys. - * That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted. - * - * This implementation changes the behavior or {@link #recoverSessionData(int, byte[][])} to first return any - * cache hits. - * If no hit is found, the method call is delegated to the underlying {@link PublicKeyDataDecryptorFactory}. - * The result of that is then placed in the cache and returned. - */ -public class CachingBcPublicKeyDataDecryptorFactory - extends BcPublicKeyDataDecryptorFactory - implements CustomPublicKeyDataDecryptorFactory { - - private static final Logger LOGGER = LoggerFactory.getLogger(CachingBcPublicKeyDataDecryptorFactory.class); - - private final Map cachedSessionKeys = new HashMap<>(); - private final SubkeyIdentifier decryptionKey; - - public CachingBcPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey, SubkeyIdentifier decryptionKey) { - super(privateKey); - this.decryptionKey = decryptionKey; - } - - @Override - public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException { - byte[] sessionKey = lookupSessionKeyData(secKeyData); - if (sessionKey == null) { - LOGGER.debug("Cache miss for encrypted session key " + Hex.toHexString(secKeyData[0])); - sessionKey = costlyRecoverSessionData(keyAlgorithm, secKeyData); - cacheSessionKeyData(secKeyData, sessionKey); - } else { - LOGGER.debug("Cache hit for encrypted session key " + Hex.toHexString(secKeyData[0])); - } - return sessionKey; - } - - public byte[] costlyRecoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException { - return super.recoverSessionData(keyAlgorithm, secKeyData); - } - - private byte[] lookupSessionKeyData(byte[][] secKeyData) { - String key = toKey(secKeyData); - byte[] sessionKey = cachedSessionKeys.get(key); - return copy(sessionKey); - } - - private void cacheSessionKeyData(byte[][] secKeyData, byte[] sessionKey) { - String key = toKey(secKeyData); - cachedSessionKeys.put(key, copy(sessionKey)); - } - - private static String toKey(byte[][] secKeyData) { - byte[] sk = secKeyData[0]; - String key = Base64.toBase64String(sk); - return key; - } - - private static byte[] copy(byte[] bytes) { - if (bytes == null) { - return null; - } - byte[] copy = new byte[bytes.length]; - System.arraycopy(bytes, 0, copy, 0, copy.length); - return copy; - } - - public void clear() { - cachedSessionKeys.clear(); - } - - @Override - public SubkeyIdentifier getSubkeyIdentifier() { - return decryptionKey; - } -} diff --git a/pgpainless-core/src/main/java/org/bouncycastle/package-info.java b/pgpainless-core/src/main/java/org/bouncycastle/package-info.java deleted file mode 100644 index 565bb5f4..00000000 --- a/pgpainless-core/src/main/java/org/bouncycastle/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes which could be upstreamed to BC at some point. - */ -package org.bouncycastle; diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt new file mode 100644 index 00000000..60b860f2 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle + +import org.bouncycastle.openpgp.PGPPrivateKey +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory +import org.bouncycastle.util.encoders.Base64 +import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactory +import org.pgpainless.key.SubkeyIdentifier + +/** + * Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys. + * That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted. + * + * This implementation changes the behavior or [recoverSessionData] to first return any + * cache hits. + * If no hit is found, the method call is delegated to the underlying [PublicKeyDataDecryptorFactory]. + * The result of that is then placed in the cache and returned. + */ +class CachingBcPublicKeyDataDecryptorFactory( + privateKey: PGPPrivateKey, + override val subkeyIdentifier: SubkeyIdentifier +) : BcPublicKeyDataDecryptorFactory(privateKey), CustomPublicKeyDataDecryptorFactory { + + private val cachedSessions: MutableMap = mutableMapOf() + + override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array): ByteArray = + lookupSessionKeyData(secKeyData) ?: + costlyRecoverSessionData(keyAlgorithm, secKeyData) + .also { cacheSessionKeyData(secKeyData, it) } + + private fun lookupSessionKeyData(secKeyData: Array): ByteArray? = + cachedSessions[toKey(secKeyData)]?.clone() + + private fun costlyRecoverSessionData(keyAlgorithm: Int, secKeyData: Array): ByteArray = + super.recoverSessionData(keyAlgorithm, secKeyData) + + private fun cacheSessionKeyData(secKeyData: Array, sessionKey: ByteArray) { + cachedSessions[toKey(secKeyData)] = sessionKey.clone() + } + + private fun toKey(secKeyData: Array): String = + Base64.toBase64String(secKeyData[0]) + + fun clear() { + cachedSessions.clear() + } +} \ No newline at end of file From 1ebf8e1e6fa9c9f9664ef0f742b8a52d1b188253 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 14:32:49 +0200 Subject: [PATCH 089/155] Kotlin conversion: KeyLength --- .../org/pgpainless/key/generation/type/KeyLength.java | 10 ---------- .../org/pgpainless/key/generation/type/KeyLength.kt | 10 ++++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyLength.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyLength.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyLength.java deleted file mode 100644 index 1cadfef8..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyLength.java +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type; - -public interface KeyLength { - - int getLength(); -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt new file mode 100644 index 00000000..3f806987 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type + +interface KeyLength { + + fun getLength(): Int +} \ No newline at end of file From 472d5c4bebd9020267f514a2f8bfc748d625de65 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 14:46:56 +0200 Subject: [PATCH 090/155] Kotlin conversion: KeyType --- .../key/generation/type/KeyType.java | 118 ------------------ .../pgpainless/key/generation/type/KeyType.kt | 110 ++++++++++++++++ 2 files changed, 110 insertions(+), 118 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyType.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyType.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyType.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyType.java deleted file mode 100644 index 191a22f7..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyType.java +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type; - -import java.security.spec.AlgorithmParameterSpec; - -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.ecc.EllipticCurve; -import org.pgpainless.key.generation.type.ecc.ecdh.ECDH; -import org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA; -import org.pgpainless.key.generation.type.eddsa.EdDSA; -import org.pgpainless.key.generation.type.eddsa.EdDSACurve; -import org.pgpainless.key.generation.type.rsa.RsaLength; -import org.pgpainless.key.generation.type.rsa.RSA; -import org.pgpainless.key.generation.type.xdh.XDH; -import org.pgpainless.key.generation.type.xdh.XDHSpec; - -public interface KeyType { - - /** - * Return the encryption algorithm name. - * - * @return algorithm name. - */ - String getName(); - - /** - * Return the public key algorithm. - * - * @return public key algorithm - */ - PublicKeyAlgorithm getAlgorithm(); - - /** - * Return the strength of the key in bits. - * @return strength of the key in bits - */ - int getBitStrength(); - - /** - * Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the key. - * - * @return algorithm parameter spec - */ - AlgorithmParameterSpec getAlgorithmSpec(); - - /** - * Return true if the key that is generated from this type is able to carry the SIGN_DATA key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}. - * - * @return true if the key can sign. - */ - default boolean canSign() { - return getAlgorithm().isSigningCapable(); - } - - /** - * Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}. - * - * @return true if the key is able to certify other keys - */ - default boolean canCertify() { - return canSign(); - } - - /** - * Return true if the key that is generated from this type is able to carry the AUTHENTICATION key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}. - * - * @return true if the key can be used for authentication purposes. - */ - default boolean canAuthenticate() { - return canSign(); - } - - /** - * Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}. - * - * @return true if the key can encrypt communication - */ - default boolean canEncryptCommunication() { - return getAlgorithm().isEncryptionCapable(); - } - - /** - * Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}. - * - * @return true if the key can encrypt for storage - */ - default boolean canEncryptStorage() { - return getAlgorithm().isEncryptionCapable(); - } - - static KeyType RSA(RsaLength length) { - return RSA.withLength(length); - } - - static KeyType ECDH(EllipticCurve curve) { - return ECDH.fromCurve(curve); - } - - static KeyType ECDSA(EllipticCurve curve) { - return ECDSA.fromCurve(curve); - } - - static KeyType EDDSA(EdDSACurve curve) { - return EdDSA.fromCurve(curve); - } - - static KeyType XDH(XDHSpec curve) { - return XDH.fromSpec(curve); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyType.kt new file mode 100644 index 00000000..105962b9 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyType.kt @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type + +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.ecc.EllipticCurve +import org.pgpainless.key.generation.type.ecc.ecdh.ECDH +import org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA +import org.pgpainless.key.generation.type.eddsa.EdDSA +import org.pgpainless.key.generation.type.eddsa.EdDSACurve +import org.pgpainless.key.generation.type.rsa.RSA +import org.pgpainless.key.generation.type.rsa.RsaLength +import org.pgpainless.key.generation.type.xdh.XDH +import org.pgpainless.key.generation.type.xdh.XDHSpec +import java.security.spec.AlgorithmParameterSpec + +@Suppress("INAPPLICABLE_JVM_NAME") // https://youtrack.jetbrains.com/issue/KT-31420 +interface KeyType { + + /** + * Return the encryption algorithm name. + * + * @return algorithm name. + */ + val name: String + + /** + * Return the public key algorithm. + * + * @return public key algorithm + */ + val algorithm: PublicKeyAlgorithm + + /** + * Return the strength of the key in bits. + * @return strength of the key in bits + */ + val bitStrength: Int + + /** + * Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the key. + * + * @return algorithm parameter spec + */ + val algorithmSpec: AlgorithmParameterSpec + + /** + * Return true if the key that is generated from this type is able to carry the SIGN_DATA key flag. + * See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}. + * + * @return true if the key can sign. + */ + val canSign: Boolean + @JvmName("canSign") get() = algorithm.signingCapable + + /** + * Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag. + * See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}. + * + * @return true if the key is able to certify other keys + */ + val canCertify: Boolean + @JvmName("canCertify") get() = canSign + + /** + * Return true if the key that is generated from this type is able to carry the AUTHENTICATION key flag. + * See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}. + * + * @return true if the key can be used for authentication purposes. + */ + val canAuthenticate: Boolean + @JvmName("canAuthenticate") get() = canSign + + /** + * Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS key flag. + * See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}. + * + * @return true if the key can encrypt communication + */ + val canEncryptCommunication: Boolean + @JvmName("canEncryptCommunication") get() = algorithm.encryptionCapable + + /** + * Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag. + * See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}. + * + * @return true if the key can encrypt for storage + */ + val canEncryptStorage: Boolean + @JvmName("canEncryptStorage") get() = algorithm.encryptionCapable + + companion object { + @JvmStatic + fun RSA(length: RsaLength): RSA = RSA.withLength(length) + + @JvmStatic + fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve) + + @JvmStatic + fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve) + + @JvmStatic + fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve) + + @JvmStatic + fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve) + } +} \ No newline at end of file From b3f4ba052a06b3ffcf6ff96bc9e1de9dfc1e8c7f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 14:47:13 +0200 Subject: [PATCH 091/155] Remove whitespace --- .../main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt index 3f806987..12e39b08 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt @@ -5,6 +5,6 @@ package org.pgpainless.key.generation.type interface KeyLength { - + fun getLength(): Int } \ No newline at end of file From 13082215d6593fffc3e040bd32c09c31544685b6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Oct 2023 12:45:37 +0200 Subject: [PATCH 092/155] Fix property access --- .../main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt index c101e2c0..2e4a6642 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -82,7 +82,7 @@ class KeyRingBuilder : KeyRingBuilderInterface { } } - private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify() + private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify override fun build(): PGPSecretKeyRing { val keyFingerprintCalculator = ImplementationFactory.getInstance() From 7f96272152de73d587c999dfe5996437f1659b32 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 14:56:42 +0200 Subject: [PATCH 093/155] Kotlin conversion: EllipticCurve --- .../generation/type/ecc/EllipticCurve.java | 42 ------------------- .../key/generation/type/ecc/EllipticCurve.kt | 27 ++++++++++++ 2 files changed, 27 insertions(+), 42 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/EllipticCurve.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/EllipticCurve.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/EllipticCurve.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/EllipticCurve.java deleted file mode 100644 index 2372896e..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/EllipticCurve.java +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.ecc; - -import javax.annotation.Nonnull; - -import org.pgpainless.key.generation.type.xdh.XDHSpec; - -/** - * Elliptic curves for use with - * {@link org.pgpainless.key.generation.type.ecc.ecdh.ECDH}/{@link org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA}. - * For curve25519 related curve definitions see - * {@link XDHSpec} and {@link org.pgpainless.key.generation.type.eddsa.EdDSACurve}. - */ -public enum EllipticCurve { - _P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see https://tools.ietf.org/search/rfc4492#page-32 - _P384("secp384r1", 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32 - _P521("secp521r1", 521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32 - _SECP256K1("secp256k1", 256), - _BRAINPOOLP256R1("brainpoolP256r1", 256), - _BRAINPOOLP384R1("brainpoolP384r1", 384), - _BRAINPOOLP512R1("brainpoolP512r1", 512) - ; - - private final String name; - private final int bitStrength; - - EllipticCurve(@Nonnull String name, int bitStrength) { - this.name = name; - this.bitStrength = bitStrength; - } - - public String getName() { - return name; - } - - public int getBitStrength() { - return bitStrength; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/EllipticCurve.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/EllipticCurve.kt new file mode 100644 index 00000000..287df67f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/EllipticCurve.kt @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.ecc + + +/** + * Elliptic curves for use with [org.pgpainless.key.generation.type.ecc.ecdh.ECDH] and + * [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA]. + * For curve25519 related curve definitions see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve]. + */ +enum class EllipticCurve( + val curveName: String, + val bitStrength: Int +) { + _P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see https://tools.ietf.org/search/rfc4492#page-32 + _P384("secp384r1", 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32 + _P521("secp521r1", 521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32 + _SECP256K1("secp256k1", 256), + _BRAINPOOLP256R1("brainpoolP256r1", 256), + _BRAINPOOLP384R1("brainpoolP384r1", 384), + _BRAINPOOLP512R1("brainpoolP512r1", 512), + ; + + fun getName(): String = curveName +} \ No newline at end of file From 9e7a25ffe19a1c62de8287377f288386a74b6eeb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:00:54 +0200 Subject: [PATCH 094/155] Kotlin conversion: ECDH --- .../key/generation/type/ecc/ecdh/ECDH.java | 46 ------------------- .../type/ecc/ecdh/package-info.java | 8 ---- .../key/generation/type/ecc/ecdh/ECDH.kt | 22 +++++++++ 3 files changed, 22 insertions(+), 54 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.java deleted file mode 100644 index bb7e3f3c..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.java +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.ecc.ecdh; - -import javax.annotation.Nonnull; -import java.security.spec.AlgorithmParameterSpec; - -import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.KeyType; -import org.pgpainless.key.generation.type.ecc.EllipticCurve; - -public final class ECDH implements KeyType { - - private final EllipticCurve curve; - - private ECDH(EllipticCurve curve) { - this.curve = curve; - } - - public static ECDH fromCurve(@Nonnull EllipticCurve curve) { - return new ECDH(curve); - } - - @Override - public String getName() { - return "ECDH"; - } - - @Override - public PublicKeyAlgorithm getAlgorithm() { - return PublicKeyAlgorithm.ECDH; - } - - @Override - public int getBitStrength() { - return curve.getBitStrength(); - } - - @Override - public AlgorithmParameterSpec getAlgorithmSpec() { - return new ECNamedCurveGenParameterSpec(curve.getName()); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/package-info.java deleted file mode 100644 index b1f2c882..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to ECDH. - */ -package org.pgpainless.key.generation.type.ecc.ecdh; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.kt new file mode 100644 index 00000000..6bab2fcc --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.kt @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.ecc.ecdh + +import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.key.generation.type.ecc.EllipticCurve + +class ECDH private constructor(val curve: EllipticCurve) : KeyType { + override val name = "ECDH" + override val algorithm = PublicKeyAlgorithm.ECDH + override val bitStrength = curve.bitStrength + override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) + + companion object { + @JvmStatic + fun fromCurve(curve: EllipticCurve) = ECDH(curve) + } +} \ No newline at end of file From 89b73895f5ceb450a86cfa47a279127ba7806848 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:03:06 +0200 Subject: [PATCH 095/155] Kotlin conversion: ECDSA --- .../key/generation/type/ecc/ecdsa/ECDSA.java | 48 ------------------- .../type/ecc/ecdsa/package-info.java | 8 ---- .../key/generation/type/ecc/package-info.java | 8 ---- .../key/generation/type/ecc/ecdsa/ECDSA.kt | 22 +++++++++ 4 files changed, 22 insertions(+), 64 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/package-info.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.java deleted file mode 100644 index 87301655..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.java +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.ecc.ecdsa; - - -import java.security.spec.AlgorithmParameterSpec; -import javax.annotation.Nonnull; - -import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.ecc.EllipticCurve; -import org.pgpainless.key.generation.type.KeyType; - -public final class ECDSA implements KeyType { - - private final EllipticCurve curve; - - private ECDSA(@Nonnull EllipticCurve curve) { - this.curve = curve; - } - - public static ECDSA fromCurve(@Nonnull EllipticCurve curve) { - return new ECDSA(curve); - } - - @Override - public String getName() { - return "ECDSA"; - } - - @Override - public PublicKeyAlgorithm getAlgorithm() { - return PublicKeyAlgorithm.ECDSA; - } - - @Override - public int getBitStrength() { - return curve.getBitStrength(); - } - - @Override - public AlgorithmParameterSpec getAlgorithmSpec() { - return new ECNamedCurveGenParameterSpec(curve.getName()); - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/package-info.java deleted file mode 100644 index 9b8ca577..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to ECDSA. - */ -package org.pgpainless.key.generation.type.ecc.ecdsa; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/package-info.java deleted file mode 100644 index d55a487a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes describing different OpenPGP key types based on elliptic curves. - */ -package org.pgpainless.key.generation.type.ecc; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.kt new file mode 100644 index 00000000..b7a0b94f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.kt @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.ecc.ecdsa + +import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.key.generation.type.ecc.EllipticCurve + +class ECDSA private constructor(val curve: EllipticCurve) : KeyType { + override val name = "ECDSA" + override val algorithm = PublicKeyAlgorithm.ECDSA + override val bitStrength = curve.bitStrength + override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) + + companion object { + @JvmStatic + fun fromCurve(curve: EllipticCurve) = ECDSA(curve) + } +} \ No newline at end of file From 8f49b01d510608e4f50fe501e6d1a6871cd5d7cc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:08:10 +0200 Subject: [PATCH 096/155] Kotlin conversion: EdDSACurve --- .../key/generation/type/eddsa/EdDSACurve.java | 28 ------------------- .../key/generation/type/eddsa/EdDSACurve.kt | 14 ++++++++++ 2 files changed, 14 insertions(+), 28 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSACurve.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSACurve.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSACurve.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSACurve.java deleted file mode 100644 index 4d5aed1c..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSACurve.java +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.eddsa; - -import javax.annotation.Nonnull; - -public enum EdDSACurve { - _Ed25519("ed25519", 256), - ; - - final String name; - final int bitStrength; - - EdDSACurve(@Nonnull String curveName, int bitStrength) { - this.name = curveName; - this.bitStrength = bitStrength; - } - - public String getName() { - return name; - } - - public int getBitStrength() { - return bitStrength; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSACurve.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSACurve.kt new file mode 100644 index 00000000..52b6949b --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSACurve.kt @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.eddsa + +enum class EdDSACurve( + val curveName: String, + val bitStrength: Int) { + _Ed25519("ed25519", 256), + ; + + fun getName() = curveName +} \ No newline at end of file From 4382c1f20e342b6c304dc559e0f2e4fd35116e14 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:08:51 +0200 Subject: [PATCH 097/155] Kotlin conversion: EdDSA --- .../key/generation/type/eddsa/EdDSA.java | 50 ------------------- .../generation/type/eddsa/package-info.java | 8 --- .../key/generation/type/eddsa/EdDSA.kt | 21 ++++++++ 3 files changed, 21 insertions(+), 58 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSA.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSA.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSA.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSA.java deleted file mode 100644 index 2db57f57..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSA.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.eddsa; - -import java.security.spec.AlgorithmParameterSpec; - -import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.KeyType; - -/** - * Edwards-curve Digital Signature Algorithm (EdDSA). - * - * @see EdDSA for OpenPGP - */ -public final class EdDSA implements KeyType { - - private final EdDSACurve curve; - - private EdDSA(EdDSACurve curve) { - this.curve = curve; - } - - public static EdDSA fromCurve(EdDSACurve curve) { - return new EdDSA(curve); - } - - @Override - public String getName() { - return "EdDSA"; - } - - @Override - public PublicKeyAlgorithm getAlgorithm() { - return PublicKeyAlgorithm.EDDSA; - } - - @Override - public int getBitStrength() { - return curve.getBitStrength(); - } - - @Override - public AlgorithmParameterSpec getAlgorithmSpec() { - return new ECNamedCurveGenParameterSpec(curve.getName()); - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/package-info.java deleted file mode 100644 index cba16f54..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to EdDSA. - */ -package org.pgpainless.key.generation.type.eddsa; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSA.kt new file mode 100644 index 00000000..d1e51a8e --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSA.kt @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.eddsa + +import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.KeyType + +class EdDSA private constructor(val curve: EdDSACurve) : KeyType { + override val name = "EdDSA" + override val algorithm = PublicKeyAlgorithm.EDDSA + override val bitStrength = curve.bitStrength + override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) + + companion object { + @JvmStatic + fun fromCurve(curve: EdDSACurve) = EdDSA(curve) + } +} \ No newline at end of file From f8abb28a81e7163ff819a0418b5622e5d1e47fce Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:14:00 +0200 Subject: [PATCH 098/155] Turn KeyLength method into val --- .../main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt index 12e39b08..377fbb94 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt @@ -6,5 +6,5 @@ package org.pgpainless.key.generation.type interface KeyLength { - fun getLength(): Int + val length: Int } \ No newline at end of file From 72147b685e10e1ec6d6835996cf35902d19ade5b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:14:30 +0200 Subject: [PATCH 099/155] Kotlin conversion: ElGamalLength --- .../generation/type/elgamal/ElGamalLength.kt} | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) rename pgpainless-core/src/main/{java/org/pgpainless/key/generation/type/elgamal/ElGamalLength.java => kotlin/org/pgpainless/key/generation/type/elgamal/ElGamalLength.kt} (91%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamalLength.java b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamalLength.kt similarity index 91% rename from pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamalLength.java rename to pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamalLength.kt index 17e79131..9eae195c 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamalLength.java +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamalLength.kt @@ -1,12 +1,11 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub +// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.key.generation.type.elgamal; +package org.pgpainless.key.generation.type.elgamal -import java.math.BigInteger; - -import org.pgpainless.key.generation.type.KeyLength; +import org.pgpainless.key.generation.type.KeyLength +import java.math.BigInteger /** * The following primes are taken from RFC-3526. @@ -16,8 +15,13 @@ import org.pgpainless.key.generation.type.KeyLength; * * @deprecated the use of ElGamal keys is no longer recommended. */ -@Deprecated -public enum ElGamalLength implements KeyLength { + +@Deprecated("The use of ElGamal keys is no longer recommended.") +enum class ElGamalLength( + override val length: Int, + p: String, + g: String +) : KeyLength { /** * prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }. @@ -56,26 +60,13 @@ public enum ElGamalLength implements KeyLength { _8192(8192, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF", "2") ; - private final int length; - private final BigInteger p; - private final BigInteger g; + val p: BigInteger + val g: BigInteger - ElGamalLength(int length, String p, String g) { - this.length = length; - this.p = new BigInteger(p, 16); - this.g = new BigInteger(g, 16); + init { + this.p = BigInteger(p, 16) + this.g = BigInteger(g, 16) } - @Override - public int getLength() { - return length; - } - public BigInteger getP() { - return p; - } - - public BigInteger getG() { - return g; - } -} +} \ No newline at end of file From 2d755be10e4dc180ddddbc1999a82f65fa674cb2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:18:06 +0200 Subject: [PATCH 100/155] Kotlin conversion: ElGamal --- .../key/generation/type/elgamal/ElGamal.java | 52 ------------------- .../generation/type/elgamal/package-info.java | 8 --- .../key/generation/type/elgamal/ElGamal.kt | 28 ++++++++++ 3 files changed, 28 insertions(+), 60 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamal.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamal.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamal.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamal.java deleted file mode 100644 index 23c33d3d..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamal.java +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.elgamal; - -import java.security.spec.AlgorithmParameterSpec; -import javax.annotation.Nonnull; - -import org.bouncycastle.jce.spec.ElGamalParameterSpec; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.KeyType; - -/** - * ElGamal encryption only key type. - * - * @deprecated the use of ElGamal is not recommended anymore. - */ -@Deprecated -public final class ElGamal implements KeyType { - - private final ElGamalLength length; - - private ElGamal(@Nonnull ElGamalLength length) { - this.length = length; - } - - public static ElGamal withLength(ElGamalLength length) { - return new ElGamal(length); - } - - @Override - public String getName() { - return "ElGamal"; - } - - @Override - public PublicKeyAlgorithm getAlgorithm() { - return PublicKeyAlgorithm.ELGAMAL_ENCRYPT; - } - - @Override - public int getBitStrength() { - return length.getLength(); - } - - @Override - public AlgorithmParameterSpec getAlgorithmSpec() { - return new ElGamalParameterSpec(length.getP(), length.getG()); - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/package-info.java deleted file mode 100644 index 19bc0214..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to ElGamal. - */ -package org.pgpainless.key.generation.type.elgamal; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamal.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamal.kt new file mode 100644 index 00000000..6cfbc8a7 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamal.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.elgamal + +import org.bouncycastle.jce.spec.ElGamalParameterSpec +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.KeyType + +/** + * ElGamal encryption only key type. + * + * @deprecated the use of ElGamal is not recommended anymore. + */ +@Deprecated("The use of ElGamal is not recommended anymore.") +class ElGamal private constructor(length: ElGamalLength) : KeyType { + + override val name = "ElGamal" + override val algorithm = PublicKeyAlgorithm.ELGAMAL_ENCRYPT + override val bitStrength = length.length + override val algorithmSpec = ElGamalParameterSpec(length.p, length.g) + + companion object { + @JvmStatic + fun withLength(length: ElGamalLength) = ElGamal(length) + } +} \ No newline at end of file From ca3ff6acce332ddb79ca855d35333fdfe2771f13 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:21:32 +0200 Subject: [PATCH 101/155] Kotlin conversion: RsaLength --- .../key/generation/type/rsa/RsaLength.java | 29 ------------------- .../key/generation/type/rsa/RsaLength.kt | 17 +++++++++++ 2 files changed, 17 insertions(+), 29 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RsaLength.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RsaLength.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RsaLength.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RsaLength.java deleted file mode 100644 index 74951f0d..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RsaLength.java +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.rsa; - -import org.pgpainless.key.generation.type.KeyLength; - -public enum RsaLength implements KeyLength { - @Deprecated - _1024(1024), - @Deprecated - _2048(2048), - _3072(3072), - _4096(4096), - _8192(8192), - ; - - private final int length; - - RsaLength(int length) { - this.length = length; - } - - @Override - public int getLength() { - return length; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RsaLength.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RsaLength.kt new file mode 100644 index 00000000..ae8bb804 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RsaLength.kt @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.rsa + +import org.pgpainless.key.generation.type.KeyLength + +enum class RsaLength(override val length: Int) : KeyLength { + @Deprecated("1024 bits are considered too weak for RSA nowadays.", ReplaceWith("_3072")) + _1024(1024), + @Deprecated("2048 bits are considered too weak for RSA nowadays.", ReplaceWith("_3072")) + _2048(2048), + _3072(3072), + _4096(4096), + _8192(8192) +} \ No newline at end of file From ac245fb56bba1c3ea7aedda8b6c2425d5eadc659 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:24:02 +0200 Subject: [PATCH 102/155] Kotlin conversion: RSA --- .../key/generation/type/rsa/RSA.java | 48 ------------------- .../key/generation/type/rsa/package-info.java | 8 ---- .../pgpainless/key/generation/type/rsa/RSA.kt | 25 ++++++++++ 3 files changed, 25 insertions(+), 56 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RSA.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RSA.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RSA.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RSA.java deleted file mode 100644 index 3cf717b2..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RSA.java +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.rsa; - -import javax.annotation.Nonnull; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.RSAKeyGenParameterSpec; - -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.KeyType; - -/** - * Key type that specifies the RSA_GENERAL algorithm. - */ -public class RSA implements KeyType { - - private final RsaLength length; - - RSA(@Nonnull RsaLength length) { - this.length = length; - } - - public static RSA withLength(@Nonnull RsaLength length) { - return new RSA(length); - } - - @Override - public String getName() { - return "RSA"; - } - - @Override - public PublicKeyAlgorithm getAlgorithm() { - return PublicKeyAlgorithm.RSA_GENERAL; - } - - @Override - public int getBitStrength() { - return length.getLength(); - } - - @Override - public AlgorithmParameterSpec getAlgorithmSpec() { - return new RSAKeyGenParameterSpec(length.getLength(), RSAKeyGenParameterSpec.F4); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/package-info.java deleted file mode 100644 index 2a2a0120..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to RSA. - */ -package org.pgpainless.key.generation.type.rsa; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RSA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RSA.kt new file mode 100644 index 00000000..1f8c0509 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RSA.kt @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.rsa + +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.KeyType +import java.security.spec.RSAKeyGenParameterSpec + +/** + * Key type that specifies the RSA_GENERAL algorithm. + */ +class RSA private constructor(length: RsaLength): KeyType { + + override val name = "RSA" + override val algorithm = PublicKeyAlgorithm.RSA_GENERAL + override val bitStrength = length.length + override val algorithmSpec = RSAKeyGenParameterSpec(length.length, RSAKeyGenParameterSpec.F4) + + companion object { + @JvmStatic + fun withLength(length: RsaLength) = RSA(length) + } +} \ No newline at end of file From 521424c23aca24a3c1966b73ff00a526097c0695 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:26:24 +0200 Subject: [PATCH 103/155] Kotlin conversion: XDHSpec --- .../key/generation/type/xdh/XDHSpec.java | 34 ------------------- .../key/generation/type/xdh/XDHSpec.kt | 15 ++++++++ 2 files changed, 15 insertions(+), 34 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDHSpec.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDHSpec.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDHSpec.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDHSpec.java deleted file mode 100644 index ccbd2038..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDHSpec.java +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.xdh; - -import javax.annotation.Nonnull; - -public enum XDHSpec { - _X25519("X25519", "curve25519", 256), - ; - - final String name; - final String curveName; - final int bitStrength; - - XDHSpec(@Nonnull String name, @Nonnull String curveName, int bitStrength) { - this.name = name; - this.curveName = curveName; - this.bitStrength = bitStrength; - } - - public String getName() { - return name; - } - - public String getCurveName() { - return curveName; - } - - public int getBitStrength() { - return bitStrength; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDHSpec.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDHSpec.kt new file mode 100644 index 00000000..9486365f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDHSpec.kt @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.xdh + +enum class XDHSpec( + val algorithmName: String, + val curveName: String, + val bitStrength: Int) { + _X25519("X25519", "curve25519", 256), + ; + + fun getName() = algorithmName +} \ No newline at end of file From ad734ca1b4bef8ffad1cbafab98314a5ca3d2da1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:29:08 +0200 Subject: [PATCH 104/155] Kotlin conversion: XDH --- .../key/generation/package-info.java | 8 ---- .../key/generation/type/package-info.java | 8 ---- .../key/generation/type/xdh/XDH.java | 45 ------------------- .../key/generation/type/xdh/package-info.java | 8 ---- .../pgpainless/key/generation/type/xdh/XDH.kt | 21 +++++++++ 5 files changed, 21 insertions(+), 69 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/package-info.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/package-info.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDH.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDH.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/package-info.java deleted file mode 100644 index 96728227..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to OpenPGP key generation. - */ -package org.pgpainless.key.generation; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/package-info.java deleted file mode 100644 index bf048484..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes describing different OpenPGP key types. - */ -package org.pgpainless.key.generation.type; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDH.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDH.java deleted file mode 100644 index 4e589677..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDH.java +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation.type.xdh; - -import java.security.spec.AlgorithmParameterSpec; - -import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.KeyType; - -public final class XDH implements KeyType { - - private final XDHSpec spec; - - private XDH(XDHSpec spec) { - this.spec = spec; - } - - public static XDH fromSpec(XDHSpec spec) { - return new XDH(spec); - } - - @Override - public String getName() { - return "XDH"; - } - - @Override - public PublicKeyAlgorithm getAlgorithm() { - return PublicKeyAlgorithm.ECDH; - } - - @Override - public int getBitStrength() { - return spec.getBitStrength(); - } - - @Override - public AlgorithmParameterSpec getAlgorithmSpec() { - return new ECNamedCurveGenParameterSpec(spec.getName()); - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/package-info.java deleted file mode 100644 index 96af405a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to Diffie-Hellman on the X25519 curve. - */ -package org.pgpainless.key.generation.type.xdh; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDH.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDH.kt new file mode 100644 index 00000000..06888237 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDH.kt @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.generation.type.xdh + +import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.KeyType + +class XDH private constructor(spec: XDHSpec): KeyType { + override val name = "XDH" + override val algorithm = PublicKeyAlgorithm.ECDH + override val bitStrength = spec.bitStrength + override val algorithmSpec = ECNamedCurveGenParameterSpec(spec.algorithmName) + + companion object { + @JvmStatic + fun fromSpec(spec: XDHSpec) = XDH(spec) + } +} \ No newline at end of file From b6e47d773991ac7863f5e5e132b3555af4426786 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 7 Sep 2023 15:56:58 +0200 Subject: [PATCH 105/155] Kotlin conversion: KeyAccessor --- .../org/pgpainless/key/info/KeyAccessor.java | 167 ------------------ .../org/pgpainless/key/SubkeyIdentifier.kt | 2 + .../org/pgpainless/key/info/KeyAccessor.kt | 88 +++++++++ 3 files changed, 90 insertions(+), 167 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java deleted file mode 100644 index 8ab8a9c4..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.info; - -import java.util.Set; - -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; - -public abstract class KeyAccessor { - - protected final KeyRingInfo info; - protected final SubkeyIdentifier key; - - KeyAccessor(@Nonnull KeyRingInfo info, @Nonnull SubkeyIdentifier key) { - this.info = info; - this.key = key; - } - - /** - * Depending on the way we address the key (key-id or user-id), return the respective {@link PGPSignature} - * which contains the algorithm preferences we are going to use. - *

- * If we address a key via its user-id, we want to rely on the algorithm preferences in the user-id certification, - * while we would instead rely on those in the direct-key signature if we'd address the key by key-id. - * - * @return signature - */ - @Nonnull - public abstract PGPSignature getSignatureWithPreferences(); - - /** - * Return preferred symmetric key encryption algorithms. - * - * @return preferred symmetric algorithms - */ - @Nonnull - public Set getPreferredSymmetricKeyAlgorithms() { - return SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(getSignatureWithPreferences()); - } - - /** - * Return preferred hash algorithms. - * - * @return preferred hash algorithms - */ - @Nonnull - public Set getPreferredHashAlgorithms() { - return SignatureSubpacketsUtil.parsePreferredHashAlgorithms(getSignatureWithPreferences()); - } - - /** - * Return preferred compression algorithms. - * - * @return preferred compression algorithms - */ - @Nonnull - public Set getPreferredCompressionAlgorithms() { - return SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(getSignatureWithPreferences()); - } - - /** - * Address the key via a user-id (e.g. "Alice <alice@wonderland.lit>"). - * In this case we are sourcing preferred algorithms from the user-id certification first. - */ - public static class ViaUserId extends KeyAccessor { - - private final String userId; - - /** - * Access a key via user-id. - * - * @param info info about a key at a given date - * @param key id of the subkey - * @param userId user-id - */ - public ViaUserId(@Nonnull KeyRingInfo info, - @Nonnull SubkeyIdentifier key, - @Nonnull String userId) { - super(info, key); - this.userId = userId; - } - - @Override - @Nonnull - public PGPSignature getSignatureWithPreferences() { - PGPSignature signature = info.getLatestUserIdCertification(userId); - if (signature != null) { - return signature; - } - throw new IllegalStateException("No valid user-id certification signature found for '" + userId + "'."); - } - } - - /** - * Address the key via key-id. - * In this case we are sourcing preferred algorithms from the keys direct-key signature first. - */ - public static class ViaKeyId extends KeyAccessor { - - /** - * Address the key via key-id. - * @param info info about the key at a given date - * @param key key-id - */ - public ViaKeyId(@Nonnull KeyRingInfo info, - @Nonnull SubkeyIdentifier key) { - super(info, key); - } - - @Override - @Nonnull - public PGPSignature getSignatureWithPreferences() { - String primaryUserId = info.getPrimaryUserId(); - // If the key is located by Key ID, the algorithm of the primary User ID of the key provides the - // preferred symmetric algorithm. - PGPSignature signature = null; - if (primaryUserId != null) { - signature = info.getLatestUserIdCertification(primaryUserId); - } - - if (signature == null) { - signature = info.getLatestDirectKeySelfSignature(); - } - - if (signature == null) { - throw new IllegalStateException("No valid signature found."); - } - return signature; - } - } - - public static class SubKey extends KeyAccessor { - - public SubKey(@Nonnull KeyRingInfo info, - @Nonnull SubkeyIdentifier key) { - super(info, key); - } - - @Override - @Nonnull - public PGPSignature getSignatureWithPreferences() { - PGPSignature signature; - if (key.getPrimaryKeyId() == key.getSubkeyId()) { - signature = info.getLatestDirectKeySelfSignature(); - if (signature == null && info.getPrimaryUserId() != null) { - signature = info.getLatestUserIdCertification(info.getPrimaryUserId()); - } - } else { - signature = info.getCurrentSubkeyBindingSignature(key.getSubkeyId()); - } - - if (signature == null) { - throw new IllegalStateException("No valid signature found."); - } - return signature; - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt index 58e7719c..cda7eccd 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt @@ -31,6 +31,8 @@ class SubkeyIdentifier( val subkeyId = subkeyFingerprint.keyId val primaryKeyId = primaryKeyFingerprint.keyId + val isPrimaryKey = keyId == subkeyId + fun matches(fingerprint: OpenPgpFingerprint) = primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt new file mode 100644 index 00000000..f49a6ca4 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.info + +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil + +abstract class KeyAccessor( + protected val info: KeyRingInfo, + protected val key: SubkeyIdentifier +) { + + /** + * Depending on the way we address the key (key-id or user-id), return the respective [PGPSignature] + * which contains the algorithm preferences we are going to use. + *

+ * If we address a key via its user-id, we want to rely on the algorithm preferences in the user-id certification, + * while we would instead rely on those in the direct-key signature if we'd address the key by key-id. + * + * @return signature + */ + abstract val signatureWithPreferences: PGPSignature + + /** + * Preferred symmetric key encryption algorithms. + */ + val preferredSymmetricKeyAlgorithms: Set + get() = SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences) + + /** + * Preferred hash algorithms. + */ + val preferredHashAlgorithms: Set + get() = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signatureWithPreferences) + + /** + * Preferred compression algorithms. + */ + val preferredCompressionAlgorithms: Set + get() = SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences) + + /** + * Address the key via a user-id (e.g. `Alice `). + * In this case we are sourcing preferred algorithms from the user-id certification first. + */ + class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence): KeyAccessor(info, key) { + override val signatureWithPreferences: PGPSignature + get() = checkNotNull(info.getLatestUserIdCertification(userId.toString())) { + "No valid user-id certification signature found for '$userId'." + } + } + + /** + * Address the key via key-id. + * In this case we are sourcing preferred algorithms from the keys direct-key signature first. + */ + class ViaKeyId(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) { + override val signatureWithPreferences: PGPSignature + get() { + // If the key is located by Key ID, the algorithm of the primary User ID of the key provides the + // preferred symmetric algorithm. + info.primaryUserId?.let { + userId -> info.getLatestUserIdCertification(userId).let { if (it != null) return it } + } + + return checkNotNull(info.latestDirectKeySelfSignature) { "No valid signature found." } + } + } + + class SubKey(info: KeyRingInfo, key: SubkeyIdentifier): KeyAccessor(info, key) { + override val signatureWithPreferences: PGPSignature + get() = checkNotNull( + if (key.isPrimaryKey) { + info.latestDirectKeySelfSignature ?: + info.primaryUserId?.let { info.getLatestUserIdCertification(it) } + } else { + info.getCurrentSubkeyBindingSignature(key.subkeyId) + } + ) { "No valid signature found." } + + } +} \ No newline at end of file From 8fe9d250a8f3839a8d7b8b0f2e7dba16ecd3134f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 8 Sep 2023 14:57:58 +0200 Subject: [PATCH 106/155] Kotlin conversion: KeyInfo --- .../java/org/pgpainless/key/info/KeyInfo.java | 139 ------------------ .../extensions/PGPPublicKeyExtensions.kt | 37 +++++ .../extensions/PGPSecretKeyExtensions.kt | 29 ++++ .../kotlin/org/pgpainless/key/info/KeyInfo.kt | 75 ++++++++++ .../key/protection/UnlockSecretKey.kt | 4 +- 5 files changed, 143 insertions(+), 141 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java create mode 100644 pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt create mode 100644 pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java deleted file mode 100644 index f455608e..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub , 2021 Flowcrypt a.s. -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.info; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers; -import org.bouncycastle.bcpg.ECDHPublicBCPGKey; -import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; -import org.bouncycastle.bcpg.ECPublicBCPGKey; -import org.bouncycastle.bcpg.EdDSAPublicBCPGKey; -import org.bouncycastle.bcpg.S2K; -import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.eddsa.EdDSACurve; - -public class KeyInfo { - - private final PGPSecretKey secretKey; - private final PGPPublicKey publicKey; - - public KeyInfo(PGPSecretKey secretKey) { - this.secretKey = secretKey; - this.publicKey = secretKey.getPublicKey(); - } - - public KeyInfo(PGPPublicKey publicKey) { - this.publicKey = publicKey; - this.secretKey = null; - } - - public String getCurveName() { - return getCurveName(publicKey); - } - - /** - * Returns indication that a contained secret key is encrypted. - * - * @return true if secret key is encrypted, false if secret key is not encrypted or there is public key only. - */ - public boolean isEncrypted() { - return secretKey != null && isEncrypted(secretKey); - } - - /** - * Returns indication that a contained secret key is not encrypted. - * - * @return true if secret key is not encrypted or there is public key only, false if secret key is encrypted. - */ - public boolean isDecrypted() { - return secretKey == null || isDecrypted(secretKey); - } - - /** - * Returns indication that a contained secret key has S2K of a type GNU_DUMMY_S2K. - * - * @return true if secret key has S2K of a type GNU_DUMMY_S2K, false if there is public key only, - * or S2K on the secret key is absent or not of a type GNU_DUMMY_S2K. - */ - public boolean hasDummyS2K() { - return secretKey != null && hasDummyS2K(secretKey); - } - - public static String getCurveName(PGPPublicKey publicKey) { - PublicKeyAlgorithm algorithm = PublicKeyAlgorithm.requireFromId(publicKey.getAlgorithm()); - ECPublicBCPGKey key; - switch (algorithm) { - case ECDSA: { - key = (ECDSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey(); - break; - } - case ECDH: { - key = (ECDHPublicBCPGKey) publicKey.getPublicKeyPacket().getKey(); - break; - } - case EDDSA: { - key = (EdDSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey(); - break; - } - default: - throw new IllegalArgumentException("Not an elliptic curve public key (" + algorithm + ")"); - } - return getCurveName(key); - } - - public static String getCurveName(ECPublicBCPGKey key) { - ASN1ObjectIdentifier identifier = key.getCurveOID(); - - String curveName = ECUtil.getCurveName(identifier); - if (curveName != null) { - return curveName; - } - - // Workaround for ECUtil not recognizing ed25519 - // see https://github.com/bcgit/bc-java/issues/1087 - // UPDATE: Apparently 1087 is not fixed properly with BC 1.71 - // See https://github.com/bcgit/bc-java/issues/1142 - // TODO: Remove once BC comes out with a fix. - if (identifier.equals(GNUObjectIdentifiers.Ed25519)) { - return EdDSACurve._Ed25519.getName(); - } - - return null; - } - - /** - * Returns indication that a secret key is encrypted. - * - * @param secretKey A secret key to examine. - * @return true if secret key is encrypted, false otherwise. - */ - public static boolean isEncrypted(PGPSecretKey secretKey) { - return secretKey.getS2KUsage() != 0; - } - - /** - * Returns indication that a secret key is not encrypted. - * - * @param secretKey A secret key to examine. - * @return true if secret key is encrypted, false otherwise. - */ - public static boolean isDecrypted(PGPSecretKey secretKey) { - return secretKey.getS2KUsage() == 0; - } - - /** - * Returns indication that a secret key has S2K of a type GNU_DUMMY_S2K. - * - * @param secretKey A secret key to examine. - * @return true if secret key has S2K of a type GNU_DUMMY_S2K, false otherwise. - */ - public static boolean hasDummyS2K(PGPSecretKey secretKey) { - final S2K s2k = secretKey.getS2K(); - return s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt new file mode 100644 index 00000000..a1ed72fe --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle.extensions + +import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers +import org.bouncycastle.bcpg.ECDHPublicBCPGKey +import org.bouncycastle.bcpg.ECDSAPublicBCPGKey +import org.bouncycastle.bcpg.EdDSAPublicBCPGKey +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil +import org.bouncycastle.openpgp.PGPPublicKey +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.eddsa.EdDSACurve + +/** + * For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and [PublicKeyAlgorithm.EDDSA], + * this method returns the name of the underlying elliptic curve. + * + * For other key types or unknown curves, this method throws an [IllegalArgumentException]. + * + * @return curve name + */ +fun PGPPublicKey.getCurveName(): String { + PublicKeyAlgorithm.requireFromId(algorithm) + .let { + when (it) { + PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey + PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey + PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey + else -> throw IllegalArgumentException("No an elliptic curve public key ($it).") + } + } + .let { if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName else it.curveOID} + .let { it to ECUtil.getCurveName(it) } + .let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") } +} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt new file mode 100644 index 00000000..cba7bdba --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle.extensions + +import org.bouncycastle.bcpg.S2K +import org.bouncycastle.openpgp.PGPSecretKey + +/** + * Returns indication that the secret key is encrypted. + * + * @return true if secret key is encrypted, false otherwise. + */ +fun PGPSecretKey?.isEncrypted(): Boolean = (this != null) && (s2KUsage != 0) + +/** + * Returns indication that the secret key is not encrypted. + * + * @return true if secret key is encrypted, false otherwise. + */ +fun PGPSecretKey?.isDecrypted(): Boolean = (this == null) || (s2KUsage == 0) + +/** + * Returns indication that the secret key has S2K of a type GNU_DUMMY_S2K. + * + * @return true if secret key has S2K of type GNU_DUMMY_S2K, false otherwise. + */ +fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt new file mode 100644 index 00000000..652cf22d --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub , 2021 Flowcrypt a.s. +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.info + +import org.bouncycastle.extensions.getCurveName +import org.bouncycastle.extensions.hasDummyS2K +import org.bouncycastle.extensions.isDecrypted +import org.bouncycastle.extensions.isEncrypted +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey + +@Deprecated("Deprecated in favor of extension functions to PGPSecretKey and PGPPublicKey.") +class KeyInfo private constructor( + val secretKey: PGPSecretKey?, + val publicKey: PGPPublicKey) { + + constructor(secretKey: PGPSecretKey): this(secretKey, secretKey.publicKey) + constructor(publicKey: PGPPublicKey): this(null, publicKey) + + /** + * Return the name of the elliptic curve used by this key, or throw an [IllegalArgumentException] if the key + * is not based on elliptic curves, or on an unknown curve. + */ + @Deprecated("Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.", + ReplaceWith("publicKey.getCurveName()")) + val curveName: String + get() = publicKey.getCurveName() + + /** + * Return true, if the secret key is encrypted. + * This method returns false, if the secret key is null. + */ + @Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isEncrypted()")) + val isEncrypted: Boolean + get() = secretKey?.isEncrypted() ?: false + + /** + * Return true, if the secret key is decrypted. + * This method returns true, if the secret key is null. + */ + @Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isDecrypted()")) + val isDecrypted: Boolean + get() = secretKey?.isDecrypted() ?: true + + /** + * Return true, if the secret key is using the GNU_DUMMY_S2K s2k type. + * This method returns false, if the secret key is null. + */ + @Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", + ReplaceWith("secretKey.hasDummyS2K()")) + val hasDummyS2K: Boolean + @JvmName("hasDummyS2K") + get() = secretKey?.hasDummyS2K() ?: false + + companion object { + @JvmStatic + @Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isEncrypted()")) + fun isEncrypted(secretKey: PGPSecretKey?) = secretKey.isEncrypted() + + @JvmStatic + @Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isDecrypted()")) + fun isDecrypted(secretKey: PGPSecretKey?) = secretKey.isDecrypted() + + @JvmStatic + @Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", + ReplaceWith("secretKey.hasDummyS2K()")) + fun hasDummyS2K(secretKey: PGPSecretKey?) = secretKey.hasDummyS2K() + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt index 1317ef05..84d7d0d7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt @@ -6,6 +6,7 @@ package org.pgpainless.key.protection import openpgp.openPgpKeyId +import org.bouncycastle.extensions.isEncrypted import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPSecretKey @@ -13,7 +14,6 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor import org.pgpainless.PGPainless import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.exception.WrongPassphraseException -import org.pgpainless.key.info.KeyInfo import org.pgpainless.key.util.PublicKeyParameterValidationUtil import org.pgpainless.util.Passphrase import kotlin.jvm.Throws @@ -25,7 +25,7 @@ class UnlockSecretKey { @JvmStatic @Throws(PGPException::class, KeyIntegrityException::class) fun unlockSecretKey(secretKey: PGPSecretKey, protector: SecretKeyRingProtector): PGPPrivateKey { - return if (KeyInfo.isEncrypted(secretKey)) { + return if (secretKey.isEncrypted()) { unlockSecretKey(secretKey, protector.getDecryptor(secretKey.keyID)) } else { unlockSecretKey(secretKey, null as PBESecretKeyDecryptor?) From 9ee0f09b8d01b149b3ce0fa12bec5484ddc40cf3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 8 Sep 2023 15:01:34 +0200 Subject: [PATCH 107/155] Fix bug caused by false field comparison in SubkeyIdentifier --- .../src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt index cda7eccd..a793fc99 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt @@ -31,7 +31,7 @@ class SubkeyIdentifier( val subkeyId = subkeyFingerprint.keyId val primaryKeyId = primaryKeyFingerprint.keyId - val isPrimaryKey = keyId == subkeyId + val isPrimaryKey = primaryKeyId == subkeyId fun matches(fingerprint: OpenPgpFingerprint) = primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint From 85e2fe956a3cea752a97d9ca3e6128350ec60856 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 8 Sep 2023 15:03:49 +0200 Subject: [PATCH 108/155] Add test for SubkeyIdentifier.isPrimaryKey() --- .../java/org/pgpainless/key/SubkeyIdentifierTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/SubkeyIdentifierTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/SubkeyIdentifierTest.java index fe792e4d..add18fd5 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/SubkeyIdentifierTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/SubkeyIdentifierTest.java @@ -5,8 +5,10 @@ package org.pgpainless.key; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.NoSuchElementException; @@ -102,6 +104,15 @@ public class SubkeyIdentifierTest { assertNotEquals(id1, null); } + @Test + public void testIsPrimaryKey() { + SubkeyIdentifier primaryKey = new SubkeyIdentifier(PRIMARY_FP); + assertTrue(primaryKey.isPrimaryKey()); + + SubkeyIdentifier subKey = new SubkeyIdentifier(PRIMARY_FP, SUBKEY_FP); + assertFalse(subKey.isPrimaryKey()); + } + @Test public void nonExistentSubkeyThrowsNoSuchElementException() { assertThrows(NoSuchElementException.class, () -> new SubkeyIdentifier(CERT, 123)); From 6f9e692474b1e2d77fcdb885a67bd984a33c2651 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Oct 2023 12:49:04 +0200 Subject: [PATCH 109/155] Kotlin conversion: KeyRingInfo --- .../org/pgpainless/key/info/KeyRingInfo.java | 1318 ----------------- .../org/pgpainless/key/info/package-info.java | 8 - .../extensions/PGPSignatureExtensions.kt | 7 + .../org/pgpainless/key/info/KeyRingInfo.kt | 765 ++++++++++ 4 files changed, 772 insertions(+), 1326 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/info/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java deleted file mode 100644 index 31a6dd86..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ /dev/null @@ -1,1318 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub , 2021 Flowcrypt a.s. -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.info; - -import static org.pgpainless.util.CollectionUtils.iteratorToList; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.S2K; -import org.bouncycastle.bcpg.sig.PrimaryUserID; -import org.bouncycastle.bcpg.sig.RevocationReason; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.EncryptionPurpose; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.RevocationState; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.exception.KeyException; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.key.util.KeyIdUtil; -import org.pgpainless.key.util.KeyRingUtils; -import org.pgpainless.key.util.RevocationAttributes; -import org.pgpainless.policy.Policy; -import org.pgpainless.signature.SignatureUtils; -import org.pgpainless.signature.consumer.SignaturePicker; -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; -import org.pgpainless.util.DateUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}. - */ -public class KeyRingInfo { - - private static final Pattern PATTERN_EMAIL_FROM_USERID = Pattern.compile("<([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)>"); - private static final Pattern PATTERN_EMAIL_EXPLICIT = Pattern.compile("^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$"); - - private static final Logger LOGGER = LoggerFactory.getLogger(KeyRingInfo.class); - - private final PGPKeyRing keys; - private final Signatures signatures; - private final Date referenceDate; - private final String primaryUserId; - private final RevocationState revocationState; - - /** - * Evaluate the key ring at creation time of the given signature. - * - * @param keyRing key ring - * @param signature signature - * @return info of key ring at signature creation time - */ - @Nonnull - public static KeyRingInfo evaluateForSignature(@Nonnull PGPKeyRing keyRing, - @Nonnull PGPSignature signature) { - return new KeyRingInfo(keyRing, signature.getCreationTime()); - } - - /** - * Evaluate the key ring right now. - * - * @param keys key ring - */ - public KeyRingInfo(@Nonnull PGPKeyRing keys) { - this(keys, new Date()); - } - - /** - * Evaluate the key ring at the provided validation date. - * - * @param keys key ring - * @param referenceDate date of validation - */ - public KeyRingInfo(@Nonnull PGPKeyRing keys, - @Nonnull Date referenceDate) { - this(keys, PGPainless.getPolicy(), referenceDate); - } - - /** - * Evaluate the key ring at the provided validation date. - * - * @param keys key ring - * @param policy policy - * @param referenceDate validation date - */ - public KeyRingInfo(@Nonnull PGPKeyRing keys, - @Nonnull Policy policy, - @Nonnull Date referenceDate) { - this.referenceDate = referenceDate; - this.keys = keys; - this.signatures = new Signatures(keys, this.referenceDate, policy); - this.primaryUserId = findPrimaryUserId(); - this.revocationState = findRevocationState(); - } - - /** - * Return the underlying {@link PGPKeyRing}. - * @return keys - */ - public PGPKeyRing getKeys() { - return keys; - } - - public List getValidSubkeys() { - List subkeys = new ArrayList<>(); - Iterator iterator = getKeys().getPublicKeys(); - while (iterator.hasNext()) { - PGPPublicKey key = iterator.next(); - if (isKeyValidlyBound(key.getKeyID())) { - subkeys.add(key); - } - } - return subkeys; - } - - @Nonnull - private RevocationState findRevocationState() { - PGPSignature revocation = signatures.primaryKeyRevocation; - if (revocation != null) { - return SignatureUtils.isHardRevocation(revocation) ? - RevocationState.hardRevoked() : RevocationState.softRevoked(revocation.getCreationTime()); - } - return RevocationState.notRevoked(); - } - - /** - * Return the first {@link PGPPublicKey} of this key ring. - * - * @return public key - */ - @Nonnull - public PGPPublicKey getPublicKey() { - return keys.getPublicKey(); - } - - /** - * Return the public key with the given fingerprint. - * - * @param fingerprint fingerprint - * @return public key or null - */ - @Nullable - public PGPPublicKey getPublicKey(@Nonnull OpenPgpFingerprint fingerprint) { - return getPublicKey(fingerprint.getKeyId()); - } - - /** - * Return the public key with the given key id. - * - * @param keyId key id - * @return public key or null - */ - @Nullable - public PGPPublicKey getPublicKey(long keyId) { - return getPublicKey(keys, keyId); - } - - /** - * Return the public key with the given key id from the provided key ring. - * - * @param keyRing key ring - * @param keyId key id - * @return public key or null - */ - @Nullable - public static PGPPublicKey getPublicKey(@Nonnull PGPKeyRing keyRing, long keyId) { - return keyRing.getPublicKey(keyId); - } - - /** - * Return true if the public key with the given key id is bound to the key ring properly. - * - * @param keyId key id - * @return true if key is bound validly - */ - public boolean isKeyValidlyBound(long keyId) { - PGPPublicKey publicKey = keys.getPublicKey(keyId); - if (publicKey == null) { - return false; - } - - if (publicKey == getPublicKey()) { - if (signatures.primaryKeyRevocation != null && SignatureUtils.isHardRevocation(signatures.primaryKeyRevocation)) { - return false; - } - return signatures.primaryKeyRevocation == null; - } - - PGPSignature binding = signatures.subkeyBindings.get(keyId); - PGPSignature revocation = signatures.subkeyRevocations.get(keyId); - - // No valid binding - if (binding == null || SignatureUtils.isSignatureExpired(binding)) { - return false; - } - - // Revocation - if (revocation != null) { - if (SignatureUtils.isHardRevocation(revocation)) { - // Subkey is hard revoked - return false; - } else { - // Key is soft-revoked, not yet re-bound - return SignatureUtils.isSignatureExpired(revocation) - || !revocation.getCreationTime().after(binding.getCreationTime()); - } - } - - return true; - } - - /** - * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. - * The first key in the list being the primary key. - * Note that the list is unmodifiable. - * - * @return list of public keys - */ - @Nonnull - public List getPublicKeys() { - Iterator iterator = keys.getPublicKeys(); - List list = iteratorToList(iterator); - return Collections.unmodifiableList(list); - } - - /** - * Return the primary {@link PGPSecretKey} of this key ring or null if the key ring is not a {@link PGPSecretKeyRing}. - * - * @return primary secret key or null if the key ring is public - */ - @Nullable - public PGPSecretKey getSecretKey() { - if (keys instanceof PGPSecretKeyRing) { - PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keys; - return secretKeys.getSecretKey(); - } - return null; - } - - /** - * Return the secret key with the given fingerprint. - * - * @param fingerprint fingerprint - * @return secret key or null - */ - @Nullable - public PGPSecretKey getSecretKey(@Nonnull OpenPgpFingerprint fingerprint) { - return getSecretKey(fingerprint.getKeyId()); - } - - /** - * Return the secret key with the given key id. - * - * @param keyId key id - * @return secret key or null - */ - @Nullable - public PGPSecretKey getSecretKey(long keyId) { - if (keys instanceof PGPSecretKeyRing) { - return ((PGPSecretKeyRing) keys).getSecretKey(keyId); - } - return null; - } - - /** - * Return all secret keys of the key ring. - * If the key ring is a {@link PGPPublicKeyRing}, then return an empty list. - * Note that the list is unmodifiable. - * - * @return list of secret keys - */ - @Nonnull - public List getSecretKeys() { - if (keys instanceof PGPSecretKeyRing) { - PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keys; - Iterator iterator = secretKeys.getSecretKeys(); - return Collections.unmodifiableList(iteratorToList(iterator)); - } - return Collections.emptyList(); - } - - /** - * Return the key id of the primary key of this key ring. - * - * @return key id - */ - public long getKeyId() { - return getPublicKey().getKeyID(); - } - - /** - * Return the {@link OpenPgpFingerprint} of this key ring. - * - * @return fingerprint - */ - @Nonnull - public OpenPgpFingerprint getFingerprint() { - return OpenPgpFingerprint.of(getPublicKey()); - } - - @Nullable - public String getPrimaryUserId() { - return primaryUserId; - } - - /** - * Return the current primary user-id of the key ring. - *

- * Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, - * this method returns the first user-id on the key, otherwise null. - * - * @return primary user-id or null - */ - @Nullable - private String findPrimaryUserId() { - String primaryUserId = null; - Date currentModificationDate = null; - - List userIds = getUserIds(); - if (userIds.isEmpty()) { - return null; - } - - String firstUserId = null; - for (String userId : userIds) { - PGPSignature certification = signatures.userIdCertifications.get(userId); - if (certification == null) { - continue; - } - - if (firstUserId == null) { - firstUserId = userId; - } - Date creationTime = certification.getCreationTime(); - - if (certification.getHashedSubPackets().isPrimaryUserID()) { - if (currentModificationDate == null || creationTime.after(currentModificationDate)) { - primaryUserId = userId; - currentModificationDate = creationTime; - } - - } - } - - if (primaryUserId != null) { - return primaryUserId; - } - - return firstUserId; - } - - /** - * Return a list of all user-ids of the primary key. - * Note: This list might also contain expired / revoked user-ids. - * Consider using {@link #getValidUserIds()} instead. - * - * @return list of user-ids - */ - @Nonnull - public List getUserIds() { - return KeyRingUtils.getUserIdsIgnoringInvalidUTF8(keys.getPublicKey()); - } - - /** - * Return a list of valid user-ids. - * - * @return valid user-ids - */ - @Nonnull - public List getValidUserIds() { - List valid = new ArrayList<>(); - List userIds = getUserIds(); - for (String userId : userIds) { - if (isUserIdBound(userId)) { - valid.add(userId); - } - } - return valid; - } - - /** - * Return a list of all user-ids that were valid at some point, but might be expired by now. - * - * @return bound user-ids - */ - @Nonnull - public List getValidAndExpiredUserIds() { - List probablyExpired = new ArrayList<>(); - List userIds = getUserIds(); - - for (String userId : userIds) { - PGPSignature certification = signatures.userIdCertifications.get(userId); - PGPSignature revocation = signatures.userIdRevocations.get(userId); - - // Unbound user-id - if (certification == null) { - continue; - } - - // Not revoked -> valid - if (revocation == null) { - probablyExpired.add(userId); - continue; - } - - // Hard revocation -> invalid - if (SignatureUtils.isHardRevocation(revocation)) { - continue; - } - - // Soft revocation -> valid if certification is newer than revocation (revalidation) - if (certification.getCreationTime().after(revocation.getCreationTime())) { - probablyExpired.add(userId); - } - } - return probablyExpired; - } - - /** - * Return true if the provided user-id is valid. - * - * @param userId user-id - * @return true if user-id is valid - */ - public boolean isUserIdValid(@Nonnull CharSequence userId) { - if (primaryUserId == null) { - // No primary userID? No userID at all! - return false; - } - - if (!userId.equals(primaryUserId)) { - if (!isUserIdBound(primaryUserId)) { - // primary user-id not valid? UserID not valid! - return false; - } - } - return isUserIdBound(userId); - } - - private boolean isUserIdBound(@Nonnull CharSequence userId) { - String userIdString = userId.toString(); - PGPSignature certification = signatures.userIdCertifications.get(userIdString); - PGPSignature revocation = signatures.userIdRevocations.get(userIdString); - - if (certification == null) { - return false; - } - if (SignatureUtils.isSignatureExpired(certification)) { - return false; - } - if (certification.getHashedSubPackets().isPrimaryUserID()) { - Date keyExpiration = SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(certification, keys.getPublicKey()); - if (keyExpiration != null && referenceDate.after(keyExpiration)) { - return false; - } - } - // Not revoked -> valid - if (revocation == null) { - return true; - } - // Hard revocation -> invalid - if (SignatureUtils.isHardRevocation(revocation)) { - return false; - } - // Soft revocation -> valid if certification is newer than revocation (revalidation) - return certification.getCreationTime().after(revocation.getCreationTime()); - } - - /** - * Return a list of all user-ids of the primary key that appear to be email-addresses. - * Note: This list might contain expired / revoked user-ids. - * - * @return email addresses - */ - @Nonnull - public List getEmailAddresses() { - List userIds = getUserIds(); - List emails = new ArrayList<>(); - for (String userId : userIds) { - Matcher matcher = PATTERN_EMAIL_FROM_USERID.matcher(userId); - if (matcher.find()) { - emails.add(matcher.group(1)); - } else { - matcher = PATTERN_EMAIL_EXPLICIT.matcher(userId); - if (matcher.find()) { - emails.add(matcher.group(1)); - } - } - } - return emails; - } - - /** - * Return the latest direct-key self signature. - *

- * Note: This signature might be expired (check with {@link SignatureUtils#isSignatureExpired(PGPSignature)}). - * - * @return latest direct key self-signature or null - */ - @Nullable - public PGPSignature getLatestDirectKeySelfSignature() { - return signatures.primaryKeySelfSignature; - } - - /** - * Return the latest revocation self-signature on the primary key. - * - * @return revocation or null - */ - @Nullable - public PGPSignature getRevocationSelfSignature() { - return signatures.primaryKeyRevocation; - } - - /** - * Return the latest certification self-signature on the provided user-id. - * - * @param userId user-id - * @return certification signature or null - */ - @Nullable - public PGPSignature getLatestUserIdCertification(@Nonnull CharSequence userId) { - return signatures.userIdCertifications.get(userId.toString()); - } - - /** - * Return the latest user-id revocation signature for the provided user-id. - * - * @param userId user-id - * @return revocation or null - */ - @Nullable - public PGPSignature getUserIdRevocation(@Nonnull CharSequence userId) { - return signatures.userIdRevocations.get(userId.toString()); - } - - /** - * Return the currently active subkey binding signature for the subkey with the provided key-id. - * - * @param keyId subkey id - * @return subkey binding signature or null - */ - @Nullable - public PGPSignature getCurrentSubkeyBindingSignature(long keyId) { - return signatures.subkeyBindings.get(keyId); - } - - /** - * Return the latest subkey binding revocation signature for the subkey with the given key-id. - * - * @param keyId subkey id - * @return subkey binding revocation or null - */ - @Nullable - public PGPSignature getSubkeyRevocationSignature(long keyId) { - return signatures.subkeyRevocations.get(keyId); - } - - /** - * Return a list of {@link KeyFlag KeyFlags} that apply to the subkey with the provided key id. - * @param keyId key-id - * @return list of key flags - */ - @Nonnull - public List getKeyFlagsOf(long keyId) { - // key is primary key - if (getPublicKey().getKeyID() == keyId) { - - PGPSignature directKeySignature = getLatestDirectKeySelfSignature(); - if (directKeySignature != null) { - List keyFlags = SignatureSubpacketsUtil.parseKeyFlags(directKeySignature); - if (keyFlags != null) { - return keyFlags; - } - } - - String primaryUserId = getPrimaryUserId(); - if (primaryUserId != null) { - PGPSignature userIdSignature = getLatestUserIdCertification(primaryUserId); - List keyFlags = SignatureSubpacketsUtil.parseKeyFlags(userIdSignature); - if (keyFlags != null) { - return keyFlags; - } - } - } - // Key is subkey - else { - PGPSignature bindingSignature = getCurrentSubkeyBindingSignature(keyId); - if (bindingSignature != null) { - List keyFlags = SignatureSubpacketsUtil.parseKeyFlags(bindingSignature); - if (keyFlags != null) { - return keyFlags; - } - } - } - return Collections.emptyList(); - } - - /** - * Return a list of {@link KeyFlag KeyFlags} that apply to the given user-id. - * - * @param userId user-id - * @return key flags - */ - @Nonnull - public List getKeyFlagsOf(String userId) { - if (!isUserIdValid(userId)) { - return Collections.emptyList(); - } - - PGPSignature userIdCertification = getLatestUserIdCertification(userId); - if (userIdCertification == null) { - throw new AssertionError("While user-id '" + userId + "' was reported as valid, there appears to be no certification for it."); - } - - List keyFlags = SignatureSubpacketsUtil.parseKeyFlags(userIdCertification); - if (keyFlags != null) { - return keyFlags; - } - return Collections.emptyList(); - } - - /** - * Return the algorithm of the primary key. - * - * @return public key algorithm - */ - @Nonnull - public PublicKeyAlgorithm getAlgorithm() { - return PublicKeyAlgorithm.requireFromId(getPublicKey().getAlgorithm()); - } - - /** - * Return the creation date of the primary key. - * - * @return creation date - */ - @Nonnull - public Date getCreationDate() { - return getPublicKey().getCreationTime(); - } - - /** - * Return the date on which the key ring was last modified. - * This date corresponds to the date of the last signature that was made on this key ring by the primary key. - * - * @return last modification date. - */ - @Nonnull - public Date getLastModified() { - PGPSignature mostRecent = getMostRecentSignature(); - if (mostRecent == null) { - // No sigs found. Return public key creation date instead. - return getLatestKeyCreationDate(); - } - return mostRecent.getCreationTime(); - } - - /** - * Return the creation time of the latest added subkey. - * - * @return latest key creation time - */ - @Nonnull - public Date getLatestKeyCreationDate() { - Date latestCreation = null; - for (PGPPublicKey key : getPublicKeys()) { - if (!isKeyValidlyBound(key.getKeyID())) { - continue; - } - Date keyCreation = key.getCreationTime(); - if (latestCreation == null || latestCreation.before(keyCreation)) { - latestCreation = keyCreation; - } - } - if (latestCreation == null) { - throw new AssertionError("Apparently there is no validly bound key in this key ring."); - } - return latestCreation; - } - - @Nullable - private PGPSignature getMostRecentSignature() { - Set allSignatures = new HashSet<>(); - PGPSignature mostRecentSelfSignature = getLatestDirectKeySelfSignature(); - PGPSignature revocationSelfSignature = getRevocationSelfSignature(); - if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature); - if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature); - allSignatures.addAll(signatures.userIdCertifications.values()); - allSignatures.addAll(signatures.userIdRevocations.values()); - allSignatures.addAll(signatures.subkeyBindings.values()); - allSignatures.addAll(signatures.subkeyRevocations.values()); - - PGPSignature mostRecent = null; - for (PGPSignature signature : allSignatures) { - if (mostRecent == null || signature.getCreationTime().after(mostRecent.getCreationTime())) { - mostRecent = signature; - } - } - return mostRecent; - } - - @Nonnull - public RevocationState getRevocationState() { - return revocationState; - } - - /** - * Return the date on which the primary key was revoked, or null if it has not yet been revoked. - * - * @return revocation date or null - */ - @Nullable - public Date getRevocationDate() { - return getRevocationState().isSoftRevocation() ? getRevocationState().getDate() : null; - } - - /** - * Return the date of expiration of the primary key or null if the key has no expiration date. - * - * @return expiration date - */ - @Nullable - public Date getPrimaryKeyExpirationDate() { - PGPSignature directKeySig = getLatestDirectKeySelfSignature(); - Date directKeyExpirationDate = null; - if (directKeySig != null) { - directKeyExpirationDate = SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(directKeySig, getPublicKey()); - } - - PGPSignature primaryUserIdCertification = null; - Date userIdExpirationDate = null; - String possiblyExpiredPrimaryUserId = getPossiblyExpiredPrimaryUserId(); - if (possiblyExpiredPrimaryUserId != null) { - primaryUserIdCertification = getLatestUserIdCertification(possiblyExpiredPrimaryUserId); - if (primaryUserIdCertification != null) { - userIdExpirationDate = SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(primaryUserIdCertification, getPublicKey()); - } - } - - if (directKeySig == null && primaryUserIdCertification == null) { - throw new NoSuchElementException("No direct-key signature and no user-id signature found."); - } - - if (directKeyExpirationDate != null && userIdExpirationDate == null) { - return directKeyExpirationDate; - } - - if (directKeyExpirationDate == null) { - return userIdExpirationDate; - } - - if (directKeyExpirationDate.before(userIdExpirationDate)) { - return directKeyExpirationDate; - } - - return userIdExpirationDate; - } - - @Nullable - public String getPossiblyExpiredPrimaryUserId() { - String validPrimaryUserId = getPrimaryUserId(); - if (validPrimaryUserId != null) { - return validPrimaryUserId; - } - - Date latestCreationTime = null; - String primaryUserId = null; - boolean foundPrimary = false; - for (String userId : getUserIds()) { - PGPSignature signature = getLatestUserIdCertification(userId); - if (signature == null) { - continue; - } - - boolean isPrimary = signature.getHashedSubPackets().isPrimaryUserID(); - if (foundPrimary && !isPrimary) { - continue; - } - - Date creationTime = signature.getCreationTime(); - if (latestCreationTime == null || creationTime.after(latestCreationTime) || isPrimary && !foundPrimary) { - latestCreationTime = creationTime; - primaryUserId = userId; - } - - foundPrimary |= isPrimary; - } - - return primaryUserId; - } - - /** - * Return the expiration date of the subkey with the provided fingerprint. - * - * @param fingerprint subkey fingerprint - * @return expiration date or null - */ - @Nullable - public Date getSubkeyExpirationDate(OpenPgpFingerprint fingerprint) { - if (getPublicKey().getKeyID() == fingerprint.getKeyId()) { - return getPrimaryKeyExpirationDate(); - } - - PGPPublicKey subkey = getPublicKey(fingerprint.getKeyId()); - if (subkey == null) { - throw new NoSuchElementException("No subkey with fingerprint " + fingerprint + " found."); - } - - PGPSignature bindingSig = getCurrentSubkeyBindingSignature(fingerprint.getKeyId()); - if (bindingSig == null) { - throw new AssertionError("Subkey has no valid binding signature."); - } - - return SignatureUtils.getKeyExpirationDate(subkey.getCreationTime(), bindingSig); - } - - /** - * Return the latest date on which the key ring is still usable for the given key flag. - * If only a subkey is carrying the required flag and the primary key expires earlier than the subkey, - * the expiry date of the primary key is returned. - *

- * This method might return null, if the primary key and a subkey with the required flag does not expire. - * @param use key flag representing the use case, e.g. {@link KeyFlag#SIGN_DATA} or - * {@link KeyFlag#ENCRYPT_COMMS}/{@link KeyFlag#ENCRYPT_STORAGE}. - * @return latest date on which the key ring can be used for the given use case, or null if it can be used indefinitely. - */ - @Nullable - public Date getExpirationDateForUse(KeyFlag use) { - if (use == KeyFlag.SPLIT || use == KeyFlag.SHARED) { - throw new IllegalArgumentException("SPLIT and SHARED are not uses, but properties."); - } - - Date primaryExpiration = getPrimaryKeyExpirationDate(); - List nonExpiringSubkeys = new ArrayList<>(); - Date latestSubkeyExpirationDate = null; - - List keysWithFlag = getKeysWithKeyFlag(use); - if (keysWithFlag.isEmpty()) { - throw new NoSuchElementException("No key with the required key flag found."); - } - - for (PGPPublicKey key : keysWithFlag) { - Date subkeyExpirationDate = getSubkeyExpirationDate(OpenPgpFingerprint.of(key)); - if (subkeyExpirationDate == null) { - nonExpiringSubkeys.add(key); - } else { - if (latestSubkeyExpirationDate == null || subkeyExpirationDate.after(latestSubkeyExpirationDate)) { - latestSubkeyExpirationDate = subkeyExpirationDate; - } - } - } - - if (nonExpiringSubkeys.isEmpty()) { - if (primaryExpiration == null) { - return latestSubkeyExpirationDate; - } - if (latestSubkeyExpirationDate.before(primaryExpiration)) { - return latestSubkeyExpirationDate; - } - } - return primaryExpiration; - } - - public boolean isHardRevoked(@Nonnull CharSequence userId) { - PGPSignature revocation = signatures.userIdRevocations.get(userId.toString()); - if (revocation == null) { - return false; - } - RevocationReason revocationReason = revocation.getHashedSubPackets().getRevocationReason(); - return revocationReason == null || RevocationAttributes.Reason.isHardRevocation(revocationReason.getRevocationReason()); - } - - /** - * Return true if the key ring is a {@link PGPSecretKeyRing}. - * If it is a {@link PGPPublicKeyRing} return false and if it is neither, throw an {@link AssertionError}. - * - * @return true if the key ring is a secret key ring. - */ - public boolean isSecretKey() { - if (keys instanceof PGPSecretKeyRing) { - return true; - } else if (keys instanceof PGPPublicKeyRing) { - return false; - } else { - throw new AssertionError("Expected PGPKeyRing to be either PGPPublicKeyRing or PGPSecretKeyRing, but got " + keys.getClass().getName() + " instead."); - } - } - - /** - * Returns true when every secret key on the key ring is not encrypted. - * If there is at least one encrypted secret key on the key ring, returns false. - * If the key ring is a {@link PGPPublicKeyRing}, returns true. - * Sub-keys with S2K of a type GNU_DUMMY_S2K do not affect the result. - * - * @return true if all secret keys are unencrypted. - */ - public boolean isFullyDecrypted() { - if (!isSecretKey()) { - return true; - } - for (PGPSecretKey secretKey : getSecretKeys()) { - if (!KeyInfo.hasDummyS2K(secretKey) && KeyInfo.isEncrypted(secretKey)) { - return false; - } - } - return true; - } - - /** - * Returns true when every secret key on the key ring is encrypted. - * If there is at least one not encrypted secret key on the key ring, returns false. - * If the key ring is a {@link PGPPublicKeyRing}, returns false. - * Sub-keys with S2K of a type GNU_DUMMY_S2K do not affect a result. - * - * @return true if all secret keys are encrypted. - */ - public boolean isFullyEncrypted() { - if (!isSecretKey()) { - return false; - } - for (PGPSecretKey secretKey : getSecretKeys()) { - if (!KeyInfo.hasDummyS2K(secretKey) && KeyInfo.isDecrypted(secretKey)) { - return false; - } - } - return true; - } - - /** - * Return the version number of the public keys format. - * - * @return version - */ - public int getVersion() { - return keys.getPublicKey().getVersion(); - } - - /** - * Return a list of all subkeys which can be used for encryption of the given purpose. - * This list does not include expired or revoked keys. - * - * @param purpose purpose (encrypt data at rest / communications) - * @return encryption subkeys - */ - @Nonnull - public List getEncryptionSubkeys(@Nonnull EncryptionPurpose purpose) { - Date primaryExpiration = getPrimaryKeyExpirationDate(); - if (primaryExpiration != null && primaryExpiration.before(referenceDate)) { - LOGGER.debug("Certificate is expired: Primary key is expired on " + DateUtil.formatUTCDate(primaryExpiration)); - return Collections.emptyList(); - } - - Iterator subkeys = keys.getPublicKeys(); - List encryptionKeys = new ArrayList<>(); - while (subkeys.hasNext()) { - PGPPublicKey subKey = subkeys.next(); - - if (!isKeyValidlyBound(subKey.getKeyID())) { - LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is not validly bound."); - continue; - } - - Date subkeyExpiration = getSubkeyExpirationDate(OpenPgpFingerprint.of(subKey)); - if (subkeyExpiration != null && subkeyExpiration.before(referenceDate)) { - LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is expired on " + DateUtil.formatUTCDate(subkeyExpiration)); - continue; - } - - if (!subKey.isEncryptionKey()) { - LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " algorithm is not capable of encryption."); - continue; - } - - List keyFlags = getKeyFlagsOf(subKey.getKeyID()); - switch (purpose) { - case COMMUNICATIONS: - if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS)) { - encryptionKeys.add(subKey); - } - break; - case STORAGE: - if (keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) { - encryptionKeys.add(subKey); - } - break; - case ANY: - if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) { - encryptionKeys.add(subKey); - } - break; - } - } - return encryptionKeys; - } - - /** - * Return a list of all subkeys that could potentially be used to decrypt a message. - * Contrary to {@link #getEncryptionSubkeys(EncryptionPurpose)}, this method also includes revoked, expired keys, - * as well as keys which do not carry any encryption keyflags. - * Merely keys which use algorithms that cannot be used for encryption at all are excluded. - * That way, decryption of messages produced by faulty implementations can still be decrypted. - * - * @return decryption keys - */ - @Nonnull - public List getDecryptionSubkeys() { - Iterator subkeys = keys.getPublicKeys(); - List decryptionKeys = new ArrayList<>(); - - while (subkeys.hasNext()) { - PGPPublicKey subKey = subkeys.next(); - - // subkeys have been valid at some point - if (subKey.getKeyID() != getKeyId()) { - PGPSignature binding = signatures.subkeyBindings.get(subKey.getKeyID()); - if (binding == null) { - LOGGER.debug("Subkey " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " was never validly bound."); - continue; - } - } - - // Public-Key algorithm can encrypt - if (!subKey.isEncryptionKey()) { - LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is not encryption-capable."); - continue; - } - - decryptionKeys.add(subKey); - } - return decryptionKeys; - } - - /** - * Return a list of all keys which carry the provided key flag in their signature. - * - * @param flag flag - * @return keys with flag - */ - @Nonnull - public List getKeysWithKeyFlag(@Nonnull KeyFlag flag) { - List keysWithFlag = new ArrayList<>(); - for (PGPPublicKey key : getPublicKeys()) { - List keyFlags = getKeyFlagsOf(key.getKeyID()); - if (keyFlags.contains(flag)) { - keysWithFlag.add(key); - } - } - - return keysWithFlag; - } - - /** - * Return a list of all subkeys that can be used for encryption with the given user-id. - * This list does not include expired or revoked keys. - * TODO: Does it make sense to pass in a user-id? - * Aren't the encryption subkeys the same, regardless of which user-id is used? - * - * @param userId user-id - * @param purpose encryption purpose - * @return encryption subkeys - */ - @Nonnull - public List getEncryptionSubkeys(@Nullable CharSequence userId, - @Nonnull EncryptionPurpose purpose) { - if (userId != null && !isUserIdValid(userId)) { - throw new KeyException.UnboundUserIdException( - OpenPgpFingerprint.of(keys), - userId.toString(), - getLatestUserIdCertification(userId), - getUserIdRevocation(userId) - ); - } - - return getEncryptionSubkeys(purpose); - } - - /** - * Return a list of all subkeys which can be used to sign data. - * - * @return signing keys - */ - @Nonnull - public List getSigningSubkeys() { - Iterator subkeys = keys.getPublicKeys(); - List signingKeys = new ArrayList<>(); - while (subkeys.hasNext()) { - PGPPublicKey subKey = subkeys.next(); - - if (!isKeyValidlyBound(subKey.getKeyID())) { - continue; - } - - List keyFlags = getKeyFlagsOf(subKey.getKeyID()); - if (keyFlags.contains(KeyFlag.SIGN_DATA)) { - signingKeys.add(subKey); - } - } - return signingKeys; - } - - @Nonnull - public Set getPreferredHashAlgorithms() { - return getPreferredHashAlgorithms(getPrimaryUserId()); - } - - @Nonnull - public Set getPreferredHashAlgorithms(@Nullable CharSequence userId) { - return getKeyAccessor(userId, getKeyId()).getPreferredHashAlgorithms(); - } - - @Nonnull - public Set getPreferredHashAlgorithms(long keyId) { - return new KeyAccessor.SubKey(this, new SubkeyIdentifier(keys, keyId)) - .getPreferredHashAlgorithms(); - } - - @Nonnull - public Set getPreferredSymmetricKeyAlgorithms() { - return getPreferredSymmetricKeyAlgorithms(getPrimaryUserId()); - } - - @Nonnull - public Set getPreferredSymmetricKeyAlgorithms(@Nullable CharSequence userId) { - return getKeyAccessor(userId, getKeyId()).getPreferredSymmetricKeyAlgorithms(); - } - - @Nonnull - public Set getPreferredSymmetricKeyAlgorithms(long keyId) { - return new KeyAccessor.SubKey(this, new SubkeyIdentifier(keys, keyId)).getPreferredSymmetricKeyAlgorithms(); - } - - @Nonnull - public Set getPreferredCompressionAlgorithms() { - return getPreferredCompressionAlgorithms(getPrimaryUserId()); - } - - @Nonnull - public Set getPreferredCompressionAlgorithms(@Nullable CharSequence userId) { - return getKeyAccessor(userId, getKeyId()).getPreferredCompressionAlgorithms(); - } - - @Nonnull - public Set getPreferredCompressionAlgorithms(long keyId) { - return new KeyAccessor.SubKey(this, new SubkeyIdentifier(keys, keyId)).getPreferredCompressionAlgorithms(); - } - - public boolean isUsableForThirdPartyCertification() { - return isKeyValidlyBound(getKeyId()) && getKeyFlagsOf(getKeyId()).contains(KeyFlag.CERTIFY_OTHER); - } - - /** - * Returns true, if the certificate has at least one usable encryption subkey. - * - * @return true if usable for encryption - */ - public boolean isUsableForEncryption() { - return isUsableForEncryption(EncryptionPurpose.ANY); - } - - /** - * Returns true, if the certificate has at least one usable encryption subkey for the given purpose. - * - * @param purpose purpose of encryption - * @return true if usable for encryption - */ - public boolean isUsableForEncryption(@Nonnull EncryptionPurpose purpose) { - return isKeyValidlyBound(getKeyId()) && !getEncryptionSubkeys(purpose).isEmpty(); - } - - /** - * Returns true, if the key ring is capable of signing. - * Contrary to {@link #isUsableForSigning()}, this method also returns true, if this {@link KeyRingInfo} is based - * on a key ring which has at least one valid public key marked for signing. - * The secret key is not required for the key ring to qualify as signing capable. - * - * @return true if key corresponding to the cert is capable of signing - */ - public boolean isSigningCapable() { - // check if primary-key is revoked / expired - if (!isKeyValidlyBound(getKeyId())) { - return false; - } - // check if it has signing-capable key - return !getSigningSubkeys().isEmpty(); - } - - /** - * Returns true, if this {@link KeyRingInfo} is based on a {@link PGPSecretKeyRing}, which has a valid signing key - * which is ready to be used (i.e. secret key is present and is not on a smart-card). - *

- * If you just want to check, whether a key / certificate has signing capable subkeys, - * use {@link #isSigningCapable()} instead. - * - * @return true if key is ready to be used for signing - */ - public boolean isUsableForSigning() { - if (!isSigningCapable()) { - return false; - } - - List signingKeys = getSigningSubkeys(); - for (PGPPublicKey pk : signingKeys) { - return isSecretKeyAvailable(pk.getKeyID()); - } - // No usable secret key found - return false; - } - - public boolean isSecretKeyAvailable(long keyId) { - PGPSecretKey sk = getSecretKey(keyId); - if (sk == null) { - // Missing secret key - return false; - } - S2K s2K = sk.getS2K(); - // Unencrypted key - if (s2K == null) { - return true; - } - - // Secret key on smart-card - int s2kType = s2K.getType(); - if (s2kType >= 100 && s2kType <= 110) { - return false; - } - // protected secret key - return true; - } - - private KeyAccessor getKeyAccessor(@Nullable CharSequence userId, long keyID) { - if (getPublicKey(keyID) == null) { - throw new NoSuchElementException("No subkey with key id " + Long.toHexString(keyID) + " found on this key."); - } - - if (userId != null && !getUserIds().contains(userId.toString())) { - throw new NoSuchElementException("No user-id '" + userId + "' found on this key."); - } - - if (userId != null) { - return new KeyAccessor.ViaUserId(this, new SubkeyIdentifier(keys, keyID), userId.toString()); - } else { - return new KeyAccessor.ViaKeyId(this, new SubkeyIdentifier(keys, keyID)); - } - } - - public static class Signatures { - - private final PGPSignature primaryKeyRevocation; - private final PGPSignature primaryKeySelfSignature; - private final Map userIdRevocations; - private final Map userIdCertifications; - private final Map subkeyRevocations; - private final Map subkeyBindings; - - public Signatures(@Nonnull PGPKeyRing keyRing, - @Nonnull Date referenceDate, - @Nonnull Policy policy) { - primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, policy, referenceDate); - primaryKeySelfSignature = SignaturePicker.pickLatestDirectKeySignature(keyRing, policy, referenceDate); - userIdRevocations = new HashMap<>(); - userIdCertifications = new HashMap<>(); - subkeyRevocations = new HashMap<>(); - subkeyBindings = new HashMap<>(); - - List userIds = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(keyRing.getPublicKey()); - for (String userId : userIds) { - PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keyRing, userId, policy, referenceDate); - if (revocation != null) { - userIdRevocations.put(userId, revocation); - } - PGPSignature certification = SignaturePicker.pickLatestUserIdCertificationSignature(keyRing, userId, policy, referenceDate); - if (certification != null) { - userIdCertifications.put(userId, certification); - } - } - - Iterator keys = keyRing.getPublicKeys(); - keys.next(); // Skip primary key - while (keys.hasNext()) { - PGPPublicKey subkey = keys.next(); - PGPSignature subkeyRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keyRing, subkey, policy, referenceDate); - if (subkeyRevocation != null) { - subkeyRevocations.put(subkey.getKeyID(), subkeyRevocation); - } - PGPSignature subkeyBinding = SignaturePicker.pickLatestSubkeyBindingSignature(keyRing, subkey, policy, referenceDate); - if (subkeyBinding != null) { - subkeyBindings.put(subkey.getKeyID(), subkeyBinding); - } - } - } - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/package-info.java deleted file mode 100644 index 9f33dd40..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Extract information from PGPKeyRings. - */ -package org.pgpainless.key.info; diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt index ccbb8f45..16f61304 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -5,6 +5,7 @@ package org.bouncycastle.extensions import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.algorithm.RevocationState import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.signature.SignatureUtils import java.util.* @@ -44,3 +45,9 @@ fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint) = SignatureUtils.w * Return true, if this signature is a hard revocation. */ fun PGPSignature.isHardRevocation() = SignatureUtils.isHardRevocation(this) + +fun PGPSignature?.toRevocationState() = + if (this == null) RevocationState.notRevoked() + else + if (isHardRevocation()) RevocationState.hardRevoked() + else RevocationState.softRevoked(creationTime) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt new file mode 100644 index 00000000..79cc8683 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt @@ -0,0 +1,765 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.info + +import openpgp.openPgpKeyId +import org.bouncycastle.extensions.* +import org.bouncycastle.openpgp.* +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.* +import org.pgpainless.exception.KeyException.UnboundUserIdException +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.key.util.KeyRingUtils +import org.pgpainless.policy.Policy +import org.pgpainless.signature.SignatureUtils +import org.pgpainless.signature.SignatureUtils.Companion.isHardRevocation +import org.pgpainless.signature.SignatureUtils.Companion.isSignatureExpired +import org.pgpainless.signature.consumer.SignaturePicker +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil.Companion.getKeyExpirationTimeAsDate +import org.pgpainless.util.DateUtil +import org.slf4j.LoggerFactory +import java.security.Key +import java.util.* +import kotlin.NoSuchElementException + +class KeyRingInfo( + val keys: PGPKeyRing, + val policy: Policy = PGPainless.getPolicy(), + val referenceDate: Date = Date()) { + + @JvmOverloads + constructor(keys: PGPKeyRing, referenceDate: Date = Date()): this(keys, PGPainless.getPolicy(), referenceDate) + + private val signatures: Signatures = Signatures(keys, referenceDate, policy) + + /** + * Primary {@link PGPPublicKey}.´ + */ + val publicKey: PGPPublicKey = KeyRingUtils.requirePrimaryPublicKeyFrom(keys) + + /** + * Primary key ID. + */ + val keyId: Long = publicKey.keyID + + /** + * Primary key fingerprint. + */ + val fingerprint: OpenPgpFingerprint = OpenPgpFingerprint.of(keys) + + /** + * All User-IDs (valid, expired, revoked). + */ + val userIds: List = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey) + + /** + * Primary User-ID. + */ + val primaryUserId = findPrimaryUserId() + + /** + * Revocation State. + */ + val revocationState = signatures.primaryKeyRevocation.toRevocationState() + /** + * Return the date on which the primary key was revoked, or null if it has not yet been revoked. + * + * @return revocation date or null + */ + val revocationDate: Date? = if (revocationState.isSoftRevocation()) revocationState.date else null + + /** + * Primary [PGPSecretKey] of this key ring or null if the key ring is not a [PGPSecretKeyRing]. + */ + val secretKey: PGPSecretKey? = when(keys) { + is PGPSecretKeyRing -> keys.secretKey!! + else -> null + } + + /** + * OpenPGP key version. + */ + val version: Int = publicKey.version + + /** + * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. + * The first key in the list being the primary key. + * Note that the list is unmodifiable. + * + * @return list of public keys + */ + val publicKeys: List = keys.publicKeys.asSequence().toList() + + /** + * All secret keys. + * If the key ring is a [PGPPublicKeyRing], then return an empty list. + */ + val secretKeys: List = when(keys) { + is PGPSecretKeyRing -> keys.secretKeys.asSequence().toList() + else -> listOf() + } + + /** + * List of valid public subkeys. + */ + val validSubkeys: List = keys.publicKeys.asSequence() + .filter { isKeyValidlyBound(it.keyID) } + .toList() + + /** + * List of valid user-IDs. + */ + val validUserIds: List = userIds.filter { isUserIdBound(it) } + + /** + * List of valid and expired user-IDs. + */ + val validAndExpiredUserIds: List = userIds.filter { + val certification = signatures.userIdCertifications[it] ?: return@filter false + val revocation = signatures.userIdRevocations[it] ?: return@filter true + return@filter !revocation.isHardRevocation() && certification.creationTime > revocation.creationTime + } + + /** + * List of email addresses that can be extracted from the user-IDs. + */ + val emailAddresses: List = userIds.mapNotNull { + PATTERN_EMAIL_FROM_USERID.matcher(it).let { m1 -> + if (m1.find()) m1.group(1) + else PATTERN_EMAIL_EXPLICIT.matcher(it).let { m2 -> + if(m2.find()) m2.group(1) else null + } + } + } + + /** + * Newest direct-key self-signature on the primary key. + */ + val latestDirectKeySelfSignature: PGPSignature? = signatures.primaryKeySelfSignature + + /** + * Newest primary-key revocation self-signature. + */ + val revocationSelfSignature: PGPSignature? = signatures.primaryKeyRevocation + + /** + * Public-key encryption-algorithm of the primary key. + */ + val algorithm: PublicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(publicKey.algorithm) + + /** + * Creation date of the primary key. + */ + val creationDate: Date = publicKey.creationTime!! + + /** + * Latest date at which the key was modified (either by adding a subkey or self-signature). + */ + val lastModified: Date = getMostRecentSignature()?.creationTime ?: getLatestKeyCreationDate() + + /** + * True, if the underlying keyring is a [PGPSecretKeyRing]. + */ + val isSecretKey: Boolean = keys is PGPSecretKeyRing + + /** + * True, if there are no encrypted secret keys. + */ + val isFullyDecrypted: Boolean = !isSecretKey || secretKeys.all { it.hasDummyS2K() || it.isDecrypted() } + + /** + * True, if there are only encrypted secret keys. + */ + val isFullyEncrypted: Boolean = isSecretKey && secretKeys.none { !it.hasDummyS2K() && it.isDecrypted() } + + /** + * List of public keys, whose secret key counterparts can be used to decrypt messages. + */ + val decryptionSubkeys: List = keys.publicKeys.asSequence().filter { + if (it.keyID != keyId) { + if (signatures.subkeyBindings[it.keyID] == null) { + LOGGER.debug("Subkey ${it.keyID.openPgpKeyId()} has no binding signature.") + return@filter false + } + } + if (!it.isEncryptionKey) { + LOGGER.debug("(Sub-?)Key ${it.keyID.openPgpKeyId()} is not encryption-capable.") + return@filter false + } + return@filter true + }.toList() + + /** + * Expiration date of the primary key. + */ + val primaryKeyExpirationDate: Date? + get() { + val directKeyExpirationDate: Date? = latestDirectKeySelfSignature?.let { getKeyExpirationTimeAsDate(it, publicKey) } + val possiblyExpiredPrimaryUserId = getPossiblyExpiredPrimaryUserId() + val primaryUserIdCertification = possiblyExpiredPrimaryUserId?.let { getLatestUserIdCertification(it) } + val userIdExpirationDate: Date? = primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) } + + if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) { + throw NoSuchElementException("No direct-key signature and no user-id signature found.") + } + if (directKeyExpirationDate != null && userIdExpirationDate == null) { + return directKeyExpirationDate + } + if (directKeyExpirationDate == null) { + return userIdExpirationDate + } + return if (directKeyExpirationDate < userIdExpirationDate) + directKeyExpirationDate + else userIdExpirationDate + } + + /** + * Return the expiration date of the subkey with the provided fingerprint. + * + * @param fingerprint subkey fingerprint + * @return expiration date or null + */ + fun getSubkeyExpirationDate(fingerprint: OpenPgpFingerprint): Date? { + return getSubkeyExpirationDate(fingerprint.keyId) + } + + /** + * Return the expiration date of the subkey with the provided keyId. + * + * @param keyId subkey keyId + * @return expiration date + */ + fun getSubkeyExpirationDate(keyId: Long): Date? { + if (publicKey.keyID == keyId) return primaryKeyExpirationDate + val subkey = getPublicKey(keyId) ?: throw NoSuchElementException("No subkey with key-ID ${keyId.openPgpKeyId()} found.") + val bindingSig = getCurrentSubkeyBindingSignature(keyId) ?: throw AssertionError("Subkey has no valid binding signature.") + return SignatureUtils.getKeyExpirationDate(subkey.creationTime, bindingSig) + } + + /** + * Return the date after which the key can no longer be used to perform the given use-case, caused by expiration. + * + * @return expiration date for the given use-case + */ + fun getExpirationDateForUse(use: KeyFlag): Date? { + require(use != KeyFlag.SPLIT && use != KeyFlag.SHARED) { + "SPLIT and SHARED are not uses, but properties." + } + + val primaryKeyExpiration = primaryKeyExpirationDate + val keysWithFlag: List = getKeysWithKeyFlag(use) + if (keysWithFlag.isEmpty()) throw NoSuchElementException("No key with the required key flag found.") + + var nonExpiring = false + val latestSubkeyExpiration = keysWithFlag.map { key -> + getSubkeyExpirationDate(key.keyID).also { if (it == null) nonExpiring = true } + }.filterNotNull().maxByOrNull { it } + + if (nonExpiring) return primaryKeyExpiration + return if (primaryKeyExpiration == null) latestSubkeyExpiration + else if (latestSubkeyExpiration == null) + primaryKeyExpiration + else minOf(primaryKeyExpiration, latestSubkeyExpiration) + } + + /** + * Return true, if the given user-ID is hard-revoked. + * + * @return true, if the given user-ID is hard-revoked. + */ + fun isHardRevoked(userId: CharSequence): Boolean { + return signatures.userIdRevocations[userId]?.isHardRevocation() ?: false + } + + /** + * Return a list of all keys which carry the provided key flag in their signature. + * + * @param flag flag + * @return keys with flag + */ + fun getKeysWithKeyFlag(flag: KeyFlag): List = publicKeys.filter { getKeyFlagsOf(it.keyID).contains(flag) } + + /** + * Return a list of all subkeys which can be used to encrypt a message for the given user-ID. + * + * @return encryption subkeys + */ + fun getEncryptionSubkeys(userId: CharSequence?, purpose: EncryptionPurpose): List { + if (userId != null && !isUserIdValid(userId)) { + throw UnboundUserIdException(OpenPgpFingerprint.of(keys), userId.toString(), + getLatestUserIdCertification(userId), getUserIdRevocation(userId)) + } + return getEncryptionSubkeys(purpose) + } + + /** + * Return a list of all subkeys which can be used to encrypt a message, given the purpose. + * + * @return subkeys which can be used for encryption + */ + fun getEncryptionSubkeys(purpose: EncryptionPurpose): List { + primaryKeyExpirationDate?.let { + if (it < referenceDate) { + LOGGER.debug("Certificate is expired: Primary key is expired on ${DateUtil.formatUTCDate(it)}") + return listOf() + } + } + + return keys.publicKeys.asSequence().filter { + if (!isKeyValidlyBound(it.keyID)) { + LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is not validly bound.") + return@filter false + } + + getSubkeyExpirationDate(it.keyID)?.let { exp -> + if (exp < referenceDate) { + LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is expired on ${DateUtil.formatUTCDate(exp)}.") + return@filter false + } + } + + if (!it.isEncryptionKey) { + LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} algorithm is not capable of encryption.") + return@filter false + } + + val keyFlags = getKeyFlagsOf(it.keyID) + when (purpose) { + EncryptionPurpose.COMMUNICATIONS -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) + EncryptionPurpose.STORAGE -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_STORAGE) + EncryptionPurpose.ANY -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || keyFlags.contains(KeyFlag.ENCRYPT_STORAGE) + } + }.toList() + } + + /** + * List of all subkeys that can be used to sign a message. + */ + val signingSubkeys: List = validSubkeys.filter { getKeyFlagsOf(it.keyID).contains(KeyFlag.SIGN_DATA) } + + /** + * Whether the key is usable for encryption. + */ + val isUsableForEncryption: Boolean = isUsableForEncryption(EncryptionPurpose.ANY) + + /** + * Return, whether the key is usable for encryption, given the purpose. + * + * @return true, if the key can be used to encrypt a message according to the encryption-purpose. + */ + fun isUsableForEncryption(purpose: EncryptionPurpose): Boolean { + return isKeyValidlyBound(keyId) && getEncryptionSubkeys(purpose).isNotEmpty() + } + + /** + * Whether the key is capable of signing messages. + * This field is also true, if the key contains a subkey that is capable of signing messages, but where the secret + * key is unavailable, e.g. because it was moved to a smart-card. + * + * To check for keys that are actually usable to sign messages, use [isUsableForSigning]. + */ + val isSigningCapable: Boolean = isKeyValidlyBound(keyId) && signingSubkeys.isNotEmpty() + + /** + * Whether the key is actually usable to sign messages. + */ + val isUsableForSigning: Boolean = isSigningCapable && signingSubkeys.any { isSecretKeyAvailable(it.keyID) } + + /** + * Return the primary user-ID, even if it is possibly expired. + * + * @return possibly expired primary user-ID + */ + fun getPossiblyExpiredPrimaryUserId(): String? = primaryUserId ?: userIds + .mapNotNull { userId -> + getLatestUserIdCertification(userId)?.let { userId to it } + } + .sortedByDescending { it.second.creationTime } + .maxByOrNull { it.second.hashedSubPackets.isPrimaryUserID }?.first + + /** + * Return the most-recently created self-signature on the key. + */ + private fun getMostRecentSignature(): PGPSignature? = + setOfNotNull(latestDirectKeySelfSignature, revocationSelfSignature).asSequence() + .plus(signatures.userIdCertifications.values) + .plus(signatures.userIdRevocations.values) + .plus(signatures.subkeyBindings.values) + .plus(signatures.subkeyRevocations.values) + .maxByOrNull { creationDate } + /** + * Return the creation time of the latest added subkey. + * + * @return latest key creation time + */ + fun getLatestKeyCreationDate(): Date = validSubkeys.maxByOrNull { creationDate }?.creationTime + ?: throw AssertionError("Apparently there is no validly bound key in this key ring.") + + /** + * Return the latest certification self-signature for the given user-ID. + * + * @return latest self-certification for the given user-ID. + */ + fun getLatestUserIdCertification(userId: CharSequence): PGPSignature? = signatures.userIdCertifications[userId] + + /** + * Return the latest revocation self-signature for the given user-ID + * + * @return latest user-ID revocation for the given user-ID + */ + fun getUserIdRevocation(userId: CharSequence): PGPSignature? = signatures.userIdRevocations[userId] + + /** + * Return the current binding signature for the subkey with the given key-ID. + * + * @return current subkey binding signature + */ + fun getCurrentSubkeyBindingSignature(keyId: Long): PGPSignature? = signatures.subkeyBindings[keyId] + + /** + * Return the current revocation signature for the subkey with the given key-ID. + * + * @return current subkey revocation signature + */ + fun getSubkeyRevocationSignature(keyId: Long): PGPSignature? = signatures.subkeyRevocations[keyId] + + /** + * Return a list of {@link KeyFlag KeyFlags} that apply to the subkey with the provided key id. + * @param keyId key-id + * @return list of key flags + */ + fun getKeyFlagsOf(keyId: Long): List = + if (keyId == publicKey.keyID) { + latestDirectKeySelfSignature?.let { sig -> + SignatureSubpacketsUtil.parseKeyFlags(sig)?.let { flags -> + return flags + } + } + + primaryUserId?.let { + SignatureSubpacketsUtil.parseKeyFlags(getLatestUserIdCertification(it))?.let { flags -> + return flags + } + } + listOf() + } else { + getCurrentSubkeyBindingSignature(keyId)?.let { + SignatureSubpacketsUtil.parseKeyFlags(it)?.let { flags -> + return flags + } + } + listOf() + } + + /** + * Return a list of {@link KeyFlag KeyFlags} that apply to the given user-id. + * + * @param userId user-id + * @return key flags + */ + fun getKeyFlagsOf(userId: CharSequence): List = + if (!isUserIdValid(userId)) { + listOf() + } else { + getLatestUserIdCertification(userId)?.let { + SignatureSubpacketsUtil.parseKeyFlags(it) ?: listOf() + } ?: throw AssertionError("While user-id '$userId' was reported as valid, there appears to be no certification for it.") + } + + /** + * Return the public key with the given key id from the provided key ring. + * + * @param keyId key id + * @return public key or null + */ + fun getPublicKey(keyId: Long): PGPPublicKey? = keys.getPublicKey(keyId) + + /** + * Return the secret key with the given key id. + * + * @param keyId key id + * @return secret key or null + */ + fun getSecretKey(keyId: Long): PGPSecretKey? = when(keys) { + is PGPSecretKeyRing -> keys.getSecretKey(keyId) + else -> null + } + + /** + * Return true, if the secret-key with the given key-ID is available (i.e. not moved to a smart-card). + * + * @return availability of the secret key + */ + fun isSecretKeyAvailable(keyId: Long): Boolean { + return getSecretKey(keyId)?.let { + return if (it.s2K == null) true // Unencrypted key + else it.s2K.type !in 100..110 // Secret key on smart-card + } ?: false // Missing secret key + } + + /** + * Return the public key with the given fingerprint. + * + * @param fingerprint fingerprint + * @return public key or null + */ + fun getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = keys.getPublicKey(fingerprint.keyId) + + /** + * Return the secret key with the given fingerprint. + * + * @param fingerprint fingerprint + * @return secret key or null + */ + fun getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? = when(keys) { + is PGPSecretKeyRing -> keys.getSecretKey(fingerprint.keyId) + else -> null + } + + /** + * Return the public key matching the given [SubkeyIdentifier]. + * + * @return public key + * @throws IllegalArgumentException if the identifier's primary key does not match the primary key of the key. + */ + fun getPublicKey(identifier: SubkeyIdentifier): PGPPublicKey? { + require(identifier.primaryKeyId == publicKey.keyID) { + "Mismatching primary key ID." + } + return getPublicKey(identifier.subkeyId) + } + + /** + * Return the secret key matching the given [SubkeyIdentifier]. + * + * @return secret key + * @throws IllegalArgumentException if the identifier's primary key does not match the primary key of the key. + */ + fun getSecretKey(identifier: SubkeyIdentifier): PGPSecretKey? = when(keys) { + is PGPSecretKeyRing -> { + require(identifier.primaryKeyId == publicKey.keyID) { + "Mismatching primary key ID." + } + keys.getSecretKey(identifier.subkeyId) + } + else -> null + } + + /** + * Return true if the public key with the given key id is bound to the key ring properly. + * + * @param keyId key id + * @return true if key is bound validly + */ + fun isKeyValidlyBound(keyId: Long): Boolean { + val publicKey = keys.getPublicKey(keyId) ?: return false + + // Primary key -> Check Primary Key Revocation + if (publicKey.keyID == this.publicKey.keyID) { + return if (signatures.primaryKeyRevocation != null && isHardRevocation(signatures.primaryKeyRevocation)) { + false + } else signatures.primaryKeyRevocation == null + } + + // Else Subkey -> Check Subkey Revocation + val binding = signatures.subkeyBindings[keyId] + val revocation = signatures.subkeyRevocations[keyId] + + // No valid binding + if (binding == null || isSignatureExpired(binding)) { + return false + } + + // Revocation + return if (revocation != null) { + if (isHardRevocation(revocation)) { + // Subkey is hard revoked + false + } else { + // Key is soft-revoked, not yet re-bound + (isSignatureExpired(revocation) || !revocation.creationTime.after(binding.creationTime)) + } + } else true + } + + /** + * Return the current primary user-id of the key ring. + *

+ * Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, + * this method returns the first user-id on the key, otherwise null. + * + * @return primary user-id or null + */ + private fun findPrimaryUserId(): String? { + if (userIds.isEmpty()) { + return null + } + + return signatures.userIdCertifications.filter { (_, certification) -> + certification.hashedSubPackets.isPrimaryUserID + }.entries.maxByOrNull { (_, certification) -> + certification.creationTime + }?.key ?: signatures.userIdCertifications.keys.firstOrNull() + } + + /** + * Return true, if the primary user-ID, as well as the given user-ID are valid and bound. + */ + fun isUserIdValid(userId: CharSequence) = + if (primaryUserId == null) { + false + } else { + isUserIdBound(primaryUserId) && (if (userId == primaryUserId) true else isUserIdBound(userId)) + } + + /** + * Return true, if the given user-ID is validly bound. + */ + fun isUserIdBound(userId: CharSequence) = + signatures.userIdCertifications[userId]?.let { sig -> + if (sig.isExpired(referenceDate)) { + // certification expired + return false + } + if (sig.hashedSubPackets.isPrimaryUserID) { + SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(sig, publicKey)?.let { expirationDate -> + // key expired? + if (expirationDate < referenceDate) return false + } + } + signatures.userIdRevocations[userId]?.let { rev -> + if (rev.isHardRevocation()) { + return false // hard revoked -> invalid + } + sig.creationTime > rev.creationTime// re-certification after soft revocation? + } ?: true // certification, but no revocation + } ?: false // no certification + + /** + * [HashAlgorithm] preferences of the primary user-ID or if absent, of the primary key. + */ + val preferredHashAlgorithms: Set + get() = primaryUserId?.let { getPreferredHashAlgorithms(it) } ?: getPreferredHashAlgorithms(keyId) + + /** + * [HashAlgorithm] preferences of the given user-ID. + */ + fun getPreferredHashAlgorithms(userId: CharSequence): Set { + return getKeyAccessor(userId, keyId).preferredHashAlgorithms + } + + /** + * [HashAlgorithm] preferences of the given key. + */ + fun getPreferredHashAlgorithms(keyId: Long): Set { + return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredHashAlgorithms + } + + /** + * [SymmetricKeyAlgorithm] preferences of the primary user-ID or if absent of the primary key. + */ + val preferredSymmetricKeyAlgorithms: Set + get() = primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) } ?: getPreferredSymmetricKeyAlgorithms(keyId) + + /** + * [SymmetricKeyAlgorithm] preferences of the given user-ID. + */ + fun getPreferredSymmetricKeyAlgorithms(userId: CharSequence): Set { + return getKeyAccessor(userId, keyId).preferredSymmetricKeyAlgorithms + } + + /** + * [SymmetricKeyAlgorithm] preferences of the given key. + */ + fun getPreferredSymmetricKeyAlgorithms(keyId: Long): Set { + return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredSymmetricKeyAlgorithms + } + + /** + * [CompressionAlgorithm] preferences of the primary user-ID or if absent, the primary key. + */ + val preferredCompressionAlgorithms: Set + get() = primaryUserId?.let { getPreferredCompressionAlgorithms(it) } ?: getPreferredCompressionAlgorithms(keyId) + + /** + * [CompressionAlgorithm] preferences of the given user-ID. + */ + fun getPreferredCompressionAlgorithms(userId: CharSequence): Set { + return getKeyAccessor(userId, keyId).preferredCompressionAlgorithms + } + + /** + * [CompressionAlgorithm] preferences of the given key. + */ + fun getPreferredCompressionAlgorithms(keyId: Long): Set { + return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredCompressionAlgorithms + } + + val isUsableForThirdPartyCertification: Boolean = + isKeyValidlyBound(keyId) && getKeyFlagsOf(keyId).contains(KeyFlag.CERTIFY_OTHER) + + private fun getKeyAccessor(userId: CharSequence?, keyId: Long): KeyAccessor { + if (getPublicKey(keyId) == null) { + throw NoSuchElementException("No subkey with key-id ${keyId.openPgpKeyId()} found on this key.") + } + if (userId != null && !userIds.contains(userId)) { + throw NoSuchElementException("No user-id '$userId' found on this key.") + } + return if (userId != null) { + KeyAccessor.ViaUserId(this, SubkeyIdentifier(keys, keyId), userId) + } else { + KeyAccessor.ViaKeyId(this, SubkeyIdentifier(keys, keyId)) + } + } + + companion object { + + /** + * Evaluate the key for the given signature. + */ + @JvmStatic + fun evaluateForSignature(keys: PGPKeyRing, signature: PGPSignature) = KeyRingInfo(keys, signature.creationTime!!) + + private val PATTERN_EMAIL_FROM_USERID = "<([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)>".toPattern() + private val PATTERN_EMAIL_EXPLICIT = "^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$".toPattern() + + @JvmStatic + private val LOGGER = LoggerFactory.getLogger(KeyRingInfo::class.java) + + } + + private class Signatures( + val keys: PGPKeyRing, + val referenceDate: Date, + val policy: Policy) { + val primaryKeyRevocation: PGPSignature? = SignaturePicker.pickCurrentRevocationSelfSignature(keys, policy, referenceDate) + val primaryKeySelfSignature: PGPSignature? = SignaturePicker.pickLatestDirectKeySignature(keys, policy, referenceDate) + val userIdRevocations = mutableMapOf() + val userIdCertifications = mutableMapOf() + val subkeyRevocations = mutableMapOf() + val subkeyBindings = mutableMapOf() + + init { + KeyRingUtils.getUserIdsIgnoringInvalidUTF8(keys.publicKey).forEach { userId -> + SignaturePicker.pickCurrentUserIdRevocationSignature(keys, userId, policy, referenceDate)?.let { + userIdRevocations[userId] = it + } + SignaturePicker.pickLatestUserIdCertificationSignature(keys, userId, policy, referenceDate)?.let { + userIdCertifications[userId] = it + } + } + keys.publicKeys.asSequence().drop(1).forEach { subkey -> + SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keys, subkey, policy, referenceDate)?.let { + subkeyRevocations[subkey.keyID] = it + } + SignaturePicker.pickLatestSubkeyBindingSignature(keys, subkey, policy, referenceDate)?.let { + subkeyBindings[subkey.keyID] = it + } + } + } + } +} \ No newline at end of file From 19063454cb37b6ab73881c44d403a1213f797dea Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Sep 2023 14:35:29 +0200 Subject: [PATCH 110/155] Add PGPSecretKey.unlock() methods --- .../extensions/PGPSecretKeyExtensions.kt | 43 +++++++++++++++++++ .../OpenPgpMessageInputStream.kt | 8 ++-- .../encryption_signing/SigningOptions.kt | 10 ++--- .../key/generation/KeyRingBuilder.kt | 4 +- .../key/protection/fixes/S2KUsageFix.kt | 4 +- .../org/pgpainless/key/util/KeyRingUtils.kt | 6 ++- 6 files changed, 60 insertions(+), 15 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt index cba7bdba..f5d4f522 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt @@ -5,7 +5,50 @@ package org.bouncycastle.extensions import org.bouncycastle.bcpg.S2K +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor +import org.pgpainless.exception.KeyIntegrityException +import org.pgpainless.exception.WrongPassphraseException +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.protection.UnlockSecretKey +import org.pgpainless.util.Passphrase + +/** + * Unlock the secret key to get its [PGPPrivateKey]. + * + * @param passphrase passphrase to unlock the secret key with. + * @throws PGPException if the key cannot be unlocked + * @throws KeyIntegrityException if the public key part was tampered with + * @throws WrongPassphraseException + */ +@Throws(PGPException::class, KeyIntegrityException::class) +fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey = + UnlockSecretKey.unlockSecretKey(this, passphrase) + +/** + * Unlock the secret key to get its [PGPPrivateKey]. + * + * @param protector protector to unlock the secret key. + * @throws PGPException if the key cannot be unlocked + * @throws KeyIntegrityException if the public key part was tampered with + */ +@Throws(PGPException::class, KeyIntegrityException::class) +@JvmOverloads +fun PGPSecretKey.unlock(protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()): PGPPrivateKey = + UnlockSecretKey.unlockSecretKey(this, protector) + +/** + * Unlock the secret key to get its [PGPPrivateKey]. + * + * @param decryptor decryptor to unlock the secret key. + * @throws PGPException if the key cannot be unlocked + * @throws KeyIntegrityException if the public key part was tampered with + */ +@Throws(PGPException::class, KeyIntegrityException::class) +fun PGPSecretKey.unlock(decryptor: PBESecretKeyDecryptor?): PGPPrivateKey = + UnlockSecretKey.unlockSecretKey(this, decryptor) /** * Returns indication that the secret key is encrypted. diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 87b0847a..11d5675f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -7,6 +7,7 @@ package org.pgpainless.decryption_verification import openpgp.openPgpKeyId import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.UnsupportedPacketVersionException +import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory @@ -21,7 +22,6 @@ import org.pgpainless.decryption_verification.syntax_check.StackSymbol import org.pgpainless.exception.* import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.SubkeyIdentifier -import org.pgpainless.key.protection.UnlockSecretKey import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.policy.Policy import org.pgpainless.signature.SignatureUtils @@ -302,7 +302,7 @@ class OpenPgpMessageInputStream( continue } - val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + val privateKey = secretKey.unlock(protector) if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { return true } @@ -325,7 +325,7 @@ class OpenPgpMessageInputStream( continue } - val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + val privateKey = secretKey.unlock(protector) if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { return true } @@ -351,7 +351,7 @@ class OpenPgpMessageInputStream( LOGGER.debug("Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.") val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue - val privateKey = UnlockSecretKey.unlockSecretKey(secretKey, protector) + val privateKey = secretKey.unlock(protector) if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { return true } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt index b9208ed5..512e074b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt @@ -4,6 +4,7 @@ package org.pgpainless.encryption_signing +import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.pgpainless.PGPainless.Companion.getPolicy import org.pgpainless.PGPainless.Companion.inspectKeyRing @@ -17,7 +18,6 @@ import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.OpenPgpFingerprint.Companion.of import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.protection.SecretKeyRingProtector -import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey import org.pgpainless.policy.Policy import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback import org.pgpainless.signature.subpackets.SignatureSubpackets @@ -157,7 +157,7 @@ class SigningOptions { for (signingPubKey in signingPubKeys) { val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) - val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val hashAlgorithms = if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) @@ -199,7 +199,7 @@ class SigningOptions { val signingSecKey = signingKey.getSecretKey(signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) - val signingSubkey = unlockSecretKey(signingSecKey, signingKeyProtector) + val signingSubkey = signingSecKey.unlock(signingKeyProtector) val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) @@ -292,7 +292,7 @@ class SigningOptions { for (signingPubKey in signingPubKeys) { val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) - val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val hashAlgorithms = if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) @@ -332,7 +332,7 @@ class SigningOptions { if (signingPubKey.keyID == keyId) { val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) - val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketsCallback) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt index 2e4a6642..530f1e55 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -4,6 +4,7 @@ package org.pgpainless.key.generation +import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor @@ -14,7 +15,6 @@ import org.pgpainless.PGPainless import org.pgpainless.algorithm.KeyFlag import org.pgpainless.algorithm.SignatureType import org.pgpainless.implementation.ImplementationFactory -import org.pgpainless.key.protection.UnlockSecretKey import org.pgpainless.policy.Policy import org.pgpainless.provider.ProviderFactory import org.pgpainless.signature.subpackets.SelfSignatureSubpackets @@ -129,7 +129,7 @@ class KeyRingBuilder : KeyRingBuilderInterface { // Attempt to add additional user-ids to the primary public key var primaryPubKey = secretKeys.next().publicKey - val privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.secretKey, secretKeyDecryptor) + val privateKey = secretKeyRing.secretKey.unlock(secretKeyDecryptor) val userIdIterator = userIds.entries.iterator() if (userIdIterator.hasNext()) { userIdIterator.next() // Skip primary userId diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt index aeef0654..c02486ac 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt @@ -5,13 +5,13 @@ package org.pgpainless.key.protection.fixes import org.bouncycastle.bcpg.SecretKeyPacket +import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKeyRing import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.exception.WrongPassphraseException import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.protection.SecretKeyRingProtector -import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey /** * Repair class to fix keys which use S2K usage of value [SecretKeyPacket.USAGE_CHECKSUM]. @@ -62,7 +62,7 @@ class S2KUsageFix { throw WrongPassphraseException("Missing passphrase for key with ID " + java.lang.Long.toHexString(keyId)) } - val privateKey = unlockSecretKey(key, protector) + val privateKey = key.unlock(protector) // This constructor makes use of USAGE_SHA1 by default val fixedKey = PGPSecretKey( privateKey, diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt index d3c77ab6..15132f9a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt @@ -8,12 +8,12 @@ import openpgp.openPgpKeyId import org.bouncycastle.bcpg.S2K import org.bouncycastle.bcpg.SecretKeyPacket import org.bouncycastle.extensions.certificate +import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.bouncycastle.util.Strings import org.pgpainless.exception.MissingPassphraseException import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.protection.SecretKeyRingProtector -import org.pgpainless.key.protection.UnlockSecretKey import org.pgpainless.key.protection.fixes.S2KUsageFix import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -164,8 +164,10 @@ class KeyRingUtils { * @throws PGPException if something goes wrong (e.g. wrong passphrase) */ @JvmStatic + @Deprecated("Deprecated in favor of secretKey.unlock(protector)", + ReplaceWith("secretKey.unlock(protector)")) fun unlockSecretKey(secretKey: PGPSecretKey, protector: SecretKeyRingProtector): PGPPrivateKey { - return UnlockSecretKey.unlockSecretKey(secretKey, protector) + return secretKey.unlock(protector) } /** From a0b01f121a9c0d54aeaf6fc4e43d292d99dda496 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Sep 2023 14:36:42 +0200 Subject: [PATCH 111/155] Remove KeyRingUtils.unlockSecretKey() --- .../org/pgpainless/key/util/KeyRingUtils.kt | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt index 15132f9a..22e78410 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt @@ -8,7 +8,6 @@ import openpgp.openPgpKeyId import org.bouncycastle.bcpg.S2K import org.bouncycastle.bcpg.SecretKeyPacket import org.bouncycastle.extensions.certificate -import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.bouncycastle.util.Strings import org.pgpainless.exception.MissingPassphraseException @@ -154,22 +153,6 @@ class KeyRingUtils { .toList()) } - /** - * Unlock a {@link PGPSecretKey} and return the resulting {@link PGPPrivateKey}. - * - * @param secretKey secret key - * @param protector protector to unlock the secret key - * @return private key - * - * @throws PGPException if something goes wrong (e.g. wrong passphrase) - */ - @JvmStatic - @Deprecated("Deprecated in favor of secretKey.unlock(protector)", - ReplaceWith("secretKey.unlock(protector)")) - fun unlockSecretKey(secretKey: PGPSecretKey, protector: SecretKeyRingProtector): PGPPrivateKey { - return secretKey.unlock(protector) - } - /** * Create a new {@link PGPPublicKeyRingCollection} from an array of {@link PGPPublicKeyRing PGPPublicKeyRings}. * From de3ea580e3dd9538072fb28db14b11eb4fb66ca7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Sep 2023 15:01:26 +0200 Subject: [PATCH 112/155] Add extension methods to PGPKeyRing, PGPSecretKeyRing and PGPSignature --- .../extensions/PGPKeyRingExtensions.kt | 48 ++++++++++++++++- .../extensions/PGPSecretKeyRingExtensions.kt | 51 +++++++++++++++++-- .../extensions/PGPSignatureExtensions.kt | 4 ++ .../org/pgpainless/key/OpenPgpFingerprint.kt | 2 + 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt index 43669bd1..2a3c85f5 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt @@ -5,6 +5,10 @@ package org.bouncycastle.extensions import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPOnePassSignature +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.SubkeyIdentifier /** @@ -12,4 +16,46 @@ import org.pgpainless.key.SubkeyIdentifier */ fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean = this.publicKey.keyID == subkeyIdentifier.primaryKeyId && - this.getPublicKey(subkeyIdentifier.subkeyId) != null \ No newline at end of file + this.getPublicKey(subkeyIdentifier.subkeyId) != null + +/** + * Return true, if the [PGPKeyRing] contains a public key with the given key-ID. + * + * @param keyId keyId + * @return true if key with the given key-ID is present, false otherwise + */ +fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = + this.getPublicKey(keyId) != null + +/** + * Return true, if the [PGPKeyRing] contains a public key with the given fingerprint. + * + * @param fingerprint fingerprint + * @return true if key with the given fingerprint is present, false otherwise + */ +fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean = + this.getPublicKey(fingerprint) != null + +/** + * Return the [PGPPublicKey] with the given [OpenPgpFingerprint] or null, if no such key is present. + * + * @param fingerprint fingerprint + * @return public key + */ +fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = + this.getPublicKey(fingerprint.bytes) + +/** + * Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. + * If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to + * identify the [PGPPublicKey] via its key-ID. + */ +fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? = + signature.getFingerprint()?.let { this.getPublicKey(it) } ?: + this.getPublicKey(signature.keyID) + +/** + * Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet. + */ +fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? = + this.getPublicKey(onePassSignature.keyID) \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt index 3a9c2918..5be7e128 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt @@ -4,8 +4,53 @@ package org.bouncycastle.extensions -import org.bouncycastle.openpgp.PGPPublicKeyRing -import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.* +import org.pgpainless.key.OpenPgpFingerprint +/** + * OpenPGP certificate containing the public keys of this OpenPGP key. + */ val PGPSecretKeyRing.certificate: PGPPublicKeyRing - get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList()) \ No newline at end of file + get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList()) + +/** + * Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given key-ID. + * + * @param keyId keyId of the secret key + * @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise + */ +fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = + this.getSecretKey(keyId) != null + +/** + * Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given fingerprint. + * + * @param fingerprint fingerprint + * @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise + */ +fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean = + this.getSecretKey(fingerprint) != null + +/** + * Return the [PGPSecretKey] with the given [OpenPgpFingerprint]. + * + * @param fingerprint fingerprint of the secret key + * @return the secret key or null + */ +fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? = + this.getSecretKey(fingerprint.bytes) + +/** + * Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. + * If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to + * identify the [PGPSecretKey] via its key-ID. + */ +fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? = + signature.getFingerprint()?.let { this.getSecretKey(it) } ?: + this.getSecretKey(signature.keyID) + +/** + * Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet. + */ +fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? = + this.getSecretKey(onePassSignature.keyID) \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt index 16f61304..80200aa6 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -8,6 +8,7 @@ import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.algorithm.RevocationState import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.signature.SignatureUtils +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import java.util.* /** @@ -51,3 +52,6 @@ fun PGPSignature?.toRevocationState() = else if (isHardRevocation()) RevocationState.hardRevoked() else RevocationState.softRevoked(creationTime) + +fun PGPSignature.getFingerprint(): OpenPgpFingerprint? = + SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt index c5a2a24f..fdcad306 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt @@ -16,6 +16,7 @@ import java.nio.charset.Charset */ abstract class OpenPgpFingerprint : CharSequence, Comparable { val fingerprint: String + val bytes: ByteArray /** * Return the version of the fingerprint. @@ -41,6 +42,7 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.") } this.fingerprint = prep + this.bytes = Hex.decode(prep) } constructor(bytes: ByteArray): this(Hex.toHexString(bytes)) From 76cf6173e87a75bdfd5490f9c82b2f3969faf6a5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Sep 2023 15:03:02 +0200 Subject: [PATCH 113/155] Add test for OpenPgpFingerprint.getBytes() --- .../test/java/org/pgpainless/key/OpenPgpV4FingerprintTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV4FingerprintTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV4FingerprintTest.java index d61aeedd..43a3dfba 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV4FingerprintTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV4FingerprintTest.java @@ -4,6 +4,7 @@ package org.pgpainless.key; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -112,6 +113,7 @@ public class OpenPgpV4FingerprintTest { OpenPgpFingerprint fingerprint = OpenPgpFingerprint.parseFromBinary(binary); assertTrue(fingerprint instanceof OpenPgpV4Fingerprint); assertEquals(hex, fingerprint.toString()); + assertArrayEquals(binary, fingerprint.getBytes()); } @Test @@ -122,6 +124,7 @@ public class OpenPgpV4FingerprintTest { OpenPgpFingerprint fingerprint = OpenPgpFingerprint.parseFromBinary(binary); assertTrue(fingerprint instanceof OpenPgpV4Fingerprint); assertEquals(hex, fingerprint.toString()); + assertArrayEquals(binary, fingerprint.getBytes()); } @Test From bb796143ff4bec8344fb123f8947c1e67458b2bf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Sep 2023 15:43:55 +0200 Subject: [PATCH 114/155] Improve public/secret key selection --- .../extensions/PGPSecretKeyRingExtensions.kt | 8 +- .../ConsumerOptions.kt | 6 + .../OpenPgpMessageInputStream.kt | 114 +++++++++++------- 3 files changed, 86 insertions(+), 42 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt index 5be7e128..a4a1621e 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt @@ -53,4 +53,10 @@ fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? = * Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */ fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? = - this.getSecretKey(onePassSignature.keyID) \ No newline at end of file + this.getSecretKey(onePassSignature.keyID) + +fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? = + when(pkesk.version) { + 3 -> this.getSecretKey(pkesk.keyID) + else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.") + } \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt index 5bbe098e..dbff0551 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt @@ -4,6 +4,7 @@ package org.pgpainless.decryption_verification +import org.bouncycastle.extensions.getPublicKeyFor import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy @@ -385,6 +386,11 @@ class ConsumerOptions { fun getCertificate(keyId: Long): PGPPublicKeyRing? { return explicitCertificates.firstOrNull { it.getPublicKey(keyId) != null } } + + fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? = + explicitCertificates.firstOrNull { + it.getPublicKeyFor(signature) != null + } } companion object { diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 11d5675f..7e059b48 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -7,6 +7,8 @@ package org.pgpainless.decryption_verification import openpgp.openPgpKeyId import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.UnsupportedPacketVersionException +import org.bouncycastle.extensions.getPublicKeyFor +import org.bouncycastle.extensions.getSecretKeyFor import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory @@ -280,31 +282,28 @@ class OpenPgpMessageInputStream( val postponedDueToMissingPassphrase = mutableListOf>() // try (known) secret keys - for (pkesk in esks.pkesks) { - val keyId = pkesk.keyID - LOGGER.debug("Encountered PKESK for recipient ${keyId.openPgpKeyId()}") - val decryptionKeys = getDecryptionKey(keyId) - if (decryptionKeys == null) { - LOGGER.debug("Skipping PKESK because no matching key ${keyId.openPgpKeyId()} was provided.") - continue - } - val secretKey = decryptionKeys.getSecretKey(keyId) - val decryptionKeyId = SubkeyIdentifier(decryptionKeys, keyId) - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { - continue - } + esks.pkesks.forEach { pkesk -> + LOGGER.debug("Encountered PKESK for recipient ${pkesk.keyID.openPgpKeyId()}") + val decryptionKeyCandidates = getDecryptionKeys(pkesk) + for (decryptionKeys in decryptionKeyCandidates) { + val secretKey = decryptionKeys.getSecretKeyFor(pkesk)!! + val decryptionKeyId = SubkeyIdentifier(decryptionKeys, secretKey.keyID) + if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + continue + } - LOGGER.debug("Attempt decryption using secret key $decryptionKeyId") - val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue - if (!protector.hasPassphraseFor(keyId)) { - LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") - postponedDueToMissingPassphrase.add(secretKey to pkesk) - continue - } + LOGGER.debug("Attempt decryption using secret key $decryptionKeyId") + val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue + if (!protector.hasPassphraseFor(secretKey.keyID)) { + LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") + postponedDueToMissingPassphrase.add(secretKey to pkesk) + continue + } - val privateKey = secretKey.unlock(protector) - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { - return true + val privateKey = secretKey.unlock(protector) + if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + return true + } } } @@ -343,7 +342,7 @@ class OpenPgpMessageInputStream( } else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) { for ((secretKey, pkesk) in postponedDueToMissingPassphrase) { val keyId = secretKey.keyID - val decryptionKeys = getDecryptionKey(keyId)!! + val decryptionKeys = getDecryptionKey(pkesk)!! val decryptionKeyId = SubkeyIdentifier(decryptionKeys, keyId) if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { continue @@ -541,6 +540,25 @@ class OpenPgpMessageInputStream( }) } + private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull { + it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> + when (pkesk.version) { + 3 -> pkesk.keyID == subkey.keyID + else -> throw NotImplementedError("Version 6 PKESK not yet supported.") + } + } + } + + private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List = + options.getDecryptionKeys().filter { + it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> + when (pkesk.version) { + 3 -> pkesk.keyID == subkey.keyID + else -> throw NotImplementedError("Version 6 PKESK not yet supported.") + } + } + } + private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List> { val algorithm = pkesk.algorithm val candidates = mutableListOf>() @@ -638,22 +656,24 @@ class OpenPgpMessageInputStream( } fun initializeSignature(signature: PGPSignature): SignatureCheck? { - val keyId = SignatureUtils.determineIssuerKeyId(signature) - val certificate = findCertificate(keyId) ?: return null - - val verifierKey = SubkeyIdentifier(certificate, keyId) - initialize(signature, certificate, keyId) + val certificate = findCertificate(signature) ?: return null + val publicKey = certificate.getPublicKeyFor(signature) ?: return null + val verifierKey = SubkeyIdentifier(certificate, publicKey.keyID) + initialize(signature, publicKey) return SignatureCheck(signature, certificate, verifierKey) } fun addOnePassSignature(signature: PGPOnePassSignature) { - val certificate = findCertificate(signature.keyID) + val certificate = findCertificate(signature) if (certificate != null) { - val ops = OnePassSignatureCheck(signature, certificate) - initialize(signature, certificate) - onePassSignatures.add(ops) - literalOPS.add(ops) + val publicKey = certificate.getPublicKeyFor(signature) + if (publicKey != null) { + val ops = OnePassSignatureCheck(signature, certificate) + initialize(signature, publicKey) + onePassSignatures.add(ops) + literalOPS.add(ops) + } } if (signature.isContaining) { enterNesting() @@ -710,14 +730,26 @@ class OpenPgpMessageInputStream( opsUpdateStack.removeFirst() } - fun findCertificate(keyId: Long): PGPPublicKeyRing? { - val cert = options.getCertificateSource().getCertificate(keyId) + private fun findCertificate(signature: PGPSignature): PGPPublicKeyRing? { + val cert = options.getCertificateSource().getCertificate(signature) if (cert != null) { return cert } if (options.getMissingCertificateCallback() != null) { - return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(keyId) + return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID) + } + return null // TODO: Missing cert for sig + } + + private fun findCertificate(signature: PGPOnePassSignature): PGPPublicKeyRing? { + val cert = options.getCertificateSource().getCertificate(signature.keyID) + if (cert != null) { + return cert + } + + if (options.getMissingCertificateCallback() != null) { + return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID) } return null // TODO: Missing cert for sig } @@ -831,22 +863,22 @@ class OpenPgpMessageInputStream( companion object { @JvmStatic - private fun initialize(signature: PGPSignature, certificate: PGPPublicKeyRing, keyId: Long) { + private fun initialize(signature: PGPSignature, publicKey: PGPPublicKey) { val verifierProvider = ImplementationFactory.getInstance() .pgpContentVerifierBuilderProvider try { - signature.init(verifierProvider, certificate.getPublicKey(keyId)) + signature.init(verifierProvider, publicKey) } catch (e : PGPException) { throw RuntimeException(e) } } @JvmStatic - private fun initialize(ops: PGPOnePassSignature, certificate: PGPPublicKeyRing) { + private fun initialize(ops: PGPOnePassSignature, publicKey: PGPPublicKey) { val verifierProvider = ImplementationFactory.getInstance() .pgpContentVerifierBuilderProvider try { - ops.init(verifierProvider, certificate.getPublicKey(ops.keyID)) + ops.init(verifierProvider, publicKey) } catch (e : PGPException) { throw RuntimeException(e) } From 68af0a4f0e984aff2f5218d29f667457e00f6706 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Sep 2023 21:55:39 +0200 Subject: [PATCH 115/155] Introduce more extension methods --- .../src/main/kotlin/openpgp/DateExtensions.kt | 23 ++++++ .../extensions/PGPKeyRingExtensions.kt | 16 +++- .../extensions/PGPPublicKeyExtensions.kt | 13 +++ .../extensions/PGPSecretKeyExtensions.kt | 15 ++++ .../extensions/PGPSecretKeyRingExtensions.kt | 19 ++++- .../extensions/PGPSignatureExtensions.kt | 62 +++++++++++---- .../org/pgpainless/key/info/KeyRingInfo.kt | 6 +- .../org/pgpainless/key/util/KeyRingUtils.kt | 6 +- .../pgpainless/signature/SignatureUtils.kt | 79 +++++++------------ 9 files changed, 168 insertions(+), 71 deletions(-) create mode 100644 pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt diff --git a/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt b/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt new file mode 100644 index 00000000..db98fb44 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package openpgp + +import java.util.* + + /** + * Return a new date which represents this date plus the given amount of seconds added. + * + * Since '0' is a special date value in the OpenPGP specification + * (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0. + * + * @param date date + * @param seconds number of seconds to be added + * @return date plus seconds or null if seconds is '0' + */ + fun Date.plusSeconds(seconds: Long): Date? { + require(Long.MAX_VALUE - time > seconds) { "Adding $seconds seconds to this date would cause time to overflow." } + return if (seconds == 0L) null + else Date(this.time + 1000 * seconds) + } diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt index 2a3c85f5..7ccddb42 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt @@ -8,6 +8,7 @@ import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPOnePassSignature import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.PGPainless import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.SubkeyIdentifier @@ -51,11 +52,22 @@ fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = * identify the [PGPPublicKey] via its key-ID. */ fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? = - signature.getFingerprint()?.let { this.getPublicKey(it) } ?: + signature.fingerprint?.let { this.getPublicKey(it) } ?: this.getPublicKey(signature.keyID) /** * Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */ fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? = - this.getPublicKey(onePassSignature.keyID) \ No newline at end of file + this.getPublicKey(onePassSignature.keyID) + +/** + * Return the [OpenPgpFingerprint] of this OpenPGP key. + */ +val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint + get() = OpenPgpFingerprint.of(this) + +/** + * Return this OpenPGP key as an ASCII armored String. + */ +fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this) \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt index a1ed72fe..ad51c6f4 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt @@ -11,6 +11,7 @@ import org.bouncycastle.bcpg.EdDSAPublicBCPGKey import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil import org.bouncycastle.openpgp.PGPPublicKey import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.generation.type.eddsa.EdDSACurve /** @@ -35,3 +36,15 @@ fun PGPPublicKey.getCurveName(): String { .let { it to ECUtil.getCurveName(it) } .let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") } } + +/** + * Return the [PublicKeyAlgorithm] of this key. + */ +val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm + get() = PublicKeyAlgorithm.requireFromId(algorithm) + +/** + * Return the [OpenPgpFingerprint] of this key. + */ +val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint + get() = OpenPgpFingerprint.of(this) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt index f5d4f522..3d759d1a 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt @@ -7,10 +7,13 @@ package org.bouncycastle.extensions import org.bouncycastle.bcpg.S2K import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPrivateKey +import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor +import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.exception.WrongPassphraseException +import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.protection.SecretKeyRingProtector import org.pgpainless.key.protection.UnlockSecretKey import org.pgpainless.util.Passphrase @@ -70,3 +73,15 @@ fun PGPSecretKey?.isDecrypted(): Boolean = (this == null) || (s2KUsage == 0) * @return true if secret key has S2K of type GNU_DUMMY_S2K, false otherwise. */ fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K) + +/** + * Return the [PublicKeyAlgorithm] of this key. + */ +val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm + get() = publicKey.publicKeyAlgorithm + +/** + * Return the [OpenPgpFingerprint] of this key. + */ +val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint + get() = OpenPgpFingerprint.of(this) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt index a4a1621e..d0529d51 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt @@ -4,6 +4,7 @@ package org.bouncycastle.extensions +import openpgp.openPgpKeyId import org.bouncycastle.openpgp.* import org.pgpainless.key.OpenPgpFingerprint @@ -40,13 +41,29 @@ fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean = fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? = this.getSecretKey(fingerprint.bytes) +/** + * Return the [PGPSecretKey] with the given key-ID. + * + * @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given key-ID + */ +fun PGPSecretKeyRing.requireSecretKey(keyId: Long): PGPSecretKey = + getSecretKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.") + +/** + * Return the [PGPSecretKey] with the given fingerprint. + * + * @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key with the given fingerprint + */ +fun PGPSecretKeyRing.requireSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey = + getSecretKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.") + /** * Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. * If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to * identify the [PGPSecretKey] via its key-ID. */ fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? = - signature.getFingerprint()?.let { this.getSecretKey(it) } ?: + signature.fingerprint?.let { this.getSecretKey(it) } ?: this.getSecretKey(signature.keyID) /** diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt index 80200aa6..a27e68e0 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -4,10 +4,12 @@ package org.bouncycastle.extensions +import openpgp.plusSeconds import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.algorithm.RevocationState +import org.pgpainless.algorithm.SignatureType import org.pgpainless.key.OpenPgpFingerprint -import org.pgpainless.signature.SignatureUtils +import org.pgpainless.key.util.RevocationAttributes.Reason import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import java.util.* @@ -16,42 +18,74 @@ import java.util.* * such a subpacket. */ fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? = - SignatureUtils.getKeyExpirationDate(keyCreationDate, this) + SignatureSubpacketsUtil.getKeyExpirationTime(this) + ?.let { keyCreationDate.plusSeconds(it.time) } /** * Return the value of the signature ExpirationTime subpacket, or null, if the signature * does not carry such a subpacket. */ -fun PGPSignature.getSignatureExpirationDate(): Date? = - SignatureUtils.getSignatureExpirationDate(this) +val PGPSignature.signatureExpirationDate: Date? + get() = SignatureSubpacketsUtil.getSignatureExpirationTime(this) + ?.let { this.creationTime.plusSeconds(it.time) } /** * Return true, if the signature is expired at the given reference time. */ fun PGPSignature.isExpired(referenceTime: Date = Date()) = - SignatureUtils.isSignatureExpired(this, referenceTime) + signatureExpirationDate?.let { referenceTime >= it } ?: false /** * Return the key-ID of the issuer, determined by examining the IssuerKeyId and IssuerFingerprint * subpackets of the signature. */ -fun PGPSignature.getIssuerKeyId() = SignatureUtils.determineIssuerKeyId(this) +val PGPSignature.issuerKeyId: Long + get() = when (version) { + 2, 3 -> keyID + else -> { + SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this) + ?.let { if (it != 0L) it else null } + ?: fingerprint?.keyId + ?: 0L + } + } /** - * Return true, if the signature was likely issued by the key with the given fingerprint. + * Return true, if the signature was likely issued by a key with the given fingerprint. */ -fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint) = SignatureUtils.wasIssuedBy(fingerprint, this) +fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint): Boolean = + this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId) + +/** + * Return true, if the signature was likely issued by a key with the given fingerprint. + * @param fingerprint fingerprint bytes + */ +@Deprecated("Discouraged in favor of method taking an OpenPgpFingerprint.") +fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean = + try { + wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint)) + } catch (e : IllegalArgumentException) { + // Unknown fingerprint length / format + false + } /** * Return true, if this signature is a hard revocation. */ -fun PGPSignature.isHardRevocation() = SignatureUtils.isHardRevocation(this) +val PGPSignature.isHardRevocation + get() = when (SignatureType.requireFromCode(signatureType)) { + SignatureType.KEY_REVOCATION, SignatureType.SUBKEY_REVOCATION, SignatureType.CERTIFICATION_REVOCATION -> { + SignatureSubpacketsUtil.getRevocationReason(this) + ?.let { Reason.isHardRevocation(it.revocationReason) } + ?: true // no reason -> hard revocation + } + else -> false // Not a revocation + } fun PGPSignature?.toRevocationState() = if (this == null) RevocationState.notRevoked() - else - if (isHardRevocation()) RevocationState.hardRevoked() - else RevocationState.softRevoked(creationTime) + else if (isHardRevocation) RevocationState.hardRevoked() + else RevocationState.softRevoked(creationTime) -fun PGPSignature.getFingerprint(): OpenPgpFingerprint? = - SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this) +val PGPSignature.fingerprint: OpenPgpFingerprint? + get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this) \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt index 79cc8683..df6023c4 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt @@ -121,7 +121,7 @@ class KeyRingInfo( val validAndExpiredUserIds: List = userIds.filter { val certification = signatures.userIdCertifications[it] ?: return@filter false val revocation = signatures.userIdRevocations[it] ?: return@filter true - return@filter !revocation.isHardRevocation() && certification.creationTime > revocation.creationTime + return@filter !revocation.isHardRevocation && certification.creationTime > revocation.creationTime } /** @@ -272,7 +272,7 @@ class KeyRingInfo( * @return true, if the given user-ID is hard-revoked. */ fun isHardRevoked(userId: CharSequence): Boolean { - return signatures.userIdRevocations[userId]?.isHardRevocation() ?: false + return signatures.userIdRevocations[userId]?.isHardRevocation ?: false } /** @@ -632,7 +632,7 @@ class KeyRingInfo( } } signatures.userIdRevocations[userId]?.let { rev -> - if (rev.isHardRevocation()) { + if (rev.isHardRevocation) { return false // hard revoked -> invalid } sig.creationTime > rev.creationTime// re-certification after soft revocation? diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt index 22e78410..79215408 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt @@ -8,6 +8,7 @@ import openpgp.openPgpKeyId import org.bouncycastle.bcpg.S2K import org.bouncycastle.bcpg.SecretKeyPacket import org.bouncycastle.extensions.certificate +import org.bouncycastle.extensions.requireSecretKey import org.bouncycastle.openpgp.* import org.bouncycastle.util.Strings import org.pgpainless.exception.MissingPassphraseException @@ -34,9 +35,10 @@ class KeyRingUtils { * @return primary secret key */ @JvmStatic + @Deprecated("Deprecated in favor of PGPSecretKeyRing extension function.", + ReplaceWith("secretKeys.requireSecretKey(keyId)")) fun requirePrimarySecretKeyFrom(secretKeys: PGPSecretKeyRing): PGPSecretKey { - return getPrimarySecretKeyFrom(secretKeys) - ?: throw NoSuchElementException("Provided PGPSecretKeyRing has no primary secret key.") + return secretKeys.requireSecretKey(secretKeys.publicKey.keyID) } /** diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt index 4ca30e86..0edb6d71 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt @@ -4,7 +4,9 @@ package org.pgpainless.signature +import openpgp.plusSeconds import org.bouncycastle.bcpg.sig.KeyExpirationTime +import org.bouncycastle.extensions.* import org.bouncycastle.openpgp.* import org.bouncycastle.util.encoders.Hex import org.bouncycastle.util.io.Streams @@ -17,6 +19,7 @@ import org.pgpainless.util.ArmorUtils import java.io.IOException import java.io.InputStream import java.util.* +import kotlin.math.sign const val MAX_ITERATIONS = 10000 @@ -32,10 +35,10 @@ class SignatureUtils { * @return key expiration date as given by the signature */ @JvmStatic + @Deprecated("Deprecated in favor of PGPSignature extension method.", + ReplaceWith("signature.getKeyExpirationDate(keyCreationDate)")) fun getKeyExpirationDate(keyCreationDate: Date, signature: PGPSignature): Date? { - val expirationPacket: KeyExpirationTime = SignatureSubpacketsUtil.getKeyExpirationTime(signature) ?: return null - val expiresInSeconds = expirationPacket.time - return datePlusSeconds(keyCreationDate, expiresInSeconds) + return signature.getKeyExpirationDate(keyCreationDate) } /** @@ -46,12 +49,9 @@ class SignatureUtils { * @return expiration date of the signature, or null if it does not expire. */ @JvmStatic - fun getSignatureExpirationDate(signature: PGPSignature): Date? { - val expirationTime = SignatureSubpacketsUtil.getSignatureExpirationTime(signature) ?: return null - - val expiresInSeconds = expirationTime.time - return datePlusSeconds(signature.creationTime, expiresInSeconds) - } + @Deprecated("Deprecated in favor of PGPSignature extension method.", + ReplaceWith("signature.signatureExpirationDate")) + fun getSignatureExpirationDate(signature: PGPSignature): Date? = signature.signatureExpirationDate /** * Return a new date which represents the given date plus the given amount of seconds added. @@ -64,11 +64,10 @@ class SignatureUtils { * @return date plus seconds or null if seconds is '0' */ @JvmStatic + @Deprecated("Deprecated in favor of Date extension method.", + ReplaceWith("date.plusSeconds(seconds)")) fun datePlusSeconds(date: Date, seconds: Long): Date? { - if (seconds == 0L) { - return null - } - return Date(date.time + 1000 * seconds) + return date.plusSeconds(seconds) } /** @@ -79,8 +78,10 @@ class SignatureUtils { * @return true if expired, false otherwise */ @JvmStatic + @Deprecated("Deprecated in favor of PGPSignature extension method.", + ReplaceWith("signature.isExpired()")) fun isSignatureExpired(signature: PGPSignature): Boolean { - return isSignatureExpired(signature, Date()) + return signature.isExpired() } /** @@ -92,9 +93,10 @@ class SignatureUtils { * @return true if sig is expired at reference date, false otherwise */ @JvmStatic + @Deprecated("Deprecated in favor of PGPSignature extension method.", + ReplaceWith("signature.isExpired(referenceTime)")) fun isSignatureExpired(signature: PGPSignature, referenceTime: Date): Boolean { - val expirationDate = getSignatureExpirationDate(signature) ?: return false - return referenceTime >= expirationDate + return signature.isExpired(referenceTime) } /** @@ -106,15 +108,10 @@ class SignatureUtils { * @return true if signature is a hard revocation */ @JvmStatic + @Deprecated("Deprecated in favor of PGPSignature extension function.", + ReplaceWith("signature.isHardRevocation()")) fun isHardRevocation(signature: PGPSignature): Boolean { - val type = SignatureType.requireFromCode(signature.signatureType) - if (type != SignatureType.KEY_REVOCATION && type != SignatureType.SUBKEY_REVOCATION && type != SignatureType.CERTIFICATION_REVOCATION) { - // Not a revocation - return false - } - - val reason = SignatureSubpacketsUtil.getRevocationReason(signature) ?: return true // no reason -> hard revocation - return Reason.isHardRevocation(reason.revocationReason) + return signature.isHardRevocation } @JvmStatic @@ -181,22 +178,10 @@ class SignatureUtils { * @return signatures issuing key id */ @JvmStatic + @Deprecated("Deprecated in favor of PGPSignature extension method.", + ReplaceWith("signature.issuerKeyId")) fun determineIssuerKeyId(signature: PGPSignature): Long { - if (signature.version == 3) { - // V3 sigs do not contain subpackets - return signature.keyID - } - - val issuerKeyId = SignatureSubpacketsUtil.getIssuerKeyId(signature) - val issuerFingerprint = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature) - - if (issuerKeyId != null && issuerKeyId.keyID != 0L) { - return issuerKeyId.keyID - } - if (issuerKeyId == null && issuerFingerprint != null) { - return issuerFingerprint.keyId - } - return 0 + return signature.issuerKeyId } /** @@ -211,21 +196,17 @@ class SignatureUtils { } @JvmStatic + @Deprecated("Deprecated in favor of PGPSignature extension method", + ReplaceWith("signature.wasIssuedBy(fingerprint)")) fun wasIssuedBy(fingerprint: ByteArray, signature: PGPSignature): Boolean { - return try { - val pgpFingerprint = OpenPgpFingerprint.parseFromBinary(fingerprint) - wasIssuedBy(pgpFingerprint, signature) - } catch (e : IllegalArgumentException) { - // Unknown fingerprint length - false - } + return signature.wasIssuedBy(fingerprint) } @JvmStatic + @Deprecated("Deprecated in favor of PGPSignature extension method", + ReplaceWith("signature.wasIssuedBy(fingerprint)")) fun wasIssuedBy(fingerprint: OpenPgpFingerprint, signature: PGPSignature): Boolean { - val issuerFp = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature) - ?: return fingerprint.keyId == signature.keyID - return fingerprint == issuerFp + return signature.wasIssuedBy(fingerprint) } /** From 4719d6ccea30ada6036f1ad19332c78af1576d9e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Sep 2023 22:07:17 +0200 Subject: [PATCH 116/155] Migrate further to extension methods --- .../OpenPgpMessageInputStream.kt | 10 ++++---- .../org/pgpainless/key/info/KeyRingInfo.kt | 15 ++++-------- .../pgpainless/signature/SignatureUtils.kt | 23 ++++++++----------- .../subpackets/SignatureSubpacketsUtil.kt | 5 ++-- 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 7e059b48..b823fdaf 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -9,6 +9,7 @@ import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.UnsupportedPacketVersionException import org.bouncycastle.extensions.getPublicKeyFor import org.bouncycastle.extensions.getSecretKeyFor +import org.bouncycastle.extensions.issuerKeyId import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory @@ -26,7 +27,6 @@ import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.policy.Policy -import org.pgpainless.signature.SignatureUtils import org.pgpainless.signature.consumer.CertificateValidator import org.pgpainless.signature.consumer.OnePassSignatureCheck import org.pgpainless.signature.consumer.SignatureCheck @@ -197,7 +197,7 @@ class OpenPgpMessageInputStream( return } - val keyId = SignatureUtils.determineIssuerKeyId(signature) + val keyId = signature.issuerKeyId if (isSigForOps) { LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with @@ -632,7 +632,7 @@ class OpenPgpMessageInputStream( fun addDetachedSignature(signature: PGPSignature) { val check = initializeSignature(signature) - val keyId = SignatureUtils.determineIssuerKeyId(signature) + val keyId = signature.issuerKeyId if (check != null) { detachedSignatures.add(check) } else { @@ -644,7 +644,7 @@ class OpenPgpMessageInputStream( fun addPrependedSignature(signature: PGPSignature) { val check = initializeSignature(signature) - val keyId = SignatureUtils.determineIssuerKeyId(signature) + val keyId = signature.issuerKeyId if (check != null) { prependedSignatures.add(check) } else { @@ -682,7 +682,7 @@ class OpenPgpMessageInputStream( fun addCorrespondingOnePassSignature(signature: PGPSignature, layer: Layer, policy: Policy) { var found = false - val keyId = SignatureUtils.determineIssuerKeyId(signature) + val keyId = signature.issuerKeyId for ((i, check) in onePassSignatures.withIndex().reversed()) { if (check.onePassSignature.keyID != keyId) { continue diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt index df6023c4..6ccdc81d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt @@ -14,17 +14,12 @@ import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.policy.Policy -import org.pgpainless.signature.SignatureUtils -import org.pgpainless.signature.SignatureUtils.Companion.isHardRevocation -import org.pgpainless.signature.SignatureUtils.Companion.isSignatureExpired import org.pgpainless.signature.consumer.SignaturePicker import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil.Companion.getKeyExpirationTimeAsDate import org.pgpainless.util.DateUtil import org.slf4j.LoggerFactory -import java.security.Key import java.util.* -import kotlin.NoSuchElementException class KeyRingInfo( val keys: PGPKeyRing, @@ -237,7 +232,7 @@ class KeyRingInfo( if (publicKey.keyID == keyId) return primaryKeyExpirationDate val subkey = getPublicKey(keyId) ?: throw NoSuchElementException("No subkey with key-ID ${keyId.openPgpKeyId()} found.") val bindingSig = getCurrentSubkeyBindingSignature(keyId) ?: throw AssertionError("Subkey has no valid binding signature.") - return SignatureUtils.getKeyExpirationDate(subkey.creationTime, bindingSig) + return bindingSig.getKeyExpirationDate(subkey.creationTime) } /** @@ -560,7 +555,7 @@ class KeyRingInfo( // Primary key -> Check Primary Key Revocation if (publicKey.keyID == this.publicKey.keyID) { - return if (signatures.primaryKeyRevocation != null && isHardRevocation(signatures.primaryKeyRevocation)) { + return if (signatures.primaryKeyRevocation != null && signatures.primaryKeyRevocation.isHardRevocation) { false } else signatures.primaryKeyRevocation == null } @@ -570,18 +565,18 @@ class KeyRingInfo( val revocation = signatures.subkeyRevocations[keyId] // No valid binding - if (binding == null || isSignatureExpired(binding)) { + if (binding == null || binding.isExpired(referenceDate)) { return false } // Revocation return if (revocation != null) { - if (isHardRevocation(revocation)) { + if (revocation.isHardRevocation) { // Subkey is hard revoked false } else { // Key is soft-revoked, not yet re-bound - (isSignatureExpired(revocation) || !revocation.creationTime.after(binding.creationTime)) + (revocation.isExpired(referenceDate) || !revocation.creationTime.after(binding.creationTime)) } } else true } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt index 0edb6d71..492c4fe4 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt @@ -10,16 +10,13 @@ import org.bouncycastle.extensions.* import org.bouncycastle.openpgp.* import org.bouncycastle.util.encoders.Hex import org.bouncycastle.util.io.Streams -import org.pgpainless.algorithm.SignatureType import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.util.RevocationAttributes.Reason -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.util.ArmorUtils import java.io.IOException import java.io.InputStream import java.util.* -import kotlin.math.sign const val MAX_ITERATIONS = 10000 @@ -36,21 +33,21 @@ class SignatureUtils { */ @JvmStatic @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.getKeyExpirationDate(keyCreationDate)")) + ReplaceWith("signature.getKeyExpirationDate(keyCreationDate)", "org.bouncycastle.extensions.getKeyExpirationDate")) fun getKeyExpirationDate(keyCreationDate: Date, signature: PGPSignature): Date? { return signature.getKeyExpirationDate(keyCreationDate) } /** * Return the expiration date of the signature. - * If the signature has no expiration date, [datePlusSeconds] will return null. + * If the signature has no expiration date, this will return null. * * @param signature signature * @return expiration date of the signature, or null if it does not expire. */ @JvmStatic @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.signatureExpirationDate")) + ReplaceWith("signature.signatureExpirationDate", "org.bouncycastle.extensions.signatureExpirationDate")) fun getSignatureExpirationDate(signature: PGPSignature): Date? = signature.signatureExpirationDate /** @@ -65,7 +62,7 @@ class SignatureUtils { */ @JvmStatic @Deprecated("Deprecated in favor of Date extension method.", - ReplaceWith("date.plusSeconds(seconds)")) + ReplaceWith("date.plusSeconds(seconds)", "openpgp.plusSeconds")) fun datePlusSeconds(date: Date, seconds: Long): Date? { return date.plusSeconds(seconds) } @@ -79,7 +76,7 @@ class SignatureUtils { */ @JvmStatic @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.isExpired()")) + ReplaceWith("signature.isExpired()", "org.bouncycastle.extensions.isExpired")) fun isSignatureExpired(signature: PGPSignature): Boolean { return signature.isExpired() } @@ -94,7 +91,7 @@ class SignatureUtils { */ @JvmStatic @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.isExpired(referenceTime)")) + ReplaceWith("signature.isExpired(referenceTime)", "org.bouncycastle.extensions.isExpired")) fun isSignatureExpired(signature: PGPSignature, referenceTime: Date): Boolean { return signature.isExpired(referenceTime) } @@ -109,7 +106,7 @@ class SignatureUtils { */ @JvmStatic @Deprecated("Deprecated in favor of PGPSignature extension function.", - ReplaceWith("signature.isHardRevocation()")) + ReplaceWith("signature.isHardRevocation", "org.bouncycastle.extensions.isHardRevocation")) fun isHardRevocation(signature: PGPSignature): Boolean { return signature.isHardRevocation } @@ -179,7 +176,7 @@ class SignatureUtils { */ @JvmStatic @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.issuerKeyId")) + ReplaceWith("signature.issuerKeyId", "org.bouncycastle.extensions.issuerKeyId")) fun determineIssuerKeyId(signature: PGPSignature): Long { return signature.issuerKeyId } @@ -197,14 +194,14 @@ class SignatureUtils { @JvmStatic @Deprecated("Deprecated in favor of PGPSignature extension method", - ReplaceWith("signature.wasIssuedBy(fingerprint)")) + ReplaceWith("signature.wasIssuedBy(fingerprint)", "org.bouncycastle.extensions.wasIssuedBy")) fun wasIssuedBy(fingerprint: ByteArray, signature: PGPSignature): Boolean { return signature.wasIssuedBy(fingerprint) } @JvmStatic @Deprecated("Deprecated in favor of PGPSignature extension method", - ReplaceWith("signature.wasIssuedBy(fingerprint)")) + ReplaceWith("signature.wasIssuedBy(fingerprint)", "org.bouncycastle.extensions.wasIssuedBy")) fun wasIssuedBy(fingerprint: OpenPgpFingerprint, signature: PGPSignature): Boolean { return signature.wasIssuedBy(fingerprint) } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt index 172ef32f..2cf79d72 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt @@ -5,6 +5,7 @@ package org.pgpainless.signature.subpackets import openpgp.openPgpKeyId +import openpgp.plusSeconds import org.bouncycastle.bcpg.sig.* import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature @@ -116,7 +117,7 @@ class SignatureSubpacketsUtil { @JvmStatic fun getSignatureExpirationTimeAsDate(signature: PGPSignature): Date? = getSignatureExpirationTime(signature)?.let { - SignatureUtils.datePlusSeconds(signature.creationTime, it.time) + signature.creationTime.plusSeconds(it.time) } /** @@ -146,7 +147,7 @@ class SignatureSubpacketsUtil { "Provided key (${signingKey.keyID.openPgpKeyId()}) did not create the signature (${signature.keyID.openPgpKeyId()})" }.run { getKeyExpirationTime(signature)?.let { - SignatureUtils.datePlusSeconds(signingKey.creationTime, it.time) + signingKey.creationTime.plusSeconds(it.time) } } From ec8ae3eff01759b354b81dd02105594640bcab38 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 13 Sep 2023 15:05:58 +0200 Subject: [PATCH 117/155] Kotlin conversion: SecretKeyRingEditor --- .../key/modification/package-info.java | 8 - .../secretkeyring/SecretKeyRingEditor.java | 810 ------------------ .../SecretKeyRingEditorInterface.java | 652 -------------- .../secretkeyring/package-info.java | 8 - .../extensions/PGPKeyRingExtensions.kt | 7 + .../org/pgpainless/key/info/KeyRingInfo.kt | 86 +- .../secretkeyring/SecretKeyRingEditor.kt | 493 +++++++++++ .../SecretKeyRingEditorInterface.kt | 560 ++++++++++++ .../pgpainless/key/info/KeyRingInfoTest.java | 2 +- .../key/modification/AddUserIdTest.java | 7 +- .../key/protection/fixes/S2KUsageFixTest.java | 2 +- 11 files changed, 1110 insertions(+), 1525 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/modification/package-info.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/package-info.java deleted file mode 100644 index 9fa73e88..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes that deal with modifications made to OpenPGP keys. - */ -package org.pgpainless.key.modification; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java deleted file mode 100644 index 663c6e41..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ /dev/null @@ -1,810 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.modification.secretkeyring; - -import static org.pgpainless.key.util.KeyRingUtils.changePassphrase; -import static org.pgpainless.util.CollectionUtils.concat; - -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.sig.KeyExpirationTime; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyPair; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.AlgorithmSuite; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.Feature; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.generation.KeyRingBuilder; -import org.pgpainless.key.generation.KeySpec; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.CachingSecretKeyRingProtector; -import org.pgpainless.key.protection.KeyRingProtectionSettings; -import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnprotectedKeysProtector; -import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; -import org.pgpainless.key.util.KeyRingUtils; -import org.pgpainless.key.util.RevocationAttributes; -import org.pgpainless.signature.builder.DirectKeySelfSignatureBuilder; -import org.pgpainless.signature.builder.PrimaryKeyBindingSignatureBuilder; -import org.pgpainless.signature.builder.RevocationSignatureBuilder; -import org.pgpainless.signature.builder.SelfSignatureBuilder; -import org.pgpainless.signature.builder.SubkeyBindingSignatureBuilder; -import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets; -import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; -import org.pgpainless.util.Passphrase; -import org.pgpainless.util.selection.userid.SelectUserId; - -public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { - - private PGPSecretKeyRing secretKeyRing; - private final Date referenceTime; - - public SecretKeyRingEditor(@Nonnull PGPSecretKeyRing secretKeyRing) { - this(secretKeyRing, new Date()); - } - - public SecretKeyRingEditor(@Nonnull PGPSecretKeyRing secretKeyRing, - @Nonnull Date referenceTime) { - this.secretKeyRing = secretKeyRing; - this.referenceTime = referenceTime; - } - - @Nonnull - @Override - public Date getReferenceTime() { - return referenceTime; - } - - @Override - public SecretKeyRingEditorInterface addUserId( - @Nonnull CharSequence userId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException { - return addUserId(userId, null, secretKeyRingProtector); - } - - @Override - public SecretKeyRingEditorInterface addUserId( - @Nonnull CharSequence userId, - @Nullable SelfSignatureSubpackets.Callback signatureSubpacketCallback, - @Nonnull SecretKeyRingProtector protector) - throws PGPException { - String sanitizeUserId = sanitizeUserId(userId); - - // user-id certifications live on the primary key - PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - - // retain key flags from previous signature - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); - if (info.isHardRevoked(userId.toString())) { - throw new IllegalArgumentException("User-ID " + userId + " is hard revoked and cannot be re-certified."); - } - List keyFlags = info.getKeyFlagsOf(info.getKeyId()); - - Set hashAlgorithmPreferences; - Set symmetricKeyAlgorithmPreferences; - Set compressionAlgorithmPreferences; - try { - hashAlgorithmPreferences = info.getPreferredHashAlgorithms(); - symmetricKeyAlgorithmPreferences = info.getPreferredSymmetricKeyAlgorithms(); - compressionAlgorithmPreferences = info.getPreferredCompressionAlgorithms(); - } catch (IllegalStateException e) { - // missing user-id sig - AlgorithmSuite algorithmSuite = AlgorithmSuite.getDefaultAlgorithmSuite(); - hashAlgorithmPreferences = algorithmSuite.getHashAlgorithms(); - symmetricKeyAlgorithmPreferences = algorithmSuite.getSymmetricKeyAlgorithms(); - compressionAlgorithmPreferences = algorithmSuite.getCompressionAlgorithms(); - } - - SelfSignatureBuilder builder = new SelfSignatureBuilder(primaryKey, protector); - builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - builder.setSignatureType(SignatureType.POSITIVE_CERTIFICATION); - - // Retain signature subpackets of previous signatures - builder.getHashedSubpackets().setKeyFlags(keyFlags); - builder.getHashedSubpackets().setPreferredHashAlgorithms(hashAlgorithmPreferences); - builder.getHashedSubpackets().setPreferredSymmetricKeyAlgorithms(symmetricKeyAlgorithmPreferences); - builder.getHashedSubpackets().setPreferredCompressionAlgorithms(compressionAlgorithmPreferences); - builder.getHashedSubpackets().setFeatures(Feature.MODIFICATION_DETECTION); - - builder.applyCallback(signatureSubpacketCallback); - - PGPSignature signature = builder.build(primaryKey.getPublicKey(), sanitizeUserId); - secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, sanitizeUserId, signature); - - return this; - } - - @Override - public SecretKeyRingEditorInterface addPrimaryUserId( - @Nonnull CharSequence userId, @Nonnull SecretKeyRingProtector protector) - throws PGPException { - - // Determine previous key expiration date - PGPPublicKey primaryKey = secretKeyRing.getSecretKey().getPublicKey(); - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); - String primaryUserId = info.getPrimaryUserId(); - PGPSignature signature = primaryUserId == null ? - info.getLatestDirectKeySelfSignature() : info.getLatestUserIdCertification(primaryUserId); - final Date previousKeyExpiration = signature == null ? null : - SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(signature, primaryKey); - - // Add new primary user-id signature - addUserId( - userId, - new SelfSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { - hashedSubpackets.setPrimaryUserId(); - if (previousKeyExpiration != null) { - hashedSubpackets.setKeyExpirationTime(primaryKey, previousKeyExpiration); - } else { - hashedSubpackets.setKeyExpirationTime(null); - } - } - }, - protector); - - // unmark previous primary user-ids to be non-primary - info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); - for (String otherUserId : info.getValidAndExpiredUserIds()) { - if (userId.toString().equals(otherUserId)) { - continue; - } - - // We need to unmark this user-id as primary - PGPSignature userIdCertification = info.getLatestUserIdCertification(otherUserId); - assert (userIdCertification != null); - - if (userIdCertification.getHashedSubPackets().isPrimaryUserID()) { - addUserId(otherUserId, new SelfSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { - hashedSubpackets.setPrimaryUserId(null); - hashedSubpackets.setKeyExpirationTime(null); // non-primary - } - }, protector); - } - } - return this; - } - - @Override - public SecretKeyRingEditorInterface removeUserId( - SelectUserId userIdSelector, - SecretKeyRingProtector protector) - throws PGPException { - RevocationAttributes revocationAttributes = RevocationAttributes.createCertificateRevocation() - .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) - .withoutDescription(); - return revokeUserIds(userIdSelector, - protector, - revocationAttributes); - } - - @Override - public SecretKeyRingEditorInterface removeUserId( - CharSequence userId, - SecretKeyRingProtector protector) - throws PGPException { - return removeUserId( - SelectUserId.exactMatch(userId.toString()), - protector); - } - - @Override - public SecretKeyRingEditorInterface replaceUserId(@Nonnull CharSequence oldUserId, - @Nonnull CharSequence newUserId, - @Nonnull SecretKeyRingProtector protector) - throws PGPException { - String oldUID = oldUserId.toString().trim(); - String newUID = newUserId.toString().trim(); - if (oldUID.isEmpty()) { - throw new IllegalArgumentException("Old user-id cannot be empty."); - } - - if (newUID.isEmpty()) { - throw new IllegalArgumentException("New user-id cannot be empty."); - } - - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); - if (!info.isUserIdValid(oldUID)) { - throw new NoSuchElementException("Key does not carry user-id '" + oldUID + "', or it is not valid."); - } - - PGPSignature oldCertification = info.getLatestUserIdCertification(oldUID); - if (oldCertification == null) { - throw new AssertionError("Certification for old user-id MUST NOT be null."); - } - - // Bind new user-id - addUserId(newUserId, new SelfSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { - SignatureSubpacketsHelper.applyFrom(oldCertification.getHashedSubPackets(), (SignatureSubpackets) hashedSubpackets); - // Primary user-id - if (oldUID.equals(info.getPrimaryUserId())) { - // Implicit primary user-id - if (!oldCertification.getHashedSubPackets().isPrimaryUserID()) { - hashedSubpackets.setPrimaryUserId(); - } - } - } - - @Override - public void modifyUnhashedSubpackets(SelfSignatureSubpackets unhashedSubpackets) { - SignatureSubpacketsHelper.applyFrom(oldCertification.getUnhashedSubPackets(), (SignatureSubpackets) unhashedSubpackets); - } - }, protector); - - return revokeUserId(oldUID, protector); - } - - // TODO: Move to utility class? - private String sanitizeUserId(@Nonnull CharSequence userId) { - // TODO: Further research how to sanitize user IDs. - // eg. what about newlines? - return userId.toString().trim(); - } - - @Override - public SecretKeyRingEditorInterface addSubKey( - @Nonnull KeySpec keySpec, - @Nonnull Passphrase subKeyPassphrase, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException { - PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); - - SecretKeyRingProtector subKeyProtector = PasswordBasedSecretKeyRingProtector - .forKeyId(keyPair.getKeyID(), subKeyPassphrase); - - SelfSignatureSubpackets.Callback callback = new SelfSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { - SignatureSubpacketsHelper.applyFrom(keySpec.getSubpackets(), (SignatureSubpackets) hashedSubpackets); - } - }; - - List keyFlags = KeyFlag.fromBitmask(keySpec.getSubpackets().getKeyFlags()); - KeyFlag firstFlag = keyFlags.remove(0); - KeyFlag[] otherFlags = keyFlags.toArray(new KeyFlag[0]); - - return addSubKey(keyPair, callback, subKeyProtector, secretKeyRingProtector, firstFlag, otherFlags); - } - - @Override - public SecretKeyRingEditorInterface addSubKey( - @Nonnull KeySpec keySpec, - @Nullable Passphrase subkeyPassphrase, - @Nullable SelfSignatureSubpackets.Callback subpacketsCallback, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { - PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); - - SecretKeyRingProtector subKeyProtector = PasswordBasedSecretKeyRingProtector - .forKeyId(keyPair.getKeyID(), subkeyPassphrase); - - List keyFlags = KeyFlag.fromBitmask(keySpec.getSubpackets().getKeyFlags()); - KeyFlag firstFlag = keyFlags.remove(0); - KeyFlag[] otherFlags = keyFlags.toArray(new KeyFlag[0]); - - return addSubKey(keyPair, subpacketsCallback, subKeyProtector, secretKeyRingProtector, firstFlag, otherFlags); - } - - @Override - public SecretKeyRingEditorInterface addSubKey( - @Nonnull PGPKeyPair subkey, - @Nullable SelfSignatureSubpackets.Callback bindingSignatureCallback, - @Nonnull SecretKeyRingProtector subkeyProtector, - @Nonnull SecretKeyRingProtector primaryKeyProtector, - @Nonnull KeyFlag keyFlag, - KeyFlag... additionalKeyFlags) - throws PGPException, IOException { - KeyFlag[] flags = concat(keyFlag, additionalKeyFlags); - PublicKeyAlgorithm subkeyAlgorithm = PublicKeyAlgorithm.requireFromId(subkey.getPublicKey().getAlgorithm()); - SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm); - - // check key against public key algorithm policy - PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(subkey.getPublicKey().getAlgorithm()); - int bitStrength = subkey.getPublicKey().getBitStrength(); - if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) { - throw new IllegalArgumentException("Public key algorithm policy violation: " + - publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable."); - } - - PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); - HashAlgorithm hashAlgorithm = HashAlgorithmNegotiator - .negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) - .negotiateHashAlgorithm(info.getPreferredHashAlgorithms()); - - PGPSecretKey secretSubkey = new PGPSecretKey(subkey.getPrivateKey(), subkey.getPublicKey(), ImplementationFactory.getInstance() - .getV4FingerprintCalculator(), false, subkeyProtector.getEncryptor(subkey.getKeyID())); - - SubkeyBindingSignatureBuilder skBindingBuilder = new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector, hashAlgorithm); - skBindingBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - skBindingBuilder.getHashedSubpackets().setKeyFlags(flags); - - if (subkeyAlgorithm.isSigningCapable()) { - PrimaryKeyBindingSignatureBuilder pkBindingBuilder = new PrimaryKeyBindingSignatureBuilder(secretSubkey, subkeyProtector, hashAlgorithm); - pkBindingBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - PGPSignature pkBinding = pkBindingBuilder.build(primaryKey.getPublicKey()); - skBindingBuilder.getHashedSubpackets().addEmbeddedSignature(pkBinding); - } - - skBindingBuilder.applyCallback(bindingSignatureCallback); - PGPSignature skBinding = skBindingBuilder.build(secretSubkey.getPublicKey()); - - secretSubkey = KeyRingUtils.secretKeyPlusSignature(secretSubkey, skBinding); - secretKeyRing = KeyRingUtils.keysPlusSecretKey(secretKeyRing, secretSubkey); - return this; - } - - @Override - public SecretKeyRingEditorInterface revoke(@Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException { - RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); - return revoke(secretKeyRingProtector, callback); - } - - @Override - public SecretKeyRingEditorInterface revoke(@Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) - throws PGPException { - return revokeSubKey(secretKeyRing.getSecretKey().getKeyID(), secretKeyRingProtector, subpacketsCallback); - } - - @Override - public SecretKeyRingEditorInterface revokeSubKey(long subKeyId, - SecretKeyRingProtector protector, - RevocationAttributes revocationAttributes) - throws PGPException { - RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); - return revokeSubKey(subKeyId, protector, callback); - } - - @Override - public SecretKeyRingEditorInterface revokeSubKey(long keyID, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) - throws PGPException { - // retrieve subkey to be revoked - PGPPublicKey revokeeSubKey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, keyID); - // create revocation - PGPSignature subKeyRevocation = generateRevocation(secretKeyRingProtector, revokeeSubKey, - subpacketsCallback); - // inject revocation sig into key ring - secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, revokeeSubKey, subKeyRevocation); - return this; - } - - @Override - public PGPSignature createRevocation(@Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException { - PGPPublicKey revokeeSubKey = secretKeyRing.getPublicKey(); - PGPSignature revocationCertificate = generateRevocation( - secretKeyRingProtector, revokeeSubKey, callbackFromRevocationAttributes(revocationAttributes)); - return revocationCertificate; - } - - @Override - public PGPSignature createRevocation( - long subkeyId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException { - PGPPublicKey revokeeSubkey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, subkeyId); - RevocationSignatureSubpackets.Callback callback = callbackFromRevocationAttributes(revocationAttributes); - return generateRevocation(secretKeyRingProtector, revokeeSubkey, callback); - } - - @Override - public PGPSignature createRevocation( - long subkeyId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback certificateSubpacketsCallback) - throws PGPException { - PGPPublicKey revokeeSubkey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, subkeyId); - return generateRevocation(secretKeyRingProtector, revokeeSubkey, certificateSubpacketsCallback); - } - - private PGPSignature generateRevocation(@Nonnull SecretKeyRingProtector protector, - @Nonnull PGPPublicKey revokeeSubKey, - @Nullable RevocationSignatureSubpackets.Callback callback) - throws PGPException { - PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - SignatureType signatureType = revokeeSubKey.isMasterKey() ? - SignatureType.KEY_REVOCATION : SignatureType.SUBKEY_REVOCATION; - - RevocationSignatureBuilder signatureBuilder = - new RevocationSignatureBuilder(signatureType, primaryKey, protector); - signatureBuilder.applyCallback(callback); - PGPSignature revocation = signatureBuilder.build(revokeeSubKey); - return revocation; - } - - private static RevocationSignatureSubpackets.Callback callbackFromRevocationAttributes( - @Nullable RevocationAttributes attributes) { - return new RevocationSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { - if (attributes != null) { - hashedSubpackets.setRevocationReason(attributes); - } - } - }; - } - - @Override - public SecretKeyRingEditorInterface revokeUserId( - @Nonnull CharSequence userId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException { - if (revocationAttributes != null) { - RevocationAttributes.Reason reason = revocationAttributes.getReason(); - if (reason != RevocationAttributes.Reason.NO_REASON - && reason != RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) { - throw new IllegalArgumentException("Revocation reason must either be NO_REASON or USER_ID_NO_LONGER_VALID"); - } - } - - RevocationSignatureSubpackets.Callback callback = new RevocationSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { - if (revocationAttributes != null) { - hashedSubpackets.setRevocationReason(false, revocationAttributes); - } - } - }; - - return revokeUserId(userId, secretKeyRingProtector, callback); - } - - @Override - public SecretKeyRingEditorInterface revokeUserId( - @Nonnull CharSequence userId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback subpacketCallback) - throws PGPException { - String sanitized = sanitizeUserId(userId); - return revokeUserIds( - SelectUserId.exactMatch(sanitized), - secretKeyRingProtector, - subpacketCallback); - } - - @Override - public SecretKeyRingEditorInterface revokeUserIds( - @Nonnull SelectUserId userIdSelector, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException { - - return revokeUserIds( - userIdSelector, - secretKeyRingProtector, - new RevocationSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(RevocationSignatureSubpackets hashedSubpackets) { - hashedSubpackets.setRevocationReason(revocationAttributes); - } - }); - } - - @Override - public SecretKeyRingEditorInterface revokeUserIds( - @Nonnull SelectUserId userIdSelector, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) - throws PGPException { - List selected = userIdSelector.selectUserIds(secretKeyRing); - if (selected.isEmpty()) { - throw new NoSuchElementException("No matching user-ids found on the key."); - } - - for (String userId : selected) { - doRevokeUserId(userId, secretKeyRingProtector, subpacketsCallback); - } - - return this; - } - - private SecretKeyRingEditorInterface doRevokeUserId( - @Nonnull String userId, - @Nonnull SecretKeyRingProtector protector, - @Nullable RevocationSignatureSubpackets.Callback callback) - throws PGPException { - PGPSecretKey primarySecretKey = secretKeyRing.getSecretKey(); - RevocationSignatureBuilder signatureBuilder = new RevocationSignatureBuilder( - SignatureType.CERTIFICATION_REVOCATION, - primarySecretKey, - protector); - if (referenceTime != null) { - signatureBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - } - - signatureBuilder.applyCallback(callback); - - PGPSignature revocationSignature = signatureBuilder.build(userId); - secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, userId, revocationSignature); - return this; - } - - @Override - public SecretKeyRingEditorInterface setExpirationDate( - @Nullable Date expiration, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException { - - PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - if (!primaryKey.isMasterKey()) { - throw new IllegalArgumentException("Key Ring does not appear to contain a primary secret key."); - } - - // reissue direct key sig - PGPSignature prevDirectKeySig = getPreviousDirectKeySignature(); - if (prevDirectKeySig != null) { - PGPSignature directKeySig = reissueDirectKeySignature(expiration, secretKeyRingProtector, prevDirectKeySig); - secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryKey.getPublicKey(), directKeySig); - } - - // reissue primary user-id sig - String primaryUserId = PGPainless.inspectKeyRing(secretKeyRing, referenceTime).getPossiblyExpiredPrimaryUserId(); - if (primaryUserId != null) { - PGPSignature prevUserIdSig = getPreviousUserIdSignatures(primaryUserId); - PGPSignature userIdSig = reissuePrimaryUserIdSig(expiration, secretKeyRingProtector, primaryUserId, prevUserIdSig); - secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryUserId, userIdSig); - } - - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); - for (String userId : info.getValidUserIds()) { - if (userId.equals(primaryUserId)) { - continue; - } - - PGPSignature prevUserIdSig = info.getLatestUserIdCertification(userId); - if (prevUserIdSig == null) { - throw new AssertionError("A valid user-id shall never have no user-id signature."); - } - - if (prevUserIdSig.getHashedSubPackets().isPrimaryUserID()) { - assert (primaryUserId != null); - PGPSignature userIdSig = reissueNonPrimaryUserId(secretKeyRingProtector, userId, prevUserIdSig); - secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryUserId, userIdSig); - } - } - - return this; - } - - @Override - public PGPPublicKeyRing createMinimalRevocationCertificate( - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes keyRevocationAttributes) - throws PGPException { - // Check reason - if (keyRevocationAttributes != null && !RevocationAttributes.Reason.isKeyRevocation(keyRevocationAttributes.getReason())) { - throw new IllegalArgumentException("Revocation reason MUST be applicable to a key revocation."); - } - - PGPSignature revocation = createRevocation(secretKeyRingProtector, keyRevocationAttributes); - PGPPublicKey primaryKey = secretKeyRing.getSecretKey().getPublicKey(); - primaryKey = KeyRingUtils.getStrippedDownPublicKey(primaryKey); - primaryKey = PGPPublicKey.addCertification(primaryKey, revocation); - return new PGPPublicKeyRing(Collections.singletonList(primaryKey)); - } - - private PGPSignature reissueNonPrimaryUserId( - SecretKeyRingProtector secretKeyRingProtector, - String userId, - PGPSignature prevUserIdSig) - throws PGPException { - SelfSignatureBuilder builder = new SelfSignatureBuilder(secretKeyRing.getSecretKey(), secretKeyRingProtector, prevUserIdSig); - if (referenceTime != null) { - builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - } - builder.applyCallback(new SelfSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { - // unmark as primary - hashedSubpackets.setPrimaryUserId(null); - } - }); - return builder.build(secretKeyRing.getPublicKey(), userId); - } - - private PGPSignature reissuePrimaryUserIdSig( - @Nullable Date expiration, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nonnull String primaryUserId, - @Nonnull PGPSignature prevUserIdSig) - throws PGPException { - PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - PGPPublicKey publicKey = primaryKey.getPublicKey(); - - SelfSignatureBuilder builder = new SelfSignatureBuilder(primaryKey, secretKeyRingProtector, prevUserIdSig); - if (referenceTime != null) { - builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - } - builder.applyCallback(new SelfSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { - if (expiration != null) { - hashedSubpackets.setKeyExpirationTime(true, publicKey.getCreationTime(), expiration); - } else { - hashedSubpackets.setKeyExpirationTime(new KeyExpirationTime(true, 0)); - } - hashedSubpackets.setPrimaryUserId(); - } - }); - return builder.build(publicKey, primaryUserId); - } - - private PGPSignature reissueDirectKeySignature( - Date expiration, - SecretKeyRingProtector secretKeyRingProtector, - PGPSignature prevDirectKeySig) - throws PGPException { - PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - PGPPublicKey publicKey = primaryKey.getPublicKey(); - final Date keyCreationTime = publicKey.getCreationTime(); - - DirectKeySelfSignatureBuilder builder = new DirectKeySelfSignatureBuilder(primaryKey, secretKeyRingProtector, prevDirectKeySig); - if (referenceTime != null) { - builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - } - builder.applyCallback(new SelfSignatureSubpackets.Callback() { - @Override - public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { - if (expiration != null) { - hashedSubpackets.setKeyExpirationTime(keyCreationTime, expiration); - } else { - hashedSubpackets.setKeyExpirationTime(null); - } - } - }); - - return builder.build(publicKey); - } - - private PGPSignature getPreviousDirectKeySignature() { - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); - return info.getLatestDirectKeySelfSignature(); - } - - private PGPSignature getPreviousUserIdSignatures(String userId) { - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeyRing, referenceTime); - return info.getLatestUserIdCertification(userId); - } - - @Override - public WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase( - @Nullable Passphrase oldPassphrase, - @Nonnull KeyRingProtectionSettings oldProtectionSettings) { - SecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector( - oldProtectionSettings, - new SolitaryPassphraseProvider(oldPassphrase)); - - return new WithKeyRingEncryptionSettingsImpl(null, protector); - } - - @Override - public WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase( - @Nonnull Long keyId, - @Nullable Passphrase oldPassphrase, - @Nonnull KeyRingProtectionSettings oldProtectionSettings) { - Map passphraseMap = Collections.singletonMap(keyId, oldPassphrase); - SecretKeyRingProtector protector = new CachingSecretKeyRingProtector( - passphraseMap, oldProtectionSettings, null); - - return new WithKeyRingEncryptionSettingsImpl(keyId, protector); - } - - @Override - public PGPSecretKeyRing done() { - return secretKeyRing; - } - - private final class WithKeyRingEncryptionSettingsImpl implements WithKeyRingEncryptionSettings { - - private final Long keyId; - // Protector to unlock the key with the old passphrase - private final SecretKeyRingProtector oldProtector; - - /** - * Builder for selecting protection settings. - * - * If the keyId is null, the whole keyRing will get the same new passphrase. - * - * @param keyId id of the subkey whose passphrase will be changed, or null. - * @param oldProtector protector do unlock the key/ring. - */ - private WithKeyRingEncryptionSettingsImpl(Long keyId, SecretKeyRingProtector oldProtector) { - this.keyId = keyId; - this.oldProtector = oldProtector; - } - - @Override - public WithPassphrase withSecureDefaultSettings() { - return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings()); - } - - @Override - public WithPassphrase withCustomSettings(KeyRingProtectionSettings settings) { - return new WithPassphraseImpl(keyId, oldProtector, settings); - } - } - - private final class WithPassphraseImpl implements WithPassphrase { - - private final SecretKeyRingProtector oldProtector; - private final KeyRingProtectionSettings newProtectionSettings; - private final Long keyId; - - private WithPassphraseImpl( - Long keyId, - SecretKeyRingProtector oldProtector, - KeyRingProtectionSettings newProtectionSettings) { - this.keyId = keyId; - this.oldProtector = oldProtector; - this.newProtectionSettings = newProtectionSettings; - } - - @Override - public SecretKeyRingEditorInterface toNewPassphrase(Passphrase passphrase) - throws PGPException { - SecretKeyRingProtector newProtector = new PasswordBasedSecretKeyRingProtector( - newProtectionSettings, new SolitaryPassphraseProvider(passphrase)); - - PGPSecretKeyRing secretKeys = changePassphrase( - keyId, SecretKeyRingEditor.this.secretKeyRing, oldProtector, newProtector); - SecretKeyRingEditor.this.secretKeyRing = secretKeys; - - return SecretKeyRingEditor.this; - } - - @Override - public SecretKeyRingEditorInterface toNoPassphrase() - throws PGPException { - SecretKeyRingProtector newProtector = new UnprotectedKeysProtector(); - - PGPSecretKeyRing secretKeys = changePassphrase( - keyId, SecretKeyRingEditor.this.secretKeyRing, oldProtector, newProtector); - SecretKeyRingEditor.this.secretKeyRing = secretKeys; - - return SecretKeyRingEditor.this; - } - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java deleted file mode 100644 index dd7ed499..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.java +++ /dev/null @@ -1,652 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// 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.Date; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyPair; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -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; - -public interface SecretKeyRingEditorInterface { - - /** - * Return the editors reference time. - * - * @return reference time - */ - @Nonnull - Date getReferenceTime(); - - /** - * Add a user-id to the key ring. - * - * @param userId user-id - * @param secretKeyRingProtector protector to unlock the secret key - * @return the builder - * - * @throws PGPException in case we cannot generate a signature for the user-id - */ - SecretKeyRingEditorInterface addUserId( - @Nonnull CharSequence userId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException; - - /** - * Add a user-id to the key ring. - * - * @param userId user-id - * @param signatureSubpacketCallback callback that can be used to modify signature subpackets of the - * certification signature. - * @param protector protector to unlock the primary secret key - * @return the builder - * - * @throws PGPException in case we cannot generate a signature for the user-id - */ - SecretKeyRingEditorInterface addUserId( - @Nonnull CharSequence userId, - @Nullable SelfSignatureSubpackets.Callback signatureSubpacketCallback, - @Nonnull SecretKeyRingProtector protector) - throws PGPException; - - /** - * 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 - */ - SecretKeyRingEditorInterface addPrimaryUserId( - @Nonnull CharSequence userId, - @Nonnull SecretKeyRingProtector protector) - throws PGPException; - - /** - * Convenience method to revoke selected user-ids using soft revocation signatures. - * The revocation will use {@link RevocationAttributes.Reason#USER_ID_NO_LONGER_VALID}, so that the user-id - * can be re-certified at a later point. - * - * @param userIdSelector 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 - */ - SecretKeyRingEditorInterface removeUserId(SelectUserId userIdSelector, - SecretKeyRingProtector protector) - throws PGPException; - - /** - * Convenience method to revoke a single user-id using a soft revocation signature. - * The revocation will use {@link 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 - */ - SecretKeyRingEditorInterface removeUserId(CharSequence userId, - SecretKeyRingProtector protector) - throws PGPException; - - /** - * 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 {@link 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 - */ - SecretKeyRingEditorInterface replaceUserId(CharSequence oldUserId, - CharSequence newUserId, - SecretKeyRingProtector protector) - throws PGPException; - - /** - * Add a subkey to the key ring. - * The subkey will be generated from the provided {@link KeySpec}. - * - * @param keySpec key specification - * @param subKeyPassphrase passphrase to encrypt the sub key - * @param secretKeyRingProtector 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 - */ - SecretKeyRingEditorInterface addSubKey( - @Nonnull KeySpec keySpec, - @Nonnull Passphrase subKeyPassphrase, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException; - - /** - * Add a subkey to the key ring. - * The subkey will be generated from the provided {@link KeySpec}. - * - * @param keySpec key spec of the subkey - * @param subkeyPassphrase passphrase to encrypt the subkey - * @param subpacketsCallback callback to modify the subpackets of the subkey binding signature - * @param secretKeyRingProtector protector to unlock the primary key - * @return 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 - */ - SecretKeyRingEditorInterface addSubKey( - @Nonnull KeySpec keySpec, - @Nonnull Passphrase subkeyPassphrase, - @Nullable SelfSignatureSubpackets.Callback subpacketsCallback, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException; - - /** - * Add a subkey to the key ring. - * - * @param subkey subkey key pair - * @param bindingSignatureCallback 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 key flag for the subkey - * @param additionalKeyFlags 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 - */ - SecretKeyRingEditorInterface addSubKey( - @Nonnull PGPKeyPair subkey, - @Nullable SelfSignatureSubpackets.Callback bindingSignatureCallback, - @Nonnull SecretKeyRingProtector subkeyProtector, - @Nonnull SecretKeyRingProtector primaryKeyProtector, - @Nonnull KeyFlag keyFlag, - KeyFlag... additionalKeyFlags) - throws PGPException, IOException; - - /** - * Revoke the key ring. - * The revocation will be a hard revocation, rendering the whole key invalid for any past or future signatures. - * - * @param secretKeyRingProtector protector of the primary key - * @return the builder - * - * @throws PGPException in case we cannot generate a revocation signature - */ - default SecretKeyRingEditorInterface revoke( - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException { - return revoke(secretKeyRingProtector, (RevocationAttributes) null); - } - - /** - * Revoke the key ring using the provided revocation attributes. - * The attributes define, whether the revocation was a hard revocation or not. - * - * @param secretKeyRingProtector protector of the primary key - * @param revocationAttributes reason for the revocation - * @return the builder - * - * @throws PGPException in case we cannot generate a revocation signature - */ - SecretKeyRingEditorInterface revoke( - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException; - - /** - * Revoke the key ring. - * You can use the {@link RevocationSignatureSubpackets.Callback} to modify the revocation signatures - * subpackets, e.g. in order to define whether this is a hard or soft revocation. - * - * @param secretKeyRingProtector protector to unlock the primary secret key - * @param subpacketsCallback callback to modify the revocations subpackets - * @return builder - * - * @throws PGPException in case we cannot generate a revocation signature - */ - SecretKeyRingEditorInterface revoke( - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) throws PGPException; - - /** - * Revoke the subkey binding signature of a subkey. - * The subkey with the provided fingerprint will be revoked. - * If no suitable subkey is found, a {@link java.util.NoSuchElementException} will be thrown. - * - * Note: This method will hard-revoke the provided subkey, meaning it cannot be re-certified at a later point. - * If you instead want to temporarily "deactivate" the subkey, provide a soft revocation reason, - * e.g. by calling {@link #revokeSubKey(OpenPgpFingerprint, SecretKeyRingProtector, RevocationAttributes)} - * and provide a suitable {@link RevocationAttributes} object. - * - * @param fingerprint fingerprint of the subkey to be revoked - * @param secretKeyRingProtector protector to unlock the secret key ring - * @return the builder - * - * @throws PGPException in case we cannot generate a revocation signature for the subkey - */ - default SecretKeyRingEditorInterface revokeSubKey( - @Nonnull OpenPgpFingerprint fingerprint, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException { - return revokeSubKey(fingerprint, secretKeyRingProtector, 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 {@link java.util.NoSuchElementException} will be thrown. - * - * @param fingerprint fingerprint of the subkey to be revoked - * @param secretKeyRingProtector 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 - */ - default SecretKeyRingEditorInterface revokeSubKey( - OpenPgpFingerprint fingerprint, - SecretKeyRingProtector secretKeyRingProtector, - RevocationAttributes revocationAttributes) - throws PGPException { - return revokeSubKey(fingerprint.getKeyId(), - secretKeyRingProtector, - 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 {@link java.util.NoSuchElementException} will be thrown. - * - * @param subKeyId id of the subkey - * @param secretKeyRingProtector 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 - */ - SecretKeyRingEditorInterface revokeSubKey( - long subKeyId, - SecretKeyRingProtector secretKeyRingProtector, - RevocationAttributes revocationAttributes) - throws PGPException; - - /** - * Revoke the subkey binding signature of a subkey. - * The subkey with the provided key-id will be revoked. - * If no suitable subkey is found, q {@link java.util.NoSuchElementException} will be thrown. - * - * Note: This method will hard-revoke the subkey, meaning it cannot be re-bound at a later point. - * If you intend to re-bind the subkey in order to make it usable again at a later point in time, - * consider using {@link #revokeSubKey(long, SecretKeyRingProtector, RevocationAttributes)} - * and provide a soft revocation reason. - * - * @param subKeyId id of the subkey - * @param secretKeyRingProtector protector to unlock the secret key ring - * @return the builder - * - * @throws PGPException in case we cannot generate a revocation signature for the subkey - */ - default SecretKeyRingEditorInterface revokeSubKey( - long subKeyId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException { - - return revokeSubKey( - subKeyId, - secretKeyRingProtector, - (RevocationSignatureSubpackets.Callback) null); - } - - /** - * Revoke the subkey binding signature of a subkey. - * The subkey with the provided key-id will be revoked. - * If no suitable subkey is found, q {@link java.util.NoSuchElementException} will be thrown. - * - * The provided subpackets callback is used to modify the revocation signatures subpackets. - * - * @param keyID id of the subkey - * @param secretKeyRingProtector protector to unlock the secret key ring - * @param subpacketsCallback 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 - */ - SecretKeyRingEditorInterface revokeSubKey( - long keyID, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) - throws PGPException; - - /** - * Revoke the given userID. - * The revocation will be a hard revocation, rendering the user-id invalid for any past or future signatures. - * If you intend to re-certify the user-id at a later point in time, consider using - * {@link #revokeUserId(CharSequence, SecretKeyRingProtector, RevocationAttributes)} instead and provide - * a soft revocation reason. - * - * @param userId userId to revoke - * @param secretKeyRingProtector protector to unlock the primary key - * @return the builder - * - * @throws PGPException in case we cannot generate a revocation signature for the user-id - */ - default SecretKeyRingEditorInterface revokeUserId( - @Nonnull CharSequence userId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException { - return revokeUserId(userId, secretKeyRingProtector, (RevocationAttributes) null); - } - - /** - * Revoke the given userID using the provided revocation attributes. - * - * @param userId userId to revoke - * @param secretKeyRingProtector 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 - */ - SecretKeyRingEditorInterface revokeUserId( - @Nonnull CharSequence userId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException; - - /** - * Revoke the provided user-id. - * Note: If you don't provide a {@link RevocationSignatureSubpackets.Callback} which - * sets a revocation reason ({@link RevocationAttributes}), the revocation might 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 secretKeyRingProtector protector to unlock the primary secret key - * @param subpacketCallback callback to modify the revocations subpackets - * @return builder - * - * @throws PGPException in case we cannot generate a revocation signature for the user-id - */ - SecretKeyRingEditorInterface revokeUserId( - @Nonnull CharSequence userId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback subpacketCallback) - throws PGPException; - - /** - * Revoke all user-ids that match the provided {@link SelectUserId} filter. - * The provided {@link 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 {@link RevocationAttributes.Reason} for more information. - * - * @param userIdSelector user-id selector - * @param secretKeyRingProtector 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 - */ - SecretKeyRingEditorInterface revokeUserIds( - @Nonnull SelectUserId userIdSelector, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException; - - /** - * Revoke all user-ids that match the provided {@link SelectUserId} filter. - * The provided {@link 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 {@link RevocationAttributes.Reason} for more information. - * - * @param userIdSelector user-id selector - * @param secretKeyRingProtector protector to unlock the primary secret key - * @param subpacketsCallback callback to modify the revocations subpackets - * @return builder - * - * @throws PGPException in case we cannot generate a revocation signature for the user-id - */ - SecretKeyRingEditorInterface revokeUserIds( - @Nonnull SelectUserId userIdSelector, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback subpacketsCallback) - throws PGPException; - - /** - * 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 secretKeyRingProtector to unlock the secret key - * @return the builder - * - * @throws PGPException in case we cannot generate a new self-signature with the changed expiration date - */ - SecretKeyRingEditorInterface setExpirationDate( - @Nullable Date expiration, - @Nonnull SecretKeyRingProtector secretKeyRingProtector) - throws PGPException; - - /** - * 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 secretKeyRingProtector protector to unlock the primary key. - * @param keyRevocationAttributes reason for the revocation (key revocation) - * @return minimal revocation certificate - * - * @throws PGPException in case we cannot generate a revocation signature - */ - PGPPublicKeyRing createMinimalRevocationCertificate(@Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes keyRevocationAttributes) - throws PGPException; - - /** - * 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 secretKeyRingProtector 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 - */ - PGPSignature createRevocation( - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException; - - /** - * 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 secretKeyRingProtector 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 - */ - PGPSignature createRevocation( - long subkeyId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException; - - /** - * 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 secretKeyRingProtector protector to unlock the primary key. - * @param certificateSubpacketsCallback callback to modify the subpackets of the revocation certificate. - * @return revocation certificate - * - * @throws PGPException in case we cannot generate a revocation certificate - */ - PGPSignature createRevocation( - long subkeyId, - @Nonnull SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationSignatureSubpackets.Callback certificateSubpacketsCallback) - throws PGPException; - - /** - * 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 secretKeyRingProtector 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 - */ - default PGPSignature createRevocation( - OpenPgpFingerprint subkeyFingerprint, - SecretKeyRingProtector secretKeyRingProtector, - @Nullable RevocationAttributes revocationAttributes) - throws PGPException { - - return createRevocation( - subkeyFingerprint.getKeyId(), - secretKeyRingProtector, - revocationAttributes); - } - - /** - * Change the passphrase of the whole key ring. - * - * @param oldPassphrase old passphrase or null, if the key was unprotected - * @return next builder step - */ - default WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase( - @Nullable Passphrase oldPassphrase) { - return changePassphraseFromOldPassphrase(oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings()); - } - - /** - * Change the passphrase of the whole key ring. - * - * @param oldPassphrase old passphrase or null, if the key was unprotected - * @param oldProtectionSettings custom settings for the old passphrase - * @return next builder step - */ - WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase( - @Nullable Passphrase oldPassphrase, - @Nonnull KeyRingProtectionSettings oldProtectionSettings); - - /** - * 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 - * @return next builder step - */ - default WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase( - @Nonnull Long keyId, - @Nullable Passphrase oldPassphrase) { - return changeSubKeyPassphraseFromOldPassphrase(keyId, oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings()); - } - - WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase( - @Nonnull Long keyId, - @Nullable Passphrase oldPassphrase, - @Nonnull KeyRingProtectionSettings oldProtectionSettings); - - interface WithKeyRingEncryptionSettings { - - /** - * Set secure default settings for the symmetric passphrase encryption. - * Note that this obviously has no effect if you decide to set {@link WithPassphrase#toNoPassphrase()}. - * - * @return next builder step - */ - WithPassphrase withSecureDefaultSettings(); - - /** - * Set custom settings for the symmetric passphrase encryption. - * - * @param settings custom settings - * @return next builder step - */ - WithPassphrase withCustomSettings(KeyRingProtectionSettings settings); - - } - - interface WithPassphrase { - - /** - * Set the passphrase. - * - * @param passphrase passphrase - * @return editor builder - * - * @throws PGPException in case the passphrase cannot be changed - */ - SecretKeyRingEditorInterface toNewPassphrase(Passphrase passphrase) - throws PGPException; - - /** - * Leave the key unprotected. - * - * @return editor builder - * - * @throws PGPException in case the passphrase cannot be changed - */ - SecretKeyRingEditorInterface toNoPassphrase() throws PGPException; - } - - /** - * Return the {@link PGPSecretKeyRing}. - * @return the key - */ - PGPSecretKeyRing done(); - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/package-info.java deleted file mode 100644 index 6b3eb3b3..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes that deal with modifications made to {@link org.bouncycastle.openpgp.PGPSecretKeyRing PGPSecretKeyRings}. - */ -package org.pgpainless.key.modification.secretkeyring; diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt index 7ccddb42..afa38aa6 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt @@ -4,6 +4,7 @@ package org.bouncycastle.extensions +import openpgp.openPgpKeyId import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPOnePassSignature import org.bouncycastle.openpgp.PGPPublicKey @@ -46,6 +47,12 @@ fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean = fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = this.getPublicKey(fingerprint.bytes) +fun PGPKeyRing.requirePublicKey(keyId: Long): PGPPublicKey = + getPublicKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.") + +fun PGPKeyRing.requirePublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey = + getPublicKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.") + /** * Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. * If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt index 6ccdc81d..208d8060 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt @@ -212,6 +212,48 @@ class KeyRingInfo( else userIdExpirationDate } + /** + * List of all subkeys that can be used to sign a message. + */ + val signingSubkeys: List = validSubkeys.filter { getKeyFlagsOf(it.keyID).contains(KeyFlag.SIGN_DATA) } + + /** + * Whether the key is usable for encryption. + */ + val isUsableForEncryption: Boolean = isUsableForEncryption(EncryptionPurpose.ANY) + + /** + * Whether the key is capable of signing messages. + * This field is also true, if the key contains a subkey that is capable of signing messages, but where the secret + * key is unavailable, e.g. because it was moved to a smart-card. + * + * To check for keys that are actually usable to sign messages, use [isUsableForSigning]. + */ + val isSigningCapable: Boolean = isKeyValidlyBound(keyId) && signingSubkeys.isNotEmpty() + + /** + * Whether the key is actually usable to sign messages. + */ + val isUsableForSigning: Boolean = isSigningCapable && signingSubkeys.any { isSecretKeyAvailable(it.keyID) } + + /** + * [HashAlgorithm] preferences of the primary user-ID or if absent, of the primary key. + */ + val preferredHashAlgorithms: Set + get() = primaryUserId?.let { getPreferredHashAlgorithms(it) } ?: getPreferredHashAlgorithms(keyId) + + /** + * [SymmetricKeyAlgorithm] preferences of the primary user-ID or if absent of the primary key. + */ + val preferredSymmetricKeyAlgorithms: Set + get() = primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) } ?: getPreferredSymmetricKeyAlgorithms(keyId) + + /** + * [CompressionAlgorithm] preferences of the primary user-ID or if absent, the primary key. + */ + val preferredCompressionAlgorithms: Set + get() = primaryUserId?.let { getPreferredCompressionAlgorithms(it) } ?: getPreferredCompressionAlgorithms(keyId) + /** * Return the expiration date of the subkey with the provided fingerprint. * @@ -331,16 +373,6 @@ class KeyRingInfo( }.toList() } - /** - * List of all subkeys that can be used to sign a message. - */ - val signingSubkeys: List = validSubkeys.filter { getKeyFlagsOf(it.keyID).contains(KeyFlag.SIGN_DATA) } - - /** - * Whether the key is usable for encryption. - */ - val isUsableForEncryption: Boolean = isUsableForEncryption(EncryptionPurpose.ANY) - /** * Return, whether the key is usable for encryption, given the purpose. * @@ -350,20 +382,6 @@ class KeyRingInfo( return isKeyValidlyBound(keyId) && getEncryptionSubkeys(purpose).isNotEmpty() } - /** - * Whether the key is capable of signing messages. - * This field is also true, if the key contains a subkey that is capable of signing messages, but where the secret - * key is unavailable, e.g. because it was moved to a smart-card. - * - * To check for keys that are actually usable to sign messages, use [isUsableForSigning]. - */ - val isSigningCapable: Boolean = isKeyValidlyBound(keyId) && signingSubkeys.isNotEmpty() - - /** - * Whether the key is actually usable to sign messages. - */ - val isUsableForSigning: Boolean = isSigningCapable && signingSubkeys.any { isSecretKeyAvailable(it.keyID) } - /** * Return the primary user-ID, even if it is possibly expired. * @@ -621,7 +639,7 @@ class KeyRingInfo( return false } if (sig.hashedSubPackets.isPrimaryUserID) { - SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(sig, publicKey)?.let { expirationDate -> + getKeyExpirationTimeAsDate(sig, publicKey)?.let { expirationDate -> // key expired? if (expirationDate < referenceDate) return false } @@ -634,12 +652,6 @@ class KeyRingInfo( } ?: true // certification, but no revocation } ?: false // no certification - /** - * [HashAlgorithm] preferences of the primary user-ID or if absent, of the primary key. - */ - val preferredHashAlgorithms: Set - get() = primaryUserId?.let { getPreferredHashAlgorithms(it) } ?: getPreferredHashAlgorithms(keyId) - /** * [HashAlgorithm] preferences of the given user-ID. */ @@ -654,12 +666,6 @@ class KeyRingInfo( return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredHashAlgorithms } - /** - * [SymmetricKeyAlgorithm] preferences of the primary user-ID or if absent of the primary key. - */ - val preferredSymmetricKeyAlgorithms: Set - get() = primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) } ?: getPreferredSymmetricKeyAlgorithms(keyId) - /** * [SymmetricKeyAlgorithm] preferences of the given user-ID. */ @@ -674,12 +680,6 @@ class KeyRingInfo( return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredSymmetricKeyAlgorithms } - /** - * [CompressionAlgorithm] preferences of the primary user-ID or if absent, the primary key. - */ - val preferredCompressionAlgorithms: Set - get() = primaryUserId?.let { getPreferredCompressionAlgorithms(it) } ?: getPreferredCompressionAlgorithms(keyId) - /** * [CompressionAlgorithm] preferences of the given user-ID. */ diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt new file mode 100644 index 00000000..2fde469c --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt @@ -0,0 +1,493 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.modification.secretkeyring + +import org.bouncycastle.bcpg.sig.KeyExpirationTime +import org.bouncycastle.extensions.getKeyExpirationDate +import org.bouncycastle.extensions.publicKeyAlgorithm +import org.bouncycastle.extensions.requirePublicKey +import org.bouncycastle.openpgp.* +import org.pgpainless.PGPainless +import org.pgpainless.PGPainless.Companion.inspectKeyRing +import org.pgpainless.algorithm.AlgorithmSuite +import org.pgpainless.algorithm.Feature +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.algorithm.SignatureType +import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.generation.KeyRingBuilder +import org.pgpainless.key.generation.KeySpec +import org.pgpainless.key.protection.* +import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider +import org.pgpainless.key.util.KeyRingUtils +import org.pgpainless.key.util.KeyRingUtils.Companion.changePassphrase +import org.pgpainless.key.util.KeyRingUtils.Companion.injectCertification +import org.pgpainless.key.util.RevocationAttributes +import org.pgpainless.signature.builder.* +import org.pgpainless.signature.subpackets.* +import org.pgpainless.util.Passphrase +import org.pgpainless.util.selection.userid.SelectUserId +import java.util.* +import javax.annotation.Nonnull + +class SecretKeyRingEditor( + var secretKeyRing: PGPSecretKeyRing, + override val referenceTime: Date = Date() +) : SecretKeyRingEditorInterface { + + override fun addUserId(userId: CharSequence, + callback: SelfSignatureSubpackets.Callback?, + protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + val sanitizedUserId = sanitizeUserId(userId).toString() + val primaryKey = secretKeyRing.secretKey + + val info = inspectKeyRing(secretKeyRing, referenceTime) + require(!info.isHardRevoked(userId)) { + "User-ID $userId is hard revoked and cannot be re-certified." + } + + val (hashAlgorithmPreferences, symmetricKeyAlgorithmPreferences, compressionAlgorithmPreferences) = try { + Triple(info.preferredHashAlgorithms, info.preferredSymmetricKeyAlgorithms, info.preferredCompressionAlgorithms) + } catch (e : IllegalStateException) { // missing user-id sig + val algorithmSuite = AlgorithmSuite.defaultAlgorithmSuite + Triple(algorithmSuite.hashAlgorithms, algorithmSuite.symmetricKeyAlgorithms, algorithmSuite.compressionAlgorithms) + } + + val builder = SelfSignatureBuilder(primaryKey, protector).apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + setSignatureType(SignatureType.POSITIVE_CERTIFICATION) + } + builder.hashedSubpackets.apply { + setKeyFlags(info.getKeyFlagsOf(primaryKey.keyID)) + setPreferredHashAlgorithms(hashAlgorithmPreferences) + setPreferredSymmetricKeyAlgorithms(symmetricKeyAlgorithmPreferences) + setPreferredCompressionAlgorithms(compressionAlgorithmPreferences) + setFeatures(Feature.MODIFICATION_DETECTION) + } + builder.applyCallback(callback) + secretKeyRing = injectCertification(secretKeyRing, sanitizedUserId, builder.build(primaryKey.publicKey, sanitizedUserId)) + return this + } + + override fun addPrimaryUserId(userId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + val uid = sanitizeUserId(userId) + val primaryKey = secretKeyRing.publicKey + var info = inspectKeyRing(secretKeyRing, referenceTime) + val primaryUserId = info.primaryUserId + val signature = if (primaryUserId == null) info.latestDirectKeySelfSignature else info.getLatestUserIdCertification(primaryUserId) + val previousKeyExpiration = signature?.getKeyExpirationDate(primaryKey.creationTime) + + // Add new primary user-id signature + addUserId(uid, object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + hashedSubpackets.apply { + setPrimaryUserId() + if (previousKeyExpiration != null) setKeyExpirationTime(primaryKey, previousKeyExpiration) + else setKeyExpirationTime(null) + } + } + }, protector) + + // unmark previous primary user-ids to be non-primary + info = inspectKeyRing(secretKeyRing, referenceTime) + info.validAndExpiredUserIds.filterNot { it == uid }.forEach { otherUserId -> + if (info.getLatestUserIdCertification(otherUserId)!!.hashedSubPackets.isPrimaryUserID) { + // We need to unmark this user-id as primary + addUserId(otherUserId, object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + hashedSubpackets.apply { + setPrimaryUserId(null) + setKeyExpirationTime(null) // non-primary + } + } + }, protector) + } + } + return this + } + + override fun removeUserId(selector: SelectUserId, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + return revokeUserIds(selector, protector, RevocationAttributes.createCertificateRevocation() + .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) + .withoutDescription()) + } + + override fun removeUserId(userId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + return removeUserId(SelectUserId.exactMatch(userId), protector) + } + + override fun replaceUserId(oldUserId: CharSequence, newUserId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + val oldUID = sanitizeUserId(oldUserId) + val newUID = sanitizeUserId(newUserId) + require(oldUID.isNotBlank()) { "Old user-ID cannot be empty." } + require(newUID.isNotBlank()) { "New user-ID cannot be empty." } + + val info = inspectKeyRing(secretKeyRing, referenceTime) + if (!info.isUserIdValid(oldUID)) { + throw NoSuchElementException("Key does not carry user-ID '$oldUID', or it is not valid.") + } + + val oldCertification = info.getLatestUserIdCertification(oldUID) + ?: throw AssertionError("Certification for old user-ID MUST NOT be null.") + + addUserId(newUID, object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets?) { + SignatureSubpacketsHelper.applyFrom(oldCertification.hashedSubPackets, hashedSubpackets as SignatureSubpackets) + if (oldUID == info.primaryUserId && !oldCertification.hashedSubPackets.isPrimaryUserID) { + hashedSubpackets.setPrimaryUserId() + } + } + + override fun modifyUnhashedSubpackets(unhashedSubpackets: SelfSignatureSubpackets?) { + SignatureSubpacketsHelper.applyFrom(oldCertification.unhashedSubPackets, unhashedSubpackets as SignatureSubpackets) + } + }, protector) + + return revokeUserId(oldUID, protector) + } + + override fun addSubKey(keySpec: KeySpec, + subkeyPassphrase: Passphrase, + protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + val callback = object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets?) { + SignatureSubpacketsHelper.applyFrom(keySpec.subpackets, hashedSubpackets as SignatureSubpackets) + } + } + return addSubKey(keySpec, subkeyPassphrase, callback, protector) + } + + override fun addSubKey(keySpec: KeySpec, + subkeyPassphrase: Passphrase, + callback: SelfSignatureSubpackets.Callback?, + protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + val keyPair = KeyRingBuilder.generateKeyPair(keySpec) + val subkeyProtector = PasswordBasedSecretKeyRingProtector.forKeyId(keyPair.keyID, subkeyPassphrase) + val keyFlags = KeyFlag.fromBitmask(keySpec.subpackets.keyFlags).toMutableList() + return addSubKey(keyPair, callback, subkeyProtector, protector, keyFlags.removeFirst(), *keyFlags.toTypedArray()) + } + + override fun addSubKey(subkey: PGPKeyPair, + callback: SelfSignatureSubpackets.Callback?, + subkeyProtector: SecretKeyRingProtector, + primaryKeyProtector: SecretKeyRingProtector, + keyFlag: KeyFlag, + vararg keyFlags: KeyFlag): SecretKeyRingEditorInterface { + val flags = listOf(keyFlag).plus(keyFlags) + val subkeyAlgorithm = subkey.publicKey.publicKeyAlgorithm + SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm) + + val bitStrength = subkey.publicKey.bitStrength + require(PGPainless.getPolicy().publicKeyAlgorithmPolicy.isAcceptable(subkeyAlgorithm, bitStrength)) { + "Public key algorithm policy violation: $subkeyAlgorithm with bit strength $bitStrength is not acceptable." + } + + val primaryKey = secretKeyRing.secretKey + val info = inspectKeyRing(secretKeyRing, referenceTime) + val hashAlgorithm = HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) + .negotiateHashAlgorithm(info.preferredHashAlgorithms) + + var secretSubkey = PGPSecretKey(subkey.privateKey, subkey.publicKey, + ImplementationFactory.getInstance().v4FingerprintCalculator, + false, subkeyProtector.getEncryptor(subkey.keyID)) + val skBindingBuilder = SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector, hashAlgorithm) + skBindingBuilder.apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + hashedSubpackets.setKeyFlags(flags) + if (subkeyAlgorithm.isSigningCapable()) { + val pkBindingBuilder = PrimaryKeyBindingSignatureBuilder(secretSubkey, subkeyProtector, hashAlgorithm) + pkBindingBuilder.hashedSubpackets.setSignatureCreationTime(referenceTime) + hashedSubpackets.addEmbeddedSignature(pkBindingBuilder.build(primaryKey.publicKey)) + } + applyCallback(callback) + } + secretSubkey = KeyRingUtils.secretKeyPlusSignature(secretSubkey, skBindingBuilder.build(secretSubkey.publicKey)) + secretKeyRing = KeyRingUtils.keysPlusSecretKey(secretKeyRing, secretSubkey) + return this + } + + override fun revoke(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { + return revoke(protector, callbackFromRevocationAttributes(revocationAttributes)) + } + + override fun revoke(protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { + return revokeSubKey(secretKeyRing.secretKey.keyID, protector, callback) + } + + override fun revokeSubKey(subkeyId: Long, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { + return revokeSubKey(subkeyId, protector, callbackFromRevocationAttributes(revocationAttributes)) + } + + override fun revokeSubKey(subkeyId: Long, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { + val revokeeSubKey = secretKeyRing.requirePublicKey(subkeyId) + val subkeyRevocation = generateRevocation(protector, revokeeSubKey, callback) + secretKeyRing = injectCertification(secretKeyRing, revokeeSubKey, subkeyRevocation) + return this + } + + override fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { + if (revocationAttributes != null) { + require(revocationAttributes.reason == RevocationAttributes.Reason.NO_REASON || + revocationAttributes.reason == RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) { + "Revocation reason must either be NO_REASON or USER_ID_NO_LONGER_VALID" + } + } + + return revokeUserId(userId, protector, object : RevocationSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { + if (revocationAttributes != null) { + hashedSubpackets.setRevocationReason(false, revocationAttributes) + } + } + }) + } + + override fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { + return revokeUserIds(SelectUserId.exactMatch(sanitizeUserId(userId)), protector, callback) + } + + override fun revokeUserIds(selector: SelectUserId, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { + return revokeUserIds(selector, protector, object : RevocationSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { + if (revocationAttributes != null) { + hashedSubpackets.setRevocationReason(revocationAttributes) + } + } + }) + } + + override fun revokeUserIds(selector: SelectUserId, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { + selector.selectUserIds(secretKeyRing).also { + if (it.isEmpty()) throw NoSuchElementException("No matching user-ids found on the key.") + }.forEach { userId -> doRevokeUserId(userId, protector, callback) } + return this + } + + override fun setExpirationDate(expiration: Date?, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + require(secretKeyRing.secretKey.isMasterKey) { + "OpenPGP key does not appear to contain a primary secret key." + } + + val prevDirectKeySig = getPreviousDirectKeySignature() + // reissue direct key sig + if (prevDirectKeySig != null) { + secretKeyRing = injectCertification(secretKeyRing, secretKeyRing.publicKey, + reissueDirectKeySignature(expiration, protector, prevDirectKeySig)) + } + + val primaryUserId = inspectKeyRing(secretKeyRing, referenceTime).getPossiblyExpiredPrimaryUserId() + if (primaryUserId != null) { + val prevUserIdSig = getPreviousUserIdSignatures(primaryUserId) + val userIdSig = reissuePrimaryUserIdSig(expiration, protector, primaryUserId, prevUserIdSig!!) + secretKeyRing = injectCertification(secretKeyRing, primaryUserId, userIdSig) + } + + val info = inspectKeyRing(secretKeyRing, referenceTime) + for (userId in info.validUserIds) { + if (userId == primaryUserId) { + continue + } + + val prevUserIdSig = info.getLatestUserIdCertification(userId) ?: throw AssertionError("A valid user-id shall never have no user-id signature.") + if (prevUserIdSig.hashedSubPackets.isPrimaryUserID) { + secretKeyRing = injectCertification(secretKeyRing, primaryUserId!!, + reissueNonPrimaryUserId(protector, userId, prevUserIdSig)) + } + } + + return this + } + + override fun createMinimalRevocationCertificate(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPPublicKeyRing { + // Check reason + if (revocationAttributes != null) { + require(RevocationAttributes.Reason.isKeyRevocation(revocationAttributes.reason)) { + "Revocation reason MUST be applicable to a key revocation." + } + } + + val revocation = createRevocation(protector, revocationAttributes) + var primaryKey = secretKeyRing.secretKey.publicKey + primaryKey = KeyRingUtils.getStrippedDownPublicKey(primaryKey) + primaryKey = PGPPublicKey.addCertification(primaryKey, revocation) + return PGPPublicKeyRing(listOf(primaryKey)) + } + + override fun createRevocation(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { + return generateRevocation(protector, secretKeyRing.publicKey, callbackFromRevocationAttributes(revocationAttributes)) + } + + override fun createRevocation(subkeyId: Long, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { + return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyId), callbackFromRevocationAttributes(revocationAttributes)) + } + + override fun createRevocation(subkeyId: Long, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): PGPSignature { + return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyId), callback) + } + + override fun createRevocation(subkeyFingerprint: OpenPgpFingerprint, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { + return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyFingerprint), callbackFromRevocationAttributes(revocationAttributes)) + } + + override fun changePassphraseFromOldPassphrase(oldPassphrase: Passphrase, oldProtectionSettings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { + return WithKeyRingEncryptionSettingsImpl(this, null, + PasswordBasedSecretKeyRingProtector(oldProtectionSettings, SolitaryPassphraseProvider(oldPassphrase))) + } + + override fun changeSubKeyPassphraseFromOldPassphrase(keyId: Long, oldPassphrase: Passphrase, oldProtectionSettings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { + return WithKeyRingEncryptionSettingsImpl(this, keyId, + CachingSecretKeyRingProtector(mapOf(keyId to oldPassphrase), oldProtectionSettings, null)) + } + + override fun done(): PGPSecretKeyRing { + return secretKeyRing + } + + private fun sanitizeUserId(userId: CharSequence): CharSequence = + // TODO: Further research how to sanitize user IDs. + // eg. what about newlines? + userId.toString().trim() + + private fun callbackFromRevocationAttributes(attributes: RevocationAttributes?) = + object : RevocationSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { + if (attributes != null) { + hashedSubpackets.setRevocationReason(attributes) + } + } + } + + private fun generateRevocation(protector: SecretKeyRingProtector, + revokeeSubkey: PGPPublicKey, + callback: RevocationSignatureSubpackets.Callback?): PGPSignature { + val primaryKey = secretKeyRing.secretKey + val signatureType = + if (revokeeSubkey.isMasterKey) SignatureType.KEY_REVOCATION + else SignatureType.SUBKEY_REVOCATION + + return RevocationSignatureBuilder(signatureType, primaryKey, protector) + .apply { applyCallback(callback) } + .build(revokeeSubkey) + } + + private fun doRevokeUserId(userId: CharSequence, + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { + RevocationSignatureBuilder(SignatureType.CERTIFICATION_REVOCATION, secretKeyRing.secretKey, protector).apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + applyCallback(callback) + }.let { + secretKeyRing = injectCertification(secretKeyRing, userId, it.build(userId.toString())) + } + return this + } + + + private fun getPreviousDirectKeySignature(): PGPSignature? { + val info = inspectKeyRing(secretKeyRing, referenceTime) + return info.latestDirectKeySelfSignature + } + + private fun getPreviousUserIdSignatures(userId: String): PGPSignature? { + val info = inspectKeyRing(secretKeyRing, referenceTime) + return info.getLatestUserIdCertification(userId) + } + + @Throws(PGPException::class) + private fun reissueNonPrimaryUserId( + secretKeyRingProtector: SecretKeyRingProtector, + userId: String, + prevUserIdSig: PGPSignature): PGPSignature { + val builder = SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig) + builder.hashedSubpackets.setSignatureCreationTime(referenceTime) + builder.applyCallback(object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + // unmark as primary + hashedSubpackets.setPrimaryUserId(null) + } + }) + return builder.build(secretKeyRing.publicKey, userId) + } + + @Throws(PGPException::class) + private fun reissuePrimaryUserIdSig( + expiration: Date?, + @Nonnull secretKeyRingProtector: SecretKeyRingProtector, + @Nonnull primaryUserId: String, + @Nonnull prevUserIdSig: PGPSignature): PGPSignature { + return SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig) + .apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + applyCallback(object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + if (expiration != null) { + hashedSubpackets.setKeyExpirationTime(true, secretKeyRing.publicKey.creationTime, expiration) + } else { + hashedSubpackets.setKeyExpirationTime(KeyExpirationTime(true, 0)) + } + hashedSubpackets.setPrimaryUserId() + } + }) + }.build(secretKeyRing.publicKey, primaryUserId) + } + + @Throws(PGPException::class) + private fun reissueDirectKeySignature( + expiration: Date?, + secretKeyRingProtector: SecretKeyRingProtector, + prevDirectKeySig: PGPSignature): PGPSignature { + return DirectKeySelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevDirectKeySig) + .apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + applyCallback(object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + if (expiration != null) { + hashedSubpackets.setKeyExpirationTime(secretKeyRing.publicKey.creationTime, expiration) + } else { + hashedSubpackets.setKeyExpirationTime(null) + } + } + }) + }.build(secretKeyRing.publicKey) + } + + private class WithKeyRingEncryptionSettingsImpl( + private val editor: SecretKeyRingEditor, + private val keyId: Long?, + private val oldProtector: SecretKeyRingProtector) : SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { + + override fun withSecureDefaultSettings(): SecretKeyRingEditorInterface.WithPassphrase { + return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings()) + } + + override fun withCustomSettings(settings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithPassphrase { + return WithPassphraseImpl(editor, keyId, oldProtector, settings) + } + } + + private class WithPassphraseImpl( + private val editor: SecretKeyRingEditor, + private val keyId: Long?, + private val oldProtector: SecretKeyRingProtector, + private val newProtectionSettings: KeyRingProtectionSettings + ) : SecretKeyRingEditorInterface.WithPassphrase { + + override fun toNewPassphrase(passphrase: Passphrase): SecretKeyRingEditorInterface { + val protector = PasswordBasedSecretKeyRingProtector(newProtectionSettings, SolitaryPassphraseProvider(passphrase)) + val secretKeys = changePassphrase(keyId, editor.secretKeyRing, oldProtector, protector) + editor.secretKeyRing = secretKeys + return editor + } + + override fun toNoPassphrase(): SecretKeyRingEditorInterface { + val protector = UnprotectedKeysProtector() + val secretKeys = changePassphrase(keyId, editor.secretKeyRing, oldProtector, protector) + editor.secretKeyRing = secretKeys + return editor + } + } + +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt new file mode 100644 index 00000000..45a09fb2 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt @@ -0,0 +1,560 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.modification.secretkeyring + +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPKeyPair +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.PGPSignature +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 +import java.io.IOException +import java.security.InvalidAlgorithmParameterException +import java.security.NoSuchAlgorithmException +import java.util.* + +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 + */ + @Throws(PGPException::class) + fun removeUserId(selector: SelectUserId, protector: SecretKeyRingProtector): 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) + fun revokeUserIds(selector: SelectUserId, + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes?): 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) + fun revokeUserIds(selector: SelectUserId, + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback?): 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 + + /** + * 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 +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java index d62d5bad..e5e452f2 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java @@ -149,7 +149,7 @@ public class KeyRingInfoTest { private static PGPSecretKeyRing encryptSecretKeys(PGPSecretKeyRing secretKeys) throws PGPException { return PGPainless.modifyKeyRing(secretKeys) - .changePassphraseFromOldPassphrase(null) + .changePassphraseFromOldPassphrase(Passphrase.emptyPassphrase()) .withSecureDefaultSettings() .toNewPassphrase(Passphrase.fromPassword("sw0rdf1sh")) .done(); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddUserIdTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddUserIdTest.java index 19f84930..7e15c998 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddUserIdTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddUserIdTest.java @@ -12,9 +12,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; +import java.util.Date; import java.util.Iterator; import java.util.NoSuchElementException; +import openpgp.DateExtensionsKt; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.junit.jupiter.api.Test; @@ -113,16 +115,17 @@ public class AddUserIdTest { @Test public void addNewPrimaryUserIdTest() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + Date now = new Date(); PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() .modernKeyRing("Alice"); UserId bob = UserId.newBuilder().withName("Bob").noEmail().noComment().build(); assertNotEquals("Bob", PGPainless.inspectKeyRing(secretKeys).getPrimaryUserId()); - secretKeys = PGPainless.modifyKeyRing(secretKeys) + secretKeys = PGPainless.modifyKeyRing(secretKeys, DateExtensionsKt.plusSeconds(now, 1)) .addPrimaryUserId(bob, SecretKeyRingProtector.unprotectedKeys()) .done(); - assertEquals("Bob", PGPainless.inspectKeyRing(secretKeys).getPrimaryUserId()); + assertEquals("Bob", PGPainless.inspectKeyRing(secretKeys, DateExtensionsKt.plusSeconds(now, 2)).getPrimaryUserId()); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/fixes/S2KUsageFixTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/fixes/S2KUsageFixTest.java index f324892f..ba6673e5 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/fixes/S2KUsageFixTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/fixes/S2KUsageFixTest.java @@ -84,7 +84,7 @@ public class S2KUsageFixTest { } PGPSecretKeyRing after = PGPainless.modifyKeyRing(unprotected) - .changePassphraseFromOldPassphrase(null) + .changePassphraseFromOldPassphrase(Passphrase.emptyPassphrase()) .withSecureDefaultSettings() .toNewPassphrase(Passphrase.fromPassword("after")) .done(); From 68ac5af255961c43c2e0fae903e2bd339a154525 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 14 Sep 2023 15:50:29 +0200 Subject: [PATCH 118/155] Kotlin conversion: UserId --- .../java/org/pgpainless/key/util/UserId.java | 356 ------------------ .../kotlin/org/pgpainless/key/util/UserId.kt | 206 ++++++++++ .../java/org/pgpainless/key/UserIdTest.java | 7 +- 3 files changed, 209 insertions(+), 360 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/util/UserId.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java deleted file mode 100644 index b115afda..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java +++ /dev/null @@ -1,356 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub , 2021 Flowcrypt a.s. -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.util; - -import java.util.Comparator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class UserId implements CharSequence { - - // Email regex: https://emailregex.com/ - // switched "a-z0-9" to "\p{L}\u0900-\u097F0-9" for better support for international characters - // \\p{L} = Unicode Letters - // \u0900-\u097F = Hindi Letters - private static final Pattern emailPattern = Pattern.compile("(?:[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+(?:\\.[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-" + - "\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9" + - "-]*[\\p{L}\\u0900-\\u097F0-9])?\\.)+[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + - "\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[$\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f" + - "\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])"); - - // User-ID Regex - // "Firstname Lastname (Comment) " - // All groups are optional - // https://www.rfc-editor.org/rfc/rfc5322#page-16 - private static final Pattern nameAddrPattern = Pattern.compile("^((?.+?)\\s)?(\\((?.+?)\\)\\s)?(<(?.+?)>)?$"); - - public static final class Builder { - private String name; - private String comment; - private String email; - - private Builder() { - } - - private Builder(String name, String comment, String email) { - this.name = name; - this.comment = comment; - this.email = email; - } - - public Builder withName(@Nonnull String name) { - this.name = name; - return this; - } - - public Builder withComment(@Nonnull String comment) { - this.comment = comment; - return this; - } - - public Builder withEmail(@Nonnull String email) { - this.email = email; - return this; - } - - public Builder noName() { - name = null; - return this; - } - - public Builder noComment() { - comment = null; - return this; - } - - public Builder noEmail() { - email = null; - return this; - } - - public UserId build() { - return new UserId(name, comment, email); - } - } - - /** - * Parse a {@link UserId} from free-form text,

name-addr
or
mailbox
string and split it - * up into its components. - * Example inputs for this method: - *
    - *
  • john@pgpainless.org
  • - *
  • <john@pgpainless.org>
  • - *
  • John Doe
  • - *
  • John Doe <john@pgpainless.org>
  • - *
  • John Doe (work email) <john@pgpainless.org>
  • - *
- * In these cases, this method will detect email addresses, names and comments and expose those - * via the respective getters. - * This method does not support parsing mail addresses of the following formats: - *
    - *
  • Local domains without TLDs (
    user@localdomain1
    )
  • - *
  • " "@example.org
    (spaces between the quotes)
  • - *
  • "very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com
  • - *
- * Note: This method does not guarantee that
string.equals(UserId.parse(string).toString())
is true. - * For example,
UserId.parse("alice@pgpainless.org").toString()
wraps the mail address in angled brackets. - * - * @see RFC5322 §3.4. Address Specification - * @param string user-id - * @return parsed {@link UserId} object - */ - public static UserId parse(@Nonnull String string) { - Builder builder = newBuilder(); - string = string.trim(); - Matcher matcher = nameAddrPattern.matcher(string); - if (matcher.find()) { - String name = matcher.group(2); - String comment = matcher.group(4); - String mail = matcher.group(6); - matcher = emailPattern.matcher(mail); - if (!matcher.matches()) { - throw new IllegalArgumentException("Malformed email address"); - } - - if (name != null) { - builder.withName(name); - } - if (comment != null) { - builder.withComment(comment); - } - builder.withEmail(mail); - } else { - matcher = emailPattern.matcher(string); - if (matcher.matches()) { - builder.withEmail(string); - } else { - throw new IllegalArgumentException("Malformed email address"); - } - } - return builder.build(); - } - - private final String name; - private final String comment; - private final String email; - private long hash = Long.MAX_VALUE; - - private UserId(@Nullable String name, @Nullable String comment, @Nullable String email) { - this.name = name == null ? null : name.trim(); - this.comment = comment == null ? null : comment.trim(); - this.email = email == null ? null : email.trim(); - } - - public static UserId onlyEmail(@Nonnull String email) { - return new UserId(null, null, email); - } - - public static UserId nameAndEmail(@Nonnull String name, @Nonnull String email) { - return new UserId(name, null, email); - } - - public static Builder newBuilder() { - return new Builder(); - } - - public Builder toBuilder() { - return new Builder(name, comment, email); - } - - public String getName() { - return getName(false); - } - - public String getName(boolean preserveQuotes) { - if (name == null || name.isEmpty()) { - return name; - } - - if (name.startsWith("\"")) { - if (preserveQuotes) { - return name; - } - String withoutQuotes = name.substring(1); - if (withoutQuotes.endsWith("\"")) { - withoutQuotes = withoutQuotes.substring(0, withoutQuotes.length() - 1); - } - return withoutQuotes; - } - return name; - } - - public String getComment() { - return comment; - } - - public String getEmail() { - return email; - } - - @Override - public int length() { - return toString().length(); - } - - @Override - public char charAt(int i) { - return toString().charAt(i); - } - - @Override - public @Nonnull CharSequence subSequence(int i, int i1) { - return toString().subSequence(i, i1); - } - - @Override - public @Nonnull String toString() { - StringBuilder sb = new StringBuilder(); - if (name != null && !name.isEmpty()) { - sb.append(getName(true)); - } - if (comment != null && !comment.isEmpty()) { - if (sb.length() > 0) { - sb.append(' '); - } - sb.append('(').append(comment).append(')'); - } - if (email != null && !email.isEmpty()) { - if (sb.length() > 0) { - sb.append(' '); - } - sb.append('<').append(email).append('>'); - } - return sb.toString(); - } - - /** - * Returns a string representation of the object. - * @return a string representation of the object. - * @deprecated use {@link #toString()} instead. - */ - @Deprecated - public String asString() { - return toString(); - } - - @Override - public boolean equals(Object o) { - if (o == null) return false; - if (o == this) return true; - if (!(o instanceof UserId)) return false; - final UserId other = (UserId) o; - return isEqualComponent(name, other.name, false) - && isEqualComponent(comment, other.comment, false) - && isEqualComponent(email, other.email, true); - } - - @Override - public int hashCode() { - if (hash != Long.MAX_VALUE) { - return (int) hash; - } else { - int hashCode = 7; - hashCode = 31 * hashCode + (name == null ? 0 : name.hashCode()); - hashCode = 31 * hashCode + (comment == null ? 0 : comment.hashCode()); - hashCode = 31 * hashCode + (email == null ? 0 : email.toLowerCase().hashCode()); - this.hash = hashCode; - return hashCode; - } - } - - private static boolean isEqualComponent(String value, String otherValue, boolean ignoreCase) { - final boolean valueIsNull = (value == null); - final boolean otherValueIsNull = (otherValue == null); - return (valueIsNull && otherValueIsNull) - || (!valueIsNull && !otherValueIsNull - && (ignoreCase ? value.equalsIgnoreCase(otherValue) : value.equals(otherValue))); - } - - public static int compare(@Nullable UserId o1, @Nullable UserId o2, @Nonnull Comparator comparator) { - return comparator.compare(o1, o2); - } - - public static class DefaultComparator implements Comparator { - - @Override - public int compare(UserId o1, UserId o2) { - if (o1 == o2) { - return 0; - } - if (o1 == null) { - return -1; - } - if (o2 == null) { - return 1; - } - - NullSafeStringComparator c = new NullSafeStringComparator(); - int cName = c.compare(o1.getName(), o2.getName()); - if (cName != 0) { - return cName; - } - - int cComment = c.compare(o1.getComment(), o2.getComment()); - if (cComment != 0) { - return cComment; - } - - return c.compare(o1.getEmail(), o2.getEmail()); - } - } - - public static class DefaultIgnoreCaseComparator implements Comparator { - - @Override - public int compare(UserId o1, UserId o2) { - if (o1 == o2) { - return 0; - } - if (o1 == null) { - return -1; - } - if (o2 == null) { - return 1; - } - - NullSafeStringComparator c = new NullSafeStringComparator(); - int cName = c.compare(lower(o1.getName()), lower(o2.getName())); - if (cName != 0) { - return cName; - } - - int cComment = c.compare(lower(o1.getComment()), lower(o2.getComment())); - if (cComment != 0) { - return cComment; - } - - return c.compare(lower(o1.getEmail()), lower(o2.getEmail())); - } - - private static String lower(String string) { - return string == null ? null : string.toLowerCase(); - } - } - - private static class NullSafeStringComparator implements Comparator { - - @Override - public int compare(String o1, String o2) { - // noinspection StringEquality - if (o1 == o2) { - return 0; - } - if (o1 == null) { - return -1; - } - if (o2 == null) { - return 1; - } - return o1.compareTo(o2); - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/UserId.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/UserId.kt new file mode 100644 index 00000000..b461f6b4 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/UserId.kt @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.util + +class UserId internal constructor( + name: String?, + comment: String?, + email: String? +) : CharSequence { + + private val _name: String? + val comment: String? + val email: String? + + init { + this._name = name?.trim() + this.comment = comment?.trim() + this.email = email?.trim() + } + + val full: String = buildString { + if (name?.isNotBlank() == true) { + append(getName(true)) + } + if (comment?.isNotBlank() == true) { + if (isNotEmpty()) { + append(' ') + } + append("($comment)") + } + if (email?.isNotBlank() == true) { + if (isNotEmpty()) { + append(' ') + } + append("<$email>") + } + } + + override val length: Int + get() = full.length + + val name: String? + get() = getName(false) + + fun getName(preserveQuotes: Boolean): String? { + return if (preserveQuotes || _name.isNullOrBlank()) { + _name + } else _name.removeSurrounding("\"") + } + + override fun equals(other: Any?): Boolean { + if (other === null) { + return false + } + if (this === other) { + return true + } + if (other !is UserId) { + return false + } + return isComponentEqual(_name, other._name, false) + && isComponentEqual(comment, other.comment, false) + && isComponentEqual(email, other.email, true) + } + + override fun get(index: Int): Char { + return full[index] + } + + override fun hashCode(): Int { + return toString().hashCode() + } + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { + return full.subSequence(startIndex, endIndex) + } + + override fun toString(): String { + return full + } + + private fun isComponentEqual(value: String?, otherValue: String?, ignoreCase: Boolean): Boolean = value.equals(otherValue, ignoreCase) + + fun toBuilder() = builder().also { builder -> + if (this._name != null) builder.withName(_name) + if (this.comment != null) builder.withComment(comment) + if (this.email != null) builder.withEmail(email) + } + + companion object { + + // Email regex: https://emailregex.com/ + // switched "a-z0-9" to "\p{L}\u0900-\u097F0-9" for better support for international characters + // \\p{L} = Unicode Letters + // \u0900-\u097F = Hindi Letters + @JvmStatic + private val emailPattern = ("(?:[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+(?:\\.[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-" + + "\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9" + + "-]*[\\p{L}\\u0900-\\u097F0-9])?\\.)+[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[$\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f" + + "\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])").toPattern() + + // User-ID Regex + // "Firstname Lastname (Comment) " + // All groups are optional + // https://www.rfc-editor.org/rfc/rfc5322#page-16 + @JvmStatic + private val nameAddrPattern = "^((?.+?)\\s)?(\\((?.+?)\\)\\s)?(<(?.+?)>)?$".toPattern() + + /** + * Parse a [UserId] from free-form text,
name-addr
or
mailbox
string and split it + * up into its components. + * Example inputs for this method: + *
    + *
  • john@pgpainless.org
  • + *
  • <john@pgpainless.org>
  • + *
  • John Doe
  • + *
  • John Doe <john@pgpainless.org>
  • + *
  • John Doe (work email) <john@pgpainless.org>
  • + *
+ * In these cases, this method will detect email addresses, names and comments and expose those + * via the respective getters. + * This method does not support parsing mail addresses of the following formats: + *
    + *
  • Local domains without TLDs (
    user@localdomain1
    )
  • + *
  • " "@example.org
    (spaces between the quotes)
  • + *
  • "very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com
  • + *
+ * Note: This method does not guarantee that
string.equals(UserId.parse(string).toString())
is true. + * For example,
UserId.parse("alice@pgpainless.org").toString()
wraps the mail address in angled brackets. + * + * @see RFC5322 §3.4. Address Specification + * @param string user-id + * @return parsed UserId object + */ + @JvmStatic + fun parse(string: String): UserId { + val trimmed = string.trim() + nameAddrPattern.matcher(trimmed).let { nameAddrMatcher -> + if (nameAddrMatcher.find()) { + val name = nameAddrMatcher.group(2) + val comment = nameAddrMatcher.group(4) + val mail = nameAddrMatcher.group(6) + require(emailPattern.matcher(mail).matches()) { "Malformed email address" } + return UserId(name, comment, mail) + } else { + require(emailPattern.matcher(trimmed).matches()) { "Malformed email address" } + return UserId(null, null, trimmed) + } + } + } + + @JvmStatic + fun onlyEmail(email: String) = UserId(null, null, email) + + @JvmStatic + fun nameAndEmail(name: String, email: String) = UserId(name, null, email) + + @JvmStatic + fun compare(u1: UserId?, u2: UserId?, comparator: Comparator) = comparator.compare(u1, u2) + + @JvmStatic + @Deprecated("Deprecated in favor of builde() method.", ReplaceWith("builder()")) + fun newBuilder() = builder() + + @JvmStatic + fun builder() = Builder() + } + + class Builder internal constructor() { + var name: String? = null + var comment: String? = null + var email: String? = null + + fun withName(name: String) = apply { this.name = name } + fun withComment(comment: String) = apply { this.comment = comment} + fun withEmail(email: String) = apply { this.email = email } + + fun noName() = apply { this.name = null } + fun noComment() = apply { this.comment = null } + fun noEmail() = apply { this.email = null } + + fun build() = UserId(name, comment, email) + } + + class DefaultComparator : Comparator { + override fun compare(o1: UserId?, o2: UserId?): Int { + return compareBy { it?._name } + .thenBy { it?.comment } + .thenBy { it?.email } + .compare(o1, o2) + } + } + + class DefaultIgnoreCaseComparator : Comparator { + override fun compare(p0: UserId?, p1: UserId?): Int { + return compareBy { it?._name?.lowercase() } + .thenBy { it?.comment?.lowercase() } + .thenBy { it?.email?.lowercase() } + .compare(p0, p1) + } + + } +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/UserIdTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/UserIdTest.java index 93cb6922..1290ad9c 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/UserIdTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/UserIdTest.java @@ -197,15 +197,14 @@ public class UserIdTest { } @Test - public void asStringTest() { - UserId id = UserId.newBuilder() + public void toStringTest() { + UserId id = UserId.builder() .withName("Alice") .withComment("Work Email") .withEmail("alice@pgpainless.org") .build(); - // noinspection deprecation - assertEquals(id.toString(), id.asString()); + assertEquals(id.toString(), id.toString()); } @Test From a6198aadb3664b3379ecac34600adb146489f4e3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 14 Sep 2023 16:31:52 +0200 Subject: [PATCH 119/155] 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 From 9ee29f7a53c38ad0bff94ab9c5cc09208012247e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 16 Sep 2023 13:09:10 +0200 Subject: [PATCH 120/155] Kotlin conversion: IntegrityProtectedInputStream --- .../IntegrityProtectedInputStream.java | 60 ------------------- .../IntegrityProtectedInputStream.kt | 42 +++++++++++++ 2 files changed, 42 insertions(+), 60 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.java deleted file mode 100644 index 37dcfca4..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.java +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.IOException; -import java.io.InputStream; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPEncryptedData; -import org.bouncycastle.openpgp.PGPException; -import org.pgpainless.exception.ModificationDetectionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class IntegrityProtectedInputStream extends InputStream { - - private static final Logger LOGGER = LoggerFactory.getLogger(IntegrityProtectedInputStream.class); - - private final InputStream inputStream; - private final PGPEncryptedData encryptedData; - private final ConsumerOptions options; - private boolean closed = false; - - public IntegrityProtectedInputStream(InputStream inputStream, PGPEncryptedData encryptedData, ConsumerOptions options) { - this.inputStream = inputStream; - this.encryptedData = encryptedData; - this.options = options; - } - - @Override - public int read() throws IOException { - return inputStream.read(); - } - - @Override - public int read(@Nonnull byte[] b, int offset, int length) throws IOException { - return inputStream.read(b, offset, length); - } - - @Override - public void close() throws IOException { - if (closed) { - return; - } - closed = true; - - if (encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) { - try { - if (!encryptedData.verify()) { - throw new ModificationDetectionException(); - } - LOGGER.debug("Integrity Protection check passed"); - } catch (PGPException e) { - throw new IOException("Data appears to not be integrity protected.", e); - } - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.kt new file mode 100644 index 00000000..a1e095f8 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.kt @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.openpgp.PGPEncryptedData +import org.bouncycastle.openpgp.PGPException +import org.pgpainless.exception.ModificationDetectionException +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.IOException +import java.io.InputStream + +class IntegrityProtectedInputStream( + private val inputStream: InputStream, + private val encryptedData: PGPEncryptedData, + private val options: ConsumerOptions +) : InputStream() { + private var closed: Boolean = false + + override fun read() = inputStream.read() + override fun read(b: ByteArray, off: Int, len: Int) = inputStream.read(b, off, len) + override fun close() { + if (closed) return + + closed = true + if (encryptedData.isIntegrityProtected && !options.isIgnoreMDCErrors()) { + try { + if (!encryptedData.verify()) throw ModificationDetectionException() + LOGGER.debug("Integrity Protection check passed.") + } catch (e : PGPException) { + throw IOException("Data appears to not be integrity protected.", e) + } + } + } + + companion object { + @JvmStatic + val LOGGER: Logger = LoggerFactory.getLogger(IntegrityProtectedInputStream::class.java) + } +} \ No newline at end of file From ea57c4aec0fe186d69c802dbb1e54c0f410571c1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 16 Sep 2023 14:22:25 +0200 Subject: [PATCH 121/155] Kotlin conversion: EncryptionStream --- .../encryption_signing/EncryptionStream.java | 313 ------------------ .../encryption_signing/EncryptionStream.kt | 251 ++++++++++++++ 2 files changed, 251 insertions(+), 313 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java deleted file mode 100644 index 7af5a7b3..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java +++ /dev/null @@ -1,313 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import javax.annotation.Nonnull; - -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.bcpg.BCPGOutputStream; -import org.bouncycastle.openpgp.PGPCompressedDataGenerator; -import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralDataGenerator; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; -import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.StreamEncoding; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.util.ArmoredOutputStreamFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * OutputStream that produces an OpenPGP message. The message can be encrypted, signed, or both, - * depending on its configuration. - * - * This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream. - * @see Source - */ -public final class EncryptionStream extends OutputStream { - - private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionStream.class); - - private final ProducerOptions options; - private final EncryptionResult.Builder resultBuilder = EncryptionResult.builder(); - - private boolean closed = false; - // 1 << 8 causes wrong partial body length encoding - // 1 << 9 fixes this. - // see https://github.com/pgpainless/pgpainless/issues/160 - private static final int BUFFER_SIZE = 1 << 9; - - OutputStream outermostStream; - OutputStream signatureLayerStream; - - private ArmoredOutputStream armorOutputStream = null; - private OutputStream publicKeyEncryptedStream = null; - private PGPCompressedDataGenerator compressedDataGenerator; - private BCPGOutputStream basicCompressionStream; - private PGPLiteralDataGenerator literalDataGenerator; - private OutputStream literalDataStream; - - EncryptionStream(@Nonnull OutputStream targetOutputStream, - @Nonnull ProducerOptions options) - throws IOException, PGPException { - this.options = options; - outermostStream = targetOutputStream; - - prepareArmor(); - prepareEncryption(); - prepareCompression(); - prepareOnePassSignatures(); - prepareLiteralDataProcessing(); - prepareSigningStream(); - prepareInputEncoding(); - } - - private void prepareArmor() { - if (!options.isAsciiArmor()) { - LOGGER.debug("Output will be unarmored"); - return; - } - - // ArmoredOutputStream better be buffered - outermostStream = new BufferedOutputStream(outermostStream); - - LOGGER.debug("Wrap encryption output in ASCII armor"); - armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options); - outermostStream = armorOutputStream; - } - - private void prepareEncryption() throws IOException, PGPException { - EncryptionOptions encryptionOptions = options.getEncryptionOptions(); - if (encryptionOptions == null || encryptionOptions.getEncryptionMethods().isEmpty()) { - // No encryption options/methods -> no encryption - resultBuilder.setEncryptionAlgorithm(SymmetricKeyAlgorithm.NULL); - return; - } - - SymmetricKeyAlgorithm encryptionAlgorithm = EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(encryptionOptions); - resultBuilder.setEncryptionAlgorithm(encryptionAlgorithm); - LOGGER.debug("Encrypt message using {}", encryptionAlgorithm); - PGPDataEncryptorBuilder dataEncryptorBuilder = - ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(encryptionAlgorithm); - dataEncryptorBuilder.setWithIntegrityPacket(true); - - PGPEncryptedDataGenerator encryptedDataGenerator = - new PGPEncryptedDataGenerator(dataEncryptorBuilder); - for (PGPKeyEncryptionMethodGenerator encryptionMethod : encryptionOptions.getEncryptionMethods()) { - encryptedDataGenerator.addMethod(encryptionMethod); - } - - for (SubkeyIdentifier recipientSubkeyIdentifier : encryptionOptions.getEncryptionKeyIdentifiers()) { - resultBuilder.addRecipient(recipientSubkeyIdentifier); - } - - publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, new byte[BUFFER_SIZE]); - outermostStream = publicKeyEncryptedStream; - } - - private void prepareCompression() throws IOException { - CompressionAlgorithm compressionAlgorithm = EncryptionBuilder.negotiateCompressionAlgorithm(options); - resultBuilder.setCompressionAlgorithm(compressionAlgorithm); - compressedDataGenerator = new PGPCompressedDataGenerator( - compressionAlgorithm.getAlgorithmId()); - if (compressionAlgorithm == CompressionAlgorithm.UNCOMPRESSED) { - return; - } - - LOGGER.debug("Compress using {}", compressionAlgorithm); - basicCompressionStream = new BCPGOutputStream(compressedDataGenerator.open(outermostStream)); - outermostStream = basicCompressionStream; - } - - private void prepareOnePassSignatures() throws IOException, PGPException { - signatureLayerStream = outermostStream; - SigningOptions signingOptions = options.getSigningOptions(); - if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) { - // No singing options/methods -> no signing - return; - } - - int sigIndex = 0; - for (SubkeyIdentifier identifier : signingOptions.getSigningMethods().keySet()) { - sigIndex++; - SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(identifier); - - if (!signingMethod.isDetached()) { - PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator(); - // The last sig is not nested, all others are - boolean nested = sigIndex != signingOptions.getSigningMethods().size(); - signatureGenerator.generateOnePassVersion(nested).encode(outermostStream); - } - } - } - - private void prepareLiteralDataProcessing() throws IOException { - if (options.isCleartextSigned()) { - int[] algorithmIds = collectHashAlgorithmsForCleartextSigning(); - armorOutputStream.beginClearText(algorithmIds); - return; - } - - literalDataGenerator = new PGPLiteralDataGenerator(); - literalDataStream = literalDataGenerator.open(outermostStream, options.getEncoding().getCode(), - options.getFileName(), options.getModificationDate(), new byte[BUFFER_SIZE]); - outermostStream = literalDataStream; - - resultBuilder.setFileName(options.getFileName()) - .setModificationDate(options.getModificationDate()) - .setFileEncoding(options.getEncoding()); - } - - public void prepareSigningStream() { - outermostStream = new SignatureGenerationStream(outermostStream, options.getSigningOptions()); - } - - public void prepareInputEncoding() { - // By buffering here, we drastically improve performance - // Reason is that CRLFGeneratorStream only implements write(int), so we need BufferedOutputStream to - // "convert" to write(buf) calls again - BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outermostStream); - CRLFGeneratorStream crlfGeneratorStream = new CRLFGeneratorStream(bufferedOutputStream, - options.isApplyCRLFEncoding() ? StreamEncoding.UTF8 : StreamEncoding.BINARY); - outermostStream = crlfGeneratorStream; - } - - private int[] collectHashAlgorithmsForCleartextSigning() { - SigningOptions signOpts = options.getSigningOptions(); - Set hashAlgorithms = new HashSet<>(); - if (signOpts != null) { - for (SigningOptions.SigningMethod method : signOpts.getSigningMethods().values()) { - hashAlgorithms.add(method.getHashAlgorithm()); - } - } - - int[] algorithmIds = new int[hashAlgorithms.size()]; - Iterator iterator = hashAlgorithms.iterator(); - for (int i = 0; i < algorithmIds.length; i++) { - algorithmIds[i] = iterator.next().getAlgorithmId(); - } - - return algorithmIds; - } - - @Override - public void write(int data) throws IOException { - outermostStream.write(data); - } - - @Override - public void write(@Nonnull byte[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - - @Override - public void write(@Nonnull byte[] buffer, int off, int len) throws IOException { - outermostStream.write(buffer, 0, len); - } - - @Override - public void flush() throws IOException { - outermostStream.flush(); - } - - @Override - public void close() throws IOException { - if (closed) { - return; - } - - outermostStream.close(); - - // Literal Data - if (literalDataStream != null) { - literalDataStream.flush(); - literalDataStream.close(); - } - if (literalDataGenerator != null) { - literalDataGenerator.close(); - } - - if (options.isCleartextSigned()) { - // Add linebreak between body and signatures - // TODO: We should only add this line if required. - // I.e. if the message already ends with \n, don't add another linebreak. - armorOutputStream.write('\r'); - armorOutputStream.write('\n'); - armorOutputStream.endClearText(); - } - - try { - writeSignatures(); - } catch (PGPException e) { - throw new IOException("Exception while writing signatures.", e); - } - - // Compressed Data - compressedDataGenerator.close(); - - // Public Key Encryption - if (publicKeyEncryptedStream != null) { - publicKeyEncryptedStream.flush(); - publicKeyEncryptedStream.close(); - } - - // Armor - if (armorOutputStream != null) { - armorOutputStream.flush(); - armorOutputStream.close(); - } - closed = true; - } - - private void writeSignatures() throws PGPException, IOException { - SigningOptions signingOptions = options.getSigningOptions(); - if (signingOptions == null || signingOptions.getSigningMethods().isEmpty()) { - return; - } - - // One-Pass-Signatures are bracketed. That means we have to append the signatures in reverse order - // compared to the one-pass-signature packets. - List signingKeys = new ArrayList<>(signingOptions.getSigningMethods().keySet()); - for (int i = signingKeys.size() - 1; i >= 0; i--) { - SubkeyIdentifier signingKey = signingKeys.get(i); - SigningOptions.SigningMethod signingMethod = signingOptions.getSigningMethods().get(signingKey); - PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator(); - PGPSignature signature = signatureGenerator.generate(); - if (signingMethod.isDetached()) { - resultBuilder.addDetachedSignature(signingKey, signature); - } - if (!signingMethod.isDetached() || options.isCleartextSigned()) { - signature.encode(signatureLayerStream); - } - } - } - - public EncryptionResult getResult() { - if (!closed) { - throw new IllegalStateException("EncryptionStream must be closed before accessing the Result."); - } - return resultBuilder.build(); - } - - public boolean isClosed() { - return closed; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt new file mode 100644 index 00000000..50397905 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt @@ -0,0 +1,251 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.bcpg.ArmoredOutputStream +import org.bouncycastle.bcpg.BCPGOutputStream +import org.bouncycastle.openpgp.PGPCompressedDataGenerator +import org.bouncycastle.openpgp.PGPEncryptedDataGenerator +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPLiteralDataGenerator +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.StreamEncoding +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.util.ArmoredOutputStreamFactory +import org.slf4j.LoggerFactory +import java.io.BufferedOutputStream +import java.io.IOException +import java.io.OutputStream + +// 1 << 8 causes wrong partial body length encoding +// 1 << 9 fixes this. +// see https://github.com/pgpainless/pgpainless/issues/160 +const val BUFFER_SIZE = 1 shl 9 + +/** + * OutputStream that produces an OpenPGP message. The message can be encrypted, signed, or both, + * depending on its configuration. + * + * This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream. + * @see Source + */ +class EncryptionStream( + private var outermostStream: OutputStream, + private val options: ProducerOptions, +) : OutputStream() { + + private val resultBuilder: EncryptionResult.Builder = EncryptionResult.builder() + private var closed: Boolean = false + + private var signatureLayerStream: OutputStream? = null + private var armorOutputStream: ArmoredOutputStream? = null + private var publicKeyEncryptedStream: OutputStream? = null + private var compressedDataGenerator: PGPCompressedDataGenerator? = null + private var basicCompressionStream: BCPGOutputStream? = null + private var literalDataGenerator: PGPLiteralDataGenerator? = null + private var literalDataStream: OutputStream? = null + + init { + prepareArmor() + prepareEncryption() + prepareCompression() + prepareOnePassSignatures() + prepareLiteralDataProcessing() + prepareSigningStream() + prepareInputEncoding() + } + + private fun prepareArmor() { + if (!options.isAsciiArmor) { + LOGGER.debug("Output will be unarmored.") + return + } + + outermostStream = BufferedOutputStream(outermostStream) + LOGGER.debug("Wrap encryption output in ASCII armor.") + armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options) + .also { outermostStream = it } + } + + @Throws(IOException::class, PGPException::class) + private fun prepareEncryption() { + if (options.encryptionOptions == null) { + // No encryption options -> no encryption + resultBuilder.setEncryptionAlgorithm(SymmetricKeyAlgorithm.NULL) + return + } + require(options.encryptionOptions.encryptionMethods.isNotEmpty()) { + "If EncryptionOptions are provided, at least one encryption method MUST be provided as well." + } + + EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let { + resultBuilder.setEncryptionAlgorithm(it) + LOGGER.debug("Encrypt message using symmetric algorithm $it.") + val encryptedDataGenerator = PGPEncryptedDataGenerator( + ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it) + .apply { setWithIntegrityPacket(true) }) + options.encryptionOptions.encryptionMethods.forEach { m -> + encryptedDataGenerator.addMethod(m) + } + options.encryptionOptions.encryptionKeyIdentifiers.forEach { r -> + resultBuilder.addRecipient(r) + } + + publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)) + .also { stream -> outermostStream = stream } + } + } + + @Throws(IOException::class) + private fun prepareCompression() { + EncryptionBuilder.negotiateCompressionAlgorithm(options).let { + resultBuilder.setCompressionAlgorithm(it) + compressedDataGenerator = PGPCompressedDataGenerator(it.algorithmId) + if (it == CompressionAlgorithm.UNCOMPRESSED) return + + LOGGER.debug("Compress using $it.") + basicCompressionStream = BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)) + .also { stream -> outermostStream = stream } + } + } + + @Throws(IOException::class, PGPException::class) + private fun prepareOnePassSignatures() { + signatureLayerStream = outermostStream + if (options.signingOptions == null) { + return + } + require(options.signingOptions.signingMethods.isNotEmpty()) { + "If SigningOptions are provided, at least one SigningMethod MUST be provided." + } + for ((index, method) in options.signingOptions.signingMethods.values.withIndex()) { + if (!method.isDetached) { + // The last sig is not nested, all others are + val nested = index + 1 < options.signingOptions.signingMethods.size + method.signatureGenerator.generateOnePassVersion(nested).encode(outermostStream) + } + } + } + + @Throws(IOException::class) + private fun prepareLiteralDataProcessing() { + if (options.isCleartextSigned) { + val hashAlgorithms = collectHashAlgorithmsForCleartextSigning() + armorOutputStream!!.beginClearText(*hashAlgorithms.toIntArray()) + return + } + + literalDataGenerator = PGPLiteralDataGenerator().also { gen -> + literalDataStream = gen.open(outermostStream, options.encoding.code, options.fileName, + options.modificationDate, ByteArray(BUFFER_SIZE)).also { stream -> + outermostStream = stream + } + } + resultBuilder.apply { + setFileName(options.fileName) + setModificationDate(options.modificationDate) + setFileEncoding(options.encoding) + } + } + + private fun prepareSigningStream() { + outermostStream = SignatureGenerationStream(outermostStream, options.signingOptions) + } + + private fun prepareInputEncoding() { + outermostStream = CRLFGeneratorStream( + // By buffering here, we drastically improve performance + // Reason is that CRLFGeneratorStream only implements write(int), so we need BufferedOutputStream to + // "convert" to write(buf) calls again + BufferedOutputStream(outermostStream), + if (options.isApplyCRLFEncoding) StreamEncoding.UTF8 else StreamEncoding.BINARY) + } + + private fun collectHashAlgorithmsForCleartextSigning(): Array { + return options.signingOptions?.signingMethods?.values + ?.map { it.hashAlgorithm }?.toSet() + ?.map { it.algorithmId }?.toTypedArray() + ?: arrayOf() + } + + @Throws(IOException::class) + override fun write(data: Int) = outermostStream.write(data) + + @Throws(IOException::class) + override fun write(buffer: ByteArray) = write(buffer, 0, buffer.size) + + @Throws(IOException::class) + override fun write(buffer: ByteArray, off: Int, len: Int) = outermostStream.write(buffer, off, len) + + @Throws(IOException::class) + override fun flush() = outermostStream.flush() + + @Throws(IOException::class) + override fun close() { + if (closed) return + + outermostStream.close() + literalDataStream?.apply { flush(); close() } + literalDataGenerator?.close() + + if (options.isCleartextSigned) { + armorOutputStream?.apply { + write('\r'.code) + write('\n'.code) + endClearText() + } + } + + try { + writeSignatures() + } catch (e : PGPException) { + throw IOException("Exception while writing signatures.", e) + } + + compressedDataGenerator?.close() + + publicKeyEncryptedStream?.apply { + flush() + close() + } + + armorOutputStream?.apply { + flush() + close() + } + closed = true + } + + @Throws(PGPException::class, IOException::class) + private fun writeSignatures() { + if (options.signingOptions == null) { + return + } + + options.signingOptions.signingMethods.entries.reversed().forEach { (key, method) -> + method.signatureGenerator.generate().let { sig -> + if (method.isDetached) { + resultBuilder.addDetachedSignature(key, sig) + } + if (!method.isDetached || options.isCleartextSigned) { + sig.encode(signatureLayerStream) + } + } + } + } + + val result: EncryptionResult + get() = check(closed) { "EncryptionStream must be closed before accessing the result." } + .let { resultBuilder.build() } + + val isClosed + get() = closed + + companion object { + @JvmStatic + private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java) + } +} \ No newline at end of file From befb1c8c0fc45e548dc5ebf232f15890255f0545 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 20 Sep 2023 13:18:48 +0200 Subject: [PATCH 122/155] Kotlin conversion: MessageInspector --- .../MessageInspector.java | 146 ------------------ .../MessageInspector.kt | 108 +++++++++++++ 2 files changed, 108 insertions(+), 146 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageInspector.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageInspector.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageInspector.java deleted file mode 100644 index 3dda0f5d..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageInspector.java +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPEncryptedData; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPOnePassSignatureList; -import org.bouncycastle.openpgp.PGPPBEEncryptedData; -import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; -import org.bouncycastle.openpgp.PGPUtil; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.util.ArmorUtils; - -/** - * Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase protected. - */ -public final class MessageInspector { - - public static class EncryptionInfo { - private final List keyIds = new ArrayList<>(); - private boolean isPassphraseEncrypted = false; - private boolean isSignedOnly = false; - - /** - * Return a list of recipient key ids for whom the message is encrypted. - * @return recipient key ids - */ - public List getKeyIds() { - return Collections.unmodifiableList(keyIds); - } - - public boolean isPassphraseEncrypted() { - return isPassphraseEncrypted; - } - - /** - * Return true, if the message is encrypted. - * - * @return true if encrypted - */ - public boolean isEncrypted() { - return isPassphraseEncrypted || !keyIds.isEmpty(); - } - - /** - * Return true, if the message is not encrypted, but signed using {@link org.bouncycastle.openpgp.PGPOnePassSignature OnePassSignatures}. - * - * @return true if message is signed only - */ - public boolean isSignedOnly() { - return isSignedOnly; - } - } - - private MessageInspector() { - - } - - /** - * Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it. - * - * @param message OpenPGP message - * @return encryption info - * - * @throws PGPException in case the message is broken - * @throws IOException in case of an IO error - */ - public static EncryptionInfo determineEncryptionInfoForMessage(String message) throws PGPException, IOException { - @SuppressWarnings("CharsetObjectCanBeUsed") - Charset charset = Charset.forName("UTF-8"); - return determineEncryptionInfoForMessage(new ByteArrayInputStream(message.getBytes(charset))); - } - - /** - * Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it. - * Note: This method does not rewind the passed in Stream, so you might need to take care of that yourselves. - * - * @param dataIn openpgp message - * @return encryption information - * - * @throws IOException in case of an IO error - * @throws PGPException if the message is broken - */ - public static EncryptionInfo determineEncryptionInfoForMessage(InputStream dataIn) throws IOException, PGPException { - InputStream decoded = ArmorUtils.getDecoderStream(dataIn); - EncryptionInfo info = new EncryptionInfo(); - - processMessage(decoded, info); - - return info; - } - - private static void processMessage(InputStream dataIn, EncryptionInfo info) throws PGPException, IOException { - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(dataIn); - - Object next; - while ((next = objectFactory.nextObject()) != null) { - if (next instanceof PGPOnePassSignatureList) { - PGPOnePassSignatureList signatures = (PGPOnePassSignatureList) next; - if (!signatures.isEmpty()) { - info.isSignedOnly = true; - return; - } - } - - if (next instanceof PGPEncryptedDataList) { - PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) next; - for (PGPEncryptedData encryptedData : encryptedDataList) { - if (encryptedData instanceof PGPPublicKeyEncryptedData) { - PGPPublicKeyEncryptedData pubKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData; - info.keyIds.add(pubKeyEncryptedData.getKeyID()); - } else if (encryptedData instanceof PGPPBEEncryptedData) { - info.isPassphraseEncrypted = true; - } - } - // Data is encrypted, we cannot go deeper - return; - } - - if (next instanceof PGPCompressedData) { - PGPCompressedData compressed = (PGPCompressedData) next; - InputStream decompressed = compressed.getDataStream(); - InputStream decoded = PGPUtil.getDecoderStream(decompressed); - objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoded); - } - - if (next instanceof PGPLiteralData) { - return; - } - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt new file mode 100644 index 00000000..64b2a5f3 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.openpgp.* +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.util.ArmorUtils +import java.io.IOException +import java.io.InputStream + +/** + * Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase protected. + */ +class MessageInspector { + + /** + * Info about an OpenPGP message. + * + * @param keyIds List of recipient key ids for whom the message is encrypted. + * @param isPassphraseEncrypted true, if the message is encrypted for a passphrase + * @param isSignedOnly true, if the message is not encrypted, but signed using OnePassSignatures + */ + data class EncryptionInfo( + val keyIds: List, + val isPassphraseEncrypted: Boolean, + val isSignedOnly: Boolean) { + + val isEncrypted: Boolean + get() = isPassphraseEncrypted || keyIds.isNotEmpty() + } + + companion object { + + /** + * Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it. + * + * @param message OpenPGP message + * @return encryption info + * + * @throws PGPException in case the message is broken + * @throws IOException in case of an IO error + */ + @JvmStatic + @Throws(PGPException::class, IOException::class) + fun determineEncryptionInfoForMessage(message: String): EncryptionInfo = determineEncryptionInfoForMessage(message.byteInputStream()) + + /** + * Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it. + * Note: This method does not rewind the passed in Stream, so you might need to take care of that yourselves. + * + * @param inputStream openpgp message + * @return encryption information + * + * @throws IOException in case of an IO error + * @throws PGPException if the message is broken + */ + @JvmStatic + @Throws(PGPException::class, IOException::class) + fun determineEncryptionInfoForMessage(inputStream: InputStream): EncryptionInfo { + return processMessage(ArmorUtils.getDecoderStream(inputStream)) + } + + @JvmStatic + @Throws(PGPException::class, IOException::class) + private fun processMessage(inputStream: InputStream): EncryptionInfo { + var objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(inputStream) + + var n: Any? + while (objectFactory.nextObject().also { n = it } != null) { + when (val next = n!!) { + + is PGPOnePassSignatureList -> { + if (!next.isEmpty) { + return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = true) + } + } + + is PGPEncryptedDataList -> { + var isPassphraseEncrypted = false + val keyIds = mutableListOf() + for (encryptedData in next) { + if (encryptedData is PGPPublicKeyEncryptedData) { + keyIds.add(encryptedData.keyID) + } else if (encryptedData is PGPPBEEncryptedData) { + isPassphraseEncrypted = true + } + } + // Data is encrypted, we cannot go deeper + return EncryptionInfo(keyIds, isPassphraseEncrypted, false) + } + + is PGPCompressedData -> { + objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( + PGPUtil.getDecoderStream(next.dataStream)) + continue + } + + is PGPLiteralData -> { + break + } + } + } + return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = false) + } + } +} \ No newline at end of file From 0fa09065cfce21ff5339e5a74f7d6cff790878c6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 20 Sep 2023 13:37:15 +0200 Subject: [PATCH 123/155] Kotlin conversion: TeeBCPGInputStream --- .../TeeBCPGInputStream.java | 159 ------------------ .../TeeBCPGInputStream.kt | 136 +++++++++++++++ 2 files changed, 136 insertions(+), 159 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java deleted file mode 100644 index 725c6f6e..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.bouncycastle.bcpg.BCPGInputStream; -import org.bouncycastle.bcpg.MarkerPacket; -import org.bouncycastle.bcpg.Packet; -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPOnePassSignature; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.algorithm.OpenPgpPacket; - -import javax.annotation.Nonnull; - -/** - * Since we need to update signatures with data from the underlying stream, this class is used to tee out the data. - * Unfortunately we cannot simply override {@link BCPGInputStream#read()} to tee the data out though, since - * {@link BCPGInputStream#readPacket()} inconsistently calls a mix of {@link BCPGInputStream#read()} and - * {@link InputStream#read()} of the underlying stream. This would cause the second length byte to get swallowed up. - * - * Therefore, this class delegates the teeing to an {@link DelayedTeeInputStream} which wraps the underlying - * stream. Since calling {@link BCPGInputStream#nextPacketTag()} reads up to and including the next packets tag, - * we need to delay teeing out that byte to signature verifiers. - * Hence, the reading methods of the {@link TeeBCPGInputStream} handle pushing this byte to the output stream using - * {@link DelayedTeeInputStream#squeeze()}. - */ -public class TeeBCPGInputStream { - - protected final DelayedTeeInputStream delayedTee; - // InputStream of OpenPGP packets of the current layer - protected final BCPGInputStream packetInputStream; - - public TeeBCPGInputStream(BCPGInputStream inputStream, OutputStream outputStream) { - this.delayedTee = new DelayedTeeInputStream(inputStream, outputStream); - this.packetInputStream = BCPGInputStream.wrap(delayedTee); - } - - public OpenPgpPacket nextPacketTag() throws IOException { - int tag = packetInputStream.nextPacketTag(); - if (tag == -1) { - return null; - } - - return OpenPgpPacket.requireFromTag(tag); - } - - public Packet readPacket() throws IOException { - return packetInputStream.readPacket(); - } - - public PGPCompressedData readCompressedData() throws IOException { - delayedTee.squeeze(); - PGPCompressedData compressedData = new PGPCompressedData(packetInputStream); - return compressedData; - } - - public PGPLiteralData readLiteralData() throws IOException { - delayedTee.squeeze(); - return new PGPLiteralData(packetInputStream); - } - - public PGPEncryptedDataList readEncryptedDataList() throws IOException { - delayedTee.squeeze(); - return new PGPEncryptedDataList(packetInputStream); - } - - public PGPOnePassSignature readOnePassSignature() throws PGPException, IOException { - PGPOnePassSignature onePassSignature = new PGPOnePassSignature(packetInputStream); - delayedTee.squeeze(); - return onePassSignature; - } - - public PGPSignature readSignature() throws PGPException, IOException { - PGPSignature signature = new PGPSignature(packetInputStream); - delayedTee.squeeze(); - return signature; - } - - public MarkerPacket readMarker() throws IOException { - MarkerPacket markerPacket = (MarkerPacket) readPacket(); - delayedTee.squeeze(); - return markerPacket; - } - - public void close() throws IOException { - this.packetInputStream.close(); - } - - public static class DelayedTeeInputStream extends InputStream { - - private int last = -1; - private final InputStream inputStream; - private final OutputStream outputStream; - - public DelayedTeeInputStream(InputStream inputStream, OutputStream outputStream) { - this.inputStream = inputStream; - this.outputStream = outputStream; - } - - @Override - public int read() throws IOException { - if (last != -1) { - outputStream.write(last); - } - try { - last = inputStream.read(); - return last; - } catch (IOException e) { - if (e.getMessage().contains("crc check failed in armored message")) { - throw e; - } - return -1; - } - } - - @Override - public int read(@Nonnull byte[] b, int off, int len) throws IOException { - if (last != -1) { - outputStream.write(last); - } - - int r = inputStream.read(b, off, len); - if (r > 0) { - outputStream.write(b, off, r - 1); - last = b[off + r - 1]; - } else { - last = -1; - } - return r; - } - - /** - * Squeeze the last byte out and update the output stream. - * - * @throws IOException in case of an IO error - */ - public void squeeze() throws IOException { - if (last != -1) { - outputStream.write(last); - } - last = -1; - } - - @Override - public void close() throws IOException { - inputStream.close(); - outputStream.close(); - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt new file mode 100644 index 00000000..f6c5a454 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification + +import org.bouncycastle.bcpg.BCPGInputStream +import org.bouncycastle.bcpg.MarkerPacket +import org.bouncycastle.bcpg.Packet +import org.bouncycastle.openpgp.PGPCompressedData +import org.bouncycastle.openpgp.PGPEncryptedDataList +import org.bouncycastle.openpgp.PGPLiteralData +import org.bouncycastle.openpgp.PGPOnePassSignature +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.algorithm.OpenPgpPacket +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +/** + * Since we need to update signatures with data from the underlying stream, this class is used to tee out the data. + * Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data out though, since + * [BCPGInputStream.readPacket] inconsistently calls a mix of [BCPGInputStream.read] and + * [InputStream.read] of the underlying stream. This would cause the second length byte to get swallowed up. + * + * Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the underlying + * stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the next packets tag, + * we need to delay teeing out that byte to signature verifiers. + * Hence, the reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using + * [DelayedTeeInputStream.squeeze]. + */ +class TeeBCPGInputStream( + inputStream: BCPGInputStream, + outputStream: OutputStream) { + + private val delayedTee: DelayedTeeInputStream + private val packetInputStream: BCPGInputStream + + init { + delayedTee = DelayedTeeInputStream(inputStream, outputStream) + packetInputStream = BCPGInputStream(delayedTee) + } + + fun nextPacketTag(): OpenPgpPacket? { + return packetInputStream.nextPacketTag().let { + if (it == -1) null + else OpenPgpPacket.requireFromTag(it) + } + } + + fun readPacket(): Packet = packetInputStream.readPacket() + + fun readCompressedData(): PGPCompressedData { + delayedTee.squeeze() + return PGPCompressedData(packetInputStream) + } + + fun readLiteralData(): PGPLiteralData { + delayedTee.squeeze() + return PGPLiteralData(packetInputStream) + } + + fun readEncryptedDataList(): PGPEncryptedDataList { + delayedTee.squeeze() + return PGPEncryptedDataList(packetInputStream) + } + + fun readOnePassSignature(): PGPOnePassSignature { + return PGPOnePassSignature(packetInputStream).also { delayedTee.squeeze() } + } + + fun readSignature(): PGPSignature { + return PGPSignature(packetInputStream).also { delayedTee.squeeze() } + } + + fun readMarker(): MarkerPacket { + return (readPacket() as MarkerPacket).also { delayedTee.squeeze() } + } + + fun close() { + packetInputStream.close() + } + + class DelayedTeeInputStream( + private val inputStream: InputStream, + private val outputStream: OutputStream + ) : InputStream() { + private var last: Int = -1 + + override fun read(): Int { + if (last != -1) { + outputStream.write(last) + } + return try { + last = inputStream.read() + last + } catch (e : IOException) { + if (e.message?.contains("crc check failed in armored message") == true) { + throw e + } + -1 + } + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + if (last != -1) { + outputStream.write(last) + } + + inputStream.read(b, off, len).let { r -> + last = if (r > 0) { + outputStream.write(b, off, r - 1) + b[off + r - 1].toInt() + } else { + -1 + } + return r + } + } + + /** + * Squeeze the last byte out and update the output stream. + */ + fun squeeze() { + if (last != -1) { + outputStream.write(last) + } + last = -1 + } + + override fun close() { + inputStream.close() + outputStream.close() + } + } +} \ No newline at end of file From 068aa0ec278d89e90c6c4153de0fa3f4462f6c73 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 20 Sep 2023 13:50:50 +0200 Subject: [PATCH 124/155] Kotlin conversion: SignatureGenerationStream --- .../SignatureGenerationStream.java | 65 ------------------- .../SignatureGenerationStream.kt | 39 +++++++++++ 2 files changed, 39 insertions(+), 65 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SignatureGenerationStream.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SignatureGenerationStream.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SignatureGenerationStream.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SignatureGenerationStream.java deleted file mode 100644 index 7a96f2e1..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SignatureGenerationStream.java +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.pgpainless.key.SubkeyIdentifier; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.IOException; -import java.io.OutputStream; - -/** - * OutputStream which has the task of updating signature generators for written data. - */ -class SignatureGenerationStream extends OutputStream { - - private final OutputStream wrapped; - private final SigningOptions options; - - SignatureGenerationStream(@Nonnull OutputStream wrapped, @Nullable SigningOptions signingOptions) { - this.wrapped = wrapped; - this.options = signingOptions; - } - - @Override - public void write(int b) throws IOException { - wrapped.write(b); - if (options == null || options.getSigningMethods().isEmpty()) { - return; - } - - for (SubkeyIdentifier signingKey : options.getSigningMethods().keySet()) { - SigningOptions.SigningMethod signingMethod = options.getSigningMethods().get(signingKey); - PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator(); - byte asByte = (byte) (b & 0xff); - signatureGenerator.update(asByte); - } - } - - @Override - public void write(@Nonnull byte[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - @Override - public void write(@Nonnull byte[] buffer, int off, int len) throws IOException { - wrapped.write(buffer, 0, len); - if (options == null || options.getSigningMethods().isEmpty()) { - return; - } - for (SubkeyIdentifier signingKey : options.getSigningMethods().keySet()) { - SigningOptions.SigningMethod signingMethod = options.getSigningMethods().get(signingKey); - PGPSignatureGenerator signatureGenerator = signingMethod.getSignatureGenerator(); - signatureGenerator.update(buffer, 0, len); - } - } - - @Override - public void close() throws IOException { - wrapped.close(); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SignatureGenerationStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SignatureGenerationStream.kt new file mode 100644 index 00000000..d0f04851 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SignatureGenerationStream.kt @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import java.io.OutputStream + +/** + * OutputStream which has the task of updating signature generators for written data. + */ +class SignatureGenerationStream( + private val wrapped: OutputStream, + private val options: SigningOptions? +) : OutputStream() { + + override fun close() = wrapped.close() + override fun flush() = wrapped.flush() + + override fun write(b: Int) { + wrapped.write(b) + options?.run { + signingMethods.values.forEach { + it.signatureGenerator.update((b and 0xff).toByte()) + } + } + } + + override fun write(b: ByteArray) = write(b, 0, b.size) + + override fun write(b: ByteArray, off: Int, len: Int) { + wrapped.write(b, off, len) + options?.run { + signingMethods.values.forEach { + it.signatureGenerator.update(b, off, len) + } + } + } +} \ No newline at end of file From a50be47fa4f9d9294747c8af7e5542e877f6beba Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 20 Sep 2023 13:57:16 +0200 Subject: [PATCH 125/155] Kotlin conversion: CRLFGeneratorStream --- .../CRLFGeneratorStream.java | 55 ------------------- .../encryption_signing/CRLFGeneratorStream.kt | 52 ++++++++++++++++++ 2 files changed, 52 insertions(+), 55 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/CRLFGeneratorStream.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/CRLFGeneratorStream.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/CRLFGeneratorStream.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/CRLFGeneratorStream.java deleted file mode 100644 index 4cab8be3..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/CRLFGeneratorStream.java +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2021 David Hook -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import org.pgpainless.algorithm.StreamEncoding; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * {@link OutputStream} which applies CR-LF encoding of its input data, based on the desired {@link StreamEncoding}. - * This implementation originates from the Bouncy Castle library. - */ -public class CRLFGeneratorStream extends OutputStream { - - protected final OutputStream crlfOut; - private final boolean isBinary; - private int lastB = 0; - - public CRLFGeneratorStream(OutputStream crlfOut, StreamEncoding encoding) { - this.crlfOut = crlfOut; - this.isBinary = encoding == StreamEncoding.BINARY; - } - - public void write(int b) throws IOException { - if (!isBinary) { - if (b == '\n' && lastB != '\r') { // Unix - crlfOut.write('\r'); - } else if (lastB == '\r') { // MAC - if (b != '\n') { - crlfOut.write('\n'); - } - } - lastB = b; - } - - crlfOut.write(b); - } - - public void close() throws IOException { - if (!isBinary && lastB == '\r') { // MAC - crlfOut.write('\n'); - } - crlfOut.close(); - } - - @Override - public void flush() throws IOException { - super.flush(); - crlfOut.flush(); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/CRLFGeneratorStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/CRLFGeneratorStream.kt new file mode 100644 index 00000000..8735d7b1 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/CRLFGeneratorStream.kt @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2021 David Hook +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.pgpainless.algorithm.StreamEncoding +import java.io.OutputStream + +/** + * [OutputStream] which applies CR-LF encoding of its input data, based on the desired [StreamEncoding]. + * This implementation originates from the Bouncy Castle library. + */ +class CRLFGeneratorStream( + private val crlfOut: OutputStream, + encoding: StreamEncoding +) : OutputStream() { + + private val isBinary: Boolean + private var lastB = 0 + + init { + isBinary = encoding == StreamEncoding.BINARY + } + + override fun write(b: Int) { + if (!isBinary) { + if (b == '\n'.code && lastB != '\r'.code) { // Unix + crlfOut.write('\r'.code) + } else if (lastB == '\r'.code) { // MAC + if (b != '\n'.code) { + crlfOut.write('\n'.code) + } + } + lastB = b + } + crlfOut.write(b) + } + + override fun close() { + if (!isBinary && lastB == 'r'.code) { + crlfOut.write('\n'.code) + } + crlfOut.close() + } + + override fun flush() { + super.flush() + crlfOut.flush() + } +} \ No newline at end of file From 53b1e3ff71e78273e38b93eea040159e99ef2be3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 26 Sep 2023 14:47:05 +0200 Subject: [PATCH 126/155] Kotlin conversion: HashContextSigning --- .../BcHashContextSigner.java | 66 ------- .../BcPGPHashContextContentSignerBuilder.java | 174 ------------------ .../PGPHashContextContentSignerBuilder.java | 83 --------- .../encryption_signing/package-info.java | 8 - .../encryption_signing/BcHashContextSigner.kt | 49 +++++ .../BcPGPHashContextContentSignerBuilder.kt | 131 +++++++++++++ .../PGPHashContextContentSignerBuilder.kt | 42 +++++ 7 files changed, 222 insertions(+), 331 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcHashContextSigner.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/encryption_signing/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcHashContextSigner.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcHashContextSigner.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcHashContextSigner.java deleted file mode 100644 index 54c6df9e..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcHashContextSigner.java +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.security.MessageDigest; -import java.util.List; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnlockSecretKey; - -import javax.annotation.Nonnull; - -public class BcHashContextSigner { - - public static PGPSignature signHashContext(@Nonnull MessageDigest hashContext, - @Nonnull SignatureType signatureType, - @Nonnull PGPSecretKeyRing secretKeys, - @Nonnull SecretKeyRingProtector protector) - throws PGPException { - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); - List signingSubkeyCandidates = info.getSigningSubkeys(); - PGPSecretKey signingKey = null; - for (PGPPublicKey signingKeyCandidate : signingSubkeyCandidates) { - signingKey = secretKeys.getSecretKey(signingKeyCandidate.getKeyID()); - if (signingKey != null) { - break; - } - } - if (signingKey == null) { - throw new PGPException("Key does not contain suitable signing subkey."); - } - - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(signingKey, protector); - return signHashContext(hashContext, signatureType, privateKey); - } - - /** - * Create an OpenPGP Signature over the given {@link MessageDigest} hash context. - * - * @param hashContext hash context - * @param privateKey signing-capable key - * @return signature - * @throws PGPException in case of an OpenPGP error - */ - static PGPSignature signHashContext(MessageDigest hashContext, SignatureType signatureType, PGPPrivateKey privateKey) - throws PGPException { - PGPSignatureGenerator sigGen = new PGPSignatureGenerator( - new BcPGPHashContextContentSignerBuilder(hashContext) - ); - - sigGen.init(signatureType.getCode(), privateKey); - return sigGen.generate(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.java deleted file mode 100644 index 5cdf9e36..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.java +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.io.OutputStream; -import java.security.MessageDigest; - -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; -import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.crypto.CryptoException; -import org.bouncycastle.crypto.DataLengthException; -import org.bouncycastle.crypto.Digest; -import org.bouncycastle.crypto.Signer; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; -import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters; -import org.bouncycastle.crypto.signers.DSADigestSigner; -import org.bouncycastle.crypto.signers.DSASigner; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.bouncycastle.crypto.signers.Ed25519Signer; -import org.bouncycastle.crypto.signers.Ed448Signer; -import org.bouncycastle.crypto.signers.RSADigestSigner; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.operator.PGPContentSigner; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter; -import org.bouncycastle.util.Arrays; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.PublicKeyAlgorithm; - -/** - * Implementation of {@link PGPContentSignerBuilder} using the BC API, which can be used to sign hash contexts. - * This can come in handy to sign data, which was already processed to calculate the hash context, without the - * need to process it again to calculate the OpenPGP signature. - */ -class BcPGPHashContextContentSignerBuilder extends PGPHashContextContentSignerBuilder { - - private final BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); - private final MessageDigest messageDigest; - private final HashAlgorithm hashAlgorithm; - - BcPGPHashContextContentSignerBuilder(MessageDigest messageDigest) { - this.messageDigest = messageDigest; - this.hashAlgorithm = requireFromName(messageDigest.getAlgorithm()); - } - - private static HashAlgorithm requireFromName(String digestName) { - HashAlgorithm hashAlgorithm = HashAlgorithm.fromName(digestName); - if (hashAlgorithm == null) { - throw new IllegalArgumentException("Cannot recognize OpenPGP Hash Algorithm: " + digestName); - } - return hashAlgorithm; - } - - @Override - public PGPContentSigner build(int signatureType, PGPPrivateKey privateKey) throws PGPException { - PublicKeyAlgorithm keyAlgorithm = PublicKeyAlgorithm.requireFromId(privateKey.getPublicKeyPacket().getAlgorithm()); - AsymmetricKeyParameter privKeyParam = keyConverter.getPrivateKey(privateKey); - final Signer signer = createSigner(keyAlgorithm, messageDigest, privKeyParam); - signer.init(true, privKeyParam); - - return new PGPContentSigner() { - public int getType() { - return signatureType; - } - - public int getHashAlgorithm() { - return hashAlgorithm.getAlgorithmId(); - } - - public int getKeyAlgorithm() { - return keyAlgorithm.getAlgorithmId(); - } - - public long getKeyID() { - return privateKey.getKeyID(); - } - - public OutputStream getOutputStream() { - return new PGPHashContextContentSignerBuilder.SignerOutputStream(signer); - } - - public byte[] getSignature() { - try { - return signer.generateSignature(); - } catch (CryptoException e) { - throw new IllegalStateException("unable to create signature"); - } - } - - public byte[] getDigest() { - return messageDigest.digest(); - } - }; - } - - static Signer createSigner( - PublicKeyAlgorithm keyAlgorithm, - MessageDigest messageDigest, - CipherParameters keyParam) - throws PGPException { - ExistingMessageDigest staticDigest = new ExistingMessageDigest(messageDigest); - switch (keyAlgorithm.getAlgorithmId()) { - case PublicKeyAlgorithmTags.RSA_GENERAL: - case PublicKeyAlgorithmTags.RSA_SIGN: - return new RSADigestSigner(staticDigest); - case PublicKeyAlgorithmTags.DSA: - return new DSADigestSigner(new DSASigner(), staticDigest); - case PublicKeyAlgorithmTags.ECDSA: - return new DSADigestSigner(new ECDSASigner(), staticDigest); - case PublicKeyAlgorithmTags.EDDSA: - if (keyParam instanceof Ed25519PrivateKeyParameters || keyParam instanceof Ed25519PublicKeyParameters) { - return new EdDsaSigner(new Ed25519Signer(), staticDigest); - } - return new EdDsaSigner(new Ed448Signer(new byte[0]), staticDigest); - default: - throw new PGPException("cannot recognise keyAlgorithm: " + keyAlgorithm); - } - } - - // Copied from BCs BcImplProvider - required since BCs class is package visible only :/ - private static class EdDsaSigner - implements Signer { - private final Signer signer; - private final Digest digest; - private final byte[] digBuf; - - EdDsaSigner(Signer signer, Digest digest) { - this.signer = signer; - this.digest = digest; - this.digBuf = new byte[digest.getDigestSize()]; - } - - public void init(boolean forSigning, CipherParameters param) { - this.signer.init(forSigning, param); - this.digest.reset(); - } - - public void update(byte b) { - this.digest.update(b); - } - - public void update(byte[] in, int off, int len) { - this.digest.update(in, off, len); - } - - public byte[] generateSignature() - throws CryptoException, DataLengthException { - digest.doFinal(digBuf, 0); - - signer.update(digBuf, 0, digBuf.length); - - return signer.generateSignature(); - } - - public boolean verifySignature(byte[] signature) { - digest.doFinal(digBuf, 0); - - signer.update(digBuf, 0, digBuf.length); - - return signer.verifySignature(signature); - } - - public void reset() { - Arrays.clear(digBuf); - signer.reset(); - digest.reset(); - } - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.java deleted file mode 100644 index 7b8529fe..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.java +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.io.OutputStream; -import java.security.MessageDigest; -import javax.annotation.Nonnull; - -import org.bouncycastle.crypto.Digest; -import org.bouncycastle.crypto.Signer; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; - -abstract class PGPHashContextContentSignerBuilder implements PGPContentSignerBuilder { - - // Copied from BC, required since BCs class is package visible only - static class SignerOutputStream - extends OutputStream { - private Signer sig; - - SignerOutputStream(Signer sig) { - this.sig = sig; - } - - public void write(@Nonnull byte[] bytes, int off, int len) { - sig.update(bytes, off, len); - } - - public void write(@Nonnull byte[] bytes) { - sig.update(bytes, 0, bytes.length); - } - - public void write(int b) { - sig.update((byte) b); - } - } - - - static class ExistingMessageDigest implements Digest { - - private final MessageDigest digest; - - ExistingMessageDigest(MessageDigest messageDigest) { - this.digest = messageDigest; - } - - @Override - public void update(byte in) { - digest.update(in); - } - - @Override - public void update(byte[] in, int inOff, int len) { - digest.update(in, inOff, len); - } - - @Override - public int doFinal(byte[] out, int outOff) { - byte[] hash = digest.digest(); - System.arraycopy(hash, 0, out, outOff, hash.length); - return getDigestSize(); - } - - @Override - public void reset() { - // Nope! - // We cannot reset, since BCs signer classes are resetting in their init() methods, which would also reset - // the messageDigest, losing its state. This would shatter our intention. - } - - @Override - public String getAlgorithmName() { - return digest.getAlgorithm(); - } - - @Override - public int getDigestSize() { - return digest.getDigestLength(); - } - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/package-info.java deleted file mode 100644 index 4a2f1f39..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes used to encrypt or sign data using OpenPGP. - */ -package org.pgpainless.encryption_signing; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcHashContextSigner.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcHashContextSigner.kt new file mode 100644 index 00000000..90f275a4 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcHashContextSigner.kt @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.extensions.unlock +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPPrivateKey +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.PGPSignatureGenerator +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.SignatureType +import org.pgpainless.key.protection.SecretKeyRingProtector +import java.security.MessageDigest + +class BcHashContextSigner { + + companion object { + @JvmStatic + fun signHashContext(hashContext: MessageDigest, + signatureType: SignatureType, + secretKey: PGPSecretKeyRing, + protector: SecretKeyRingProtector): PGPSignature { + val info = PGPainless.inspectKeyRing(secretKey) + return info.signingSubkeys.mapNotNull { info.getSecretKey(it.keyID) }.firstOrNull() + ?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) } + ?: throw PGPException("Key does not contain suitable signing subkey.") + } + + /** + * Create an OpenPGP Signature over the given [MessageDigest] hash context. + * + * @param hashContext hash context + * @param privateKey signing-capable key + * @return signature + * @throws PGPException in case of an OpenPGP error + */ + @JvmStatic + internal fun signHashContext(hashContext: MessageDigest, + signatureType: SignatureType, + privateKey: PGPPrivateKey): PGPSignature { + return PGPSignatureGenerator(BcPGPHashContextContentSignerBuilder(hashContext)) + .apply { init(signatureType.code, privateKey) } + .generate() + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt new file mode 100644 index 00000000..381c4716 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags +import org.bouncycastle.crypto.CipherParameters +import org.bouncycastle.crypto.CryptoException +import org.bouncycastle.crypto.Digest +import org.bouncycastle.crypto.Signer +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters +import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters +import org.bouncycastle.crypto.signers.DSADigestSigner +import org.bouncycastle.crypto.signers.DSASigner +import org.bouncycastle.crypto.signers.ECDSASigner +import org.bouncycastle.crypto.signers.Ed25519Signer +import org.bouncycastle.crypto.signers.Ed448Signer +import org.bouncycastle.crypto.signers.RSADigestSigner +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPPrivateKey +import org.bouncycastle.openpgp.operator.PGPContentSigner +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.PublicKeyAlgorithm +import java.io.OutputStream +import java.security.MessageDigest + +/** + * Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash contexts. + * This can come in handy to sign data, which was already processed to calculate the hash context, without the + * need to process it again to calculate the OpenPGP signature. + */ +class BcPGPHashContextContentSignerBuilder( + private val messageDigest: MessageDigest +) : PGPHashContextContentSignerBuilder() { + + private val keyConverter = BcPGPKeyConverter() + private val _hashAlgorithm: HashAlgorithm + + init { + _hashAlgorithm = requireFromName(messageDigest.algorithm) + } + + override fun build(signatureType: Int, privateKey: PGPPrivateKey): PGPContentSigner { + val keyAlgorithm = PublicKeyAlgorithm.requireFromId(privateKey.publicKeyPacket.algorithm) + val privKeyParam = keyConverter.getPrivateKey(privateKey) + val signer = createSigner(keyAlgorithm, messageDigest, privKeyParam) + signer.init(true, privKeyParam) + + return object : PGPContentSigner { + override fun getOutputStream(): OutputStream = SignerOutputStream(signer) + override fun getSignature(): ByteArray = try { + signer.generateSignature() + } catch (e : CryptoException) { + throw IllegalStateException("unable to create signature.", e) + } + override fun getDigest(): ByteArray = messageDigest.digest() + override fun getType(): Int = signatureType + override fun getHashAlgorithm(): Int = _hashAlgorithm.algorithmId + override fun getKeyAlgorithm(): Int = keyAlgorithm.algorithmId + override fun getKeyID(): Long = privateKey.keyID + } + } + + companion object { + @JvmStatic + private fun requireFromName(digestName: String): HashAlgorithm { + val algorithm = HashAlgorithm.fromName(digestName) + require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName"} + return algorithm + } + + @JvmStatic + private fun createSigner(keyAlgorithm: PublicKeyAlgorithm, + messageDigest: MessageDigest, + keyParam: CipherParameters): Signer { + val staticDigest = ExistingMessageDigest(messageDigest) + return when (keyAlgorithm.algorithmId) { + PublicKeyAlgorithmTags.RSA_GENERAL, PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest) + PublicKeyAlgorithmTags.DSA -> DSADigestSigner(DSASigner(), staticDigest) + PublicKeyAlgorithmTags.ECDSA -> DSADigestSigner(ECDSASigner(), staticDigest) + PublicKeyAlgorithmTags.EDDSA_LEGACY -> { + if (keyParam is Ed25519PrivateKeyParameters || keyParam is Ed25519PublicKeyParameters) + EdDsaSigner(Ed25519Signer(), staticDigest) + else EdDsaSigner(Ed448Signer(byteArrayOf()), staticDigest) + } + else -> throw PGPException("cannot recognize keyAlgorithm: $keyAlgorithm") + } + } + } + + // Copied from BCs BcImplProvider - required since BCs class is package visible only :/ + internal class EdDsaSigner( + private val signer: Signer, + private val digest: Digest + ) : Signer { + private val digBuf: ByteArray = ByteArray(digest.digestSize) + + override fun init(forSigning: Boolean, param: CipherParameters) { + signer.init(forSigning, param) + digest.reset() + } + + override fun update(b: Byte) { + digest.update(b) + } + + override fun update(b: ByteArray, off: Int, len: Int) { + digest.update(b, off, len) + } + + override fun generateSignature(): ByteArray { + digest.doFinal(digBuf, 0) + signer.update(digBuf, 0, digBuf.size) + return signer.generateSignature() + } + + override fun verifySignature(signature: ByteArray): Boolean { + digest.doFinal(digBuf, 0) + signer.update(digBuf, 0, digBuf.size) + return signer.verifySignature(signature) + } + + override fun reset() { + digBuf.fill(0) + signer.reset() + digest.reset() + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.kt new file mode 100644 index 00000000..aa3dd58c --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.kt @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.crypto.Digest +import org.bouncycastle.crypto.Signer +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder +import java.io.OutputStream +import java.security.MessageDigest + +abstract class PGPHashContextContentSignerBuilder : PGPContentSignerBuilder { + + // Copied from BC, required since BCs class is package visible only + internal class SignerOutputStream( + private val signer: Signer + ) : OutputStream() { + override fun write(p0: Int) = signer.update(p0.toByte()) + override fun write(b: ByteArray) = signer.update(b, 0, b.size) + override fun write(b: ByteArray, off: Int, len: Int) = signer.update(b, off, len) + } + + internal class ExistingMessageDigest( + private val digest: MessageDigest + ) : Digest { + + override fun getAlgorithmName(): String = digest.algorithm + override fun getDigestSize(): Int = digest.digestLength + override fun update(b: Byte) = digest.update(b) + override fun update(buf: ByteArray, inOff: Int, len: Int) = digest.update(buf) + override fun doFinal(out: ByteArray, outOff: Int): Int { + digest.digest().copyInto(out, outOff) + return digestSize + } + override fun reset() { + // Nope! + // We cannot reset, since BCs signer classes are resetting in their init() methods, which would also reset + // the messageDigest, losing its state. This would shatter our intention. + } + } +} \ No newline at end of file From 33037b9743515e426c50ac5ad82c6e8e6465f633 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 26 Sep 2023 15:19:32 +0200 Subject: [PATCH 127/155] Kotlin conversion: Passphrase --- .../java/org/pgpainless/util/Passphrase.java | 168 ------------------ .../kotlin/org/pgpainless/util/Passphrase.kt | 105 +++++++++++ 2 files changed, 105 insertions(+), 168 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java b/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java deleted file mode 100644 index 9576fb3e..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/Passphrase.java +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Arrays; - -public class Passphrase { - - public final Object lock = new Object(); - - private final char[] chars; - private boolean valid = true; - - /** - * Passphrase for keys etc. - * - * @param chars may be null for empty passwords. - */ - public Passphrase(@Nullable char[] chars) { - if (chars == null) { - this.chars = null; - } else { - char[] trimmed = removeTrailingAndLeadingWhitespace(chars); - if (trimmed.length == 0) { - this.chars = null; - } else { - this.chars = trimmed; - } - } - } - - /** - * Return a copy of the passed in char array, with leading and trailing whitespace characters removed. - * - * @param chars char array - * @return copy of char array with leading and trailing whitespace characters removed - */ - private static char[] removeTrailingAndLeadingWhitespace(char[] chars) { - int i = 0; - while (i < chars.length && isWhitespace(chars[i])) { - i++; - } - int j = chars.length - 1; - while (j >= i && isWhitespace(chars[j])) { - j--; - } - - char[] trimmed = new char[chars.length - i - (chars.length - 1 - j)]; - System.arraycopy(chars, i, trimmed, 0, trimmed.length); - - return trimmed; - } - - /** - * Return true, if the passed in char is a whitespace symbol (space, newline, tab). - * - * @param xar char - * @return true if whitespace - */ - private static boolean isWhitespace(char xar) { - return xar == ' ' || xar == '\n' || xar == '\t'; - } - - /** - * Create a {@link Passphrase} from a {@link String}. - * - * @param password password - * @return passphrase - */ - public static Passphrase fromPassword(@Nonnull String password) { - return new Passphrase(password.toCharArray()); - } - - /** - * Overwrite the char array with spaces and mark the {@link Passphrase} as invalidated. - */ - public void clear() { - synchronized (lock) { - if (chars != null) { - Arrays.fill(chars, ' '); - } - valid = false; - } - } - - /** - * Return a copy of the underlying char array. - * A return value of {@code null} represents no password. - * - * @return passphrase chars. - * - * @throws IllegalStateException in case the password has been cleared at this point. - */ - public @Nullable char[] getChars() { - synchronized (lock) { - if (!valid) { - throw new IllegalStateException("Passphrase has been cleared."); - } - - if (chars == null) { - return null; - } - - char[] copy = new char[chars.length]; - System.arraycopy(chars, 0, copy, 0, chars.length); - return copy; - } - } - - /** - * Return true if the passphrase has not yet been cleared. - * - * @return valid - */ - public boolean isValid() { - synchronized (lock) { - return valid; - } - } - - /** - * Return true if the passphrase represents no password. - * - * @return empty - */ - public boolean isEmpty() { - synchronized (lock) { - return valid && chars == null; - } - } - - /** - * Represents a {@link Passphrase} instance that represents no password. - * - * @return empty passphrase - */ - public static Passphrase emptyPassphrase() { - return new Passphrase(null); - } - - @Override - public int hashCode() { - if (getChars() == null) { - return 0; - } - return new String(getChars()).hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (!(obj instanceof Passphrase)) { - return false; - } - Passphrase other = (Passphrase) obj; - return (getChars() == null && other.getChars() == null) || - org.bouncycastle.util.Arrays.constantTimeAreEqual(getChars(), other.getChars()); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt new file mode 100644 index 00000000..a64fda53 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +import org.bouncycastle.util.Arrays + +/** + * Passphrase for keys or messages. + * + * @param chars may be null for empty passwords. + */ +class Passphrase( + chars: CharArray? +) { + private val lock = Any() + private var valid = true + private val chars: CharArray? + + init { + this.chars = trimWhitespace(chars) + } + + /** + * Return a copy of the underlying char array. + * A return value of null represents an empty password. + * + * @return passphrase chars. + * + * @throws IllegalStateException in case the password has been cleared at this point. + */ + fun getChars(): CharArray? = synchronized(lock) { + check(valid) { "Passphrase has been cleared." } + chars?.copyOf() + } + + /** + * Return true if the passphrase has not yet been cleared. + * + * @return valid + */ + val isValid: Boolean + get() = synchronized(lock) { valid } + + /** + * Return true if the passphrase represents no password. + * + * @return empty + */ + val isEmpty: Boolean + get() = synchronized(lock) { valid && chars == null } + + /** + * Overwrite the char array with spaces and mark the [Passphrase] as invalidated. + */ + fun clear() = synchronized(lock) { + chars?.fill(' ') + valid = false + } + + override fun equals(other: Any?): Boolean { + return if (other == null) + false + else if (this === other) + true + else if (other !is Passphrase) + false + else + getChars() == null && other.getChars() == null || Arrays.constantTimeAreEqual(getChars(), other.getChars()) + } + + override fun hashCode(): Int = getChars()?.let { String(it) }.hashCode() + + companion object { + + /** + * Create a [Passphrase] from a [CharSequence]. + * + * @param password password + * @return passphrase + */ + @JvmStatic + fun fromPassword(password: CharSequence) = Passphrase(password.toString().toCharArray()) + + @JvmStatic + fun emptyPassphrase() = Passphrase(null) + + /** + * Return a copy of the passed in char array, with leading and trailing whitespace characters removed. + * If the passed in char array is null, return null. + * If the resulting char array is empty, return null as well. + * + * @param chars char array + * @return copy of char array with leading and trailing whitespace characters removed + */ + @JvmStatic + private fun trimWhitespace(chars: CharArray?): CharArray? { + return chars?.dropWhile { it.isWhitespace() } + ?.dropLastWhile { it.isWhitespace() } + ?.toCharArray() + ?.let { if (it.isEmpty()) null else it } + } + } +} \ No newline at end of file From c9f988b2d18caf7e0609fa73813e986ac3b25674 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 26 Sep 2023 17:38:07 +0200 Subject: [PATCH 128/155] Kotlin conversion: SelectUserId --- .../util/selection/userid/SelectUserId.java | 231 ------------------ .../util/selection/userid/package-info.java | 8 - .../secretkeyring/SecretKeyRingEditor.kt | 37 ++- .../SecretKeyRingEditorInterface.kt | 78 +++++- .../util/selection/userid/SelectUserId.kt | 104 ++++++++ .../selection/userid/SelectUserIdTest.java | 78 +++--- 6 files changed, 240 insertions(+), 296 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/userid/SelectUserId.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/userid/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/selection/userid/SelectUserId.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/userid/SelectUserId.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/userid/SelectUserId.java deleted file mode 100644 index 5c1611af..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/userid/SelectUserId.java +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util.selection.userid; - -import java.util.ArrayList; -import java.util.List; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.pgpainless.PGPainless; -import org.pgpainless.key.info.KeyRingInfo; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Filter for selecting user-ids from keys and from lists. - */ -public abstract class SelectUserId { - - /** - * Return true, if the given user-id is accepted by this particular filter, false otherwise. - * - * @param userId user-id - * @return acceptance of the filter - */ - protected abstract boolean accept(String userId); - - /** - * Select all currently valid user-ids of the given key ring. - * - * @param keyRing public or secret key ring - * @return valid user-ids - */ - @Nonnull - public List selectUserIds(@Nonnull PGPKeyRing keyRing) { - List userIds = PGPainless.inspectKeyRing(keyRing).getValidUserIds(); - return selectUserIds(userIds); - } - - /** - * Select all acceptable (see {@link #accept(String)}) from the given list of user-ids. - * - * @param userIds list of user-ids - * @return sub-list of acceptable user-ids - */ - @Nonnull - public List selectUserIds(@Nonnull List userIds) { - List selected = new ArrayList<>(); - for (String userId : userIds) { - if (accept(userId)) { - selected.add(userId); - } - } - return selected; - } - - /** - * Return the first valid, acceptable user-id from the given public or secret key ring. - * - * @param keyRing public or secret key ring - * @return first matching valid user-id or null - */ - @Nullable - public String firstMatch(PGPKeyRing keyRing) { - return firstMatch(selectUserIds(keyRing)); - } - - /** - * Return the first valid, acceptable user-id from the list of user-ids. - * - * @param userIds list of user-ids - * @return first matching valid user-id or null - */ - @Nullable - public String firstMatch(@Nonnull List userIds) { - for (String userId : userIds) { - if (accept(userId)) { - return userId; - } - } - return null; - } - - /** - * Filter that filters for user-ids which contain the given
query
as a substring. - * - * @param query query - * @return filter - */ - public static SelectUserId containsSubstring(@Nonnull CharSequence query) { - return new SelectUserId() { - @Override - protected boolean accept(String userId) { - return userId.contains(query.toString()); - } - }; - } - - /** - * Filter that filters for user-ids which match the given
query
exactly. - * - * @param query query - * @return filter - */ - public static SelectUserId exactMatch(@Nonnull CharSequence query) { - return new SelectUserId() { - @Override - protected boolean accept(String userId) { - return userId.equals(query.toString()); - } - }; - } - - /** - * Filter that filters for user-ids which start with the given
substring
. - * - * @param substring substring - * @return filter - */ - public static SelectUserId startsWith(@Nonnull CharSequence substring) { - String string = substring.toString(); - return new SelectUserId() { - @Override - protected boolean accept(String userId) { - return userId.startsWith(string); - } - }; - } - - /** - * Filter that filters for user-ids which contain the given
email
address. - * Note: This only accepts user-ids which properly have the email address surrounded by angle brackets. - * - * The argument
email
can both be a plain email address (
"foo@bar.baz"
), - * or surrounded by angle brackets ({@code
""
}), the result of the filter will be the same. - * - * @param email email address - * @return filter - */ - public static SelectUserId containsEmailAddress(@Nonnull CharSequence email) { - String string = email.toString(); - return containsSubstring(string.matches("^<.+>$") ? string : '<' + string + '>'); - } - - /** - * Filter that filters for valid user-ids on the given
keyRing
only. - * - * @param keyRing public / secret keys - * @return filter - */ - public static SelectUserId validUserId(PGPKeyRing keyRing) { - final KeyRingInfo info = PGPainless.inspectKeyRing(keyRing); - - return new SelectUserId() { - @Override - protected boolean accept(String userId) { - return info.isUserIdValid(userId); - } - }; - } - - /** - * Filter that filters for user-ids which pass all the given
filters
. - * - * @param filters filters - * @return filter - */ - public static SelectUserId and(SelectUserId... filters) { - return new SelectUserId() { - @Override - protected boolean accept(String userId) { - boolean accept = true; - for (SelectUserId filter : filters) { - accept &= filter.accept(userId); - } - return accept; - } - }; - } - - /** - * Filter that filters for user-ids which pass at least one of the given
filters
. - * - * @param filters filters - * @return filter - */ - public static SelectUserId or(SelectUserId... filters) { - return new SelectUserId() { - @Override - protected boolean accept(String userId) { - boolean accept = false; - for (SelectUserId filter : filters) { - accept |= filter.accept(userId); - } - return accept; - } - }; - } - - /** - * Filter that inverts the result of the given
filter
. - * - * @param filter filter - * @return inverting filter - */ - public static SelectUserId not(SelectUserId filter) { - return new SelectUserId() { - @Override - protected boolean accept(String userId) { - return !filter.accept(userId); - } - }; - } - - /** - * Filter that selects user-ids by the given
email
address. - * It returns user-ids which either contain the given
email
address as angle-bracketed string, - * or which equal the given
email
string exactly. - * - * @param email email - * @return filter - */ - public static SelectUserId byEmail(CharSequence email) { - return SelectUserId.or( - SelectUserId.exactMatch(email), - SelectUserId.containsEmailAddress(email) - ); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/userid/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/userid/package-info.java deleted file mode 100644 index 5b70f412..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/userid/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * UserID selection strategies. - */ -package org.pgpainless.util.selection.userid; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt index 2fde469c..b219a739 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt @@ -31,7 +31,9 @@ import org.pgpainless.signature.subpackets.* import org.pgpainless.util.Passphrase import org.pgpainless.util.selection.userid.SelectUserId import java.util.* +import java.util.function.Predicate import javax.annotation.Nonnull +import kotlin.NoSuchElementException class SecretKeyRingEditor( var secretKeyRing: PGPSecretKeyRing, @@ -109,14 +111,22 @@ class SecretKeyRingEditor( return this } + @Deprecated("Use of SelectUserId class is deprecated.", replaceWith = ReplaceWith("removeUserId(protector, predicate)")) override fun removeUserId(selector: SelectUserId, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { return revokeUserIds(selector, protector, RevocationAttributes.createCertificateRevocation() .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) .withoutDescription()) } + override fun removeUserId(protector: SecretKeyRingProtector, predicate: (String) -> Boolean): SecretKeyRingEditorInterface { + return revokeUserIds(protector, RevocationAttributes.createCertificateRevocation() + .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) + .withoutDescription(), + predicate) + } + override fun removeUserId(userId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { - return removeUserId(SelectUserId.exactMatch(userId), protector) + return removeUserId(protector) { uid -> userId == uid } } override fun replaceUserId(oldUserId: CharSequence, newUserId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { @@ -246,21 +256,23 @@ class SecretKeyRingEditor( } override fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { - return revokeUserIds(SelectUserId.exactMatch(sanitizeUserId(userId)), protector, callback) + return revokeUserIds(protector, callback, SelectUserId.exactMatch(sanitizeUserId(userId))) } - override fun revokeUserIds(selector: SelectUserId, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { - return revokeUserIds(selector, protector, object : RevocationSignatureSubpackets.Callback { + override fun revokeUserIds(protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes?, + predicate: (String) -> Boolean): SecretKeyRingEditorInterface { + return revokeUserIds(protector, object : RevocationSignatureSubpackets.Callback { override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { - if (revocationAttributes != null) { - hashedSubpackets.setRevocationReason(revocationAttributes) - } + if (revocationAttributes != null) hashedSubpackets.setRevocationReason(revocationAttributes) } - }) + }, predicate) } - override fun revokeUserIds(selector: SelectUserId, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { - selector.selectUserIds(secretKeyRing).also { + override fun revokeUserIds(protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback?, + predicate: (String) -> Boolean): SecretKeyRingEditorInterface { + selectUserIds(predicate).also { if (it.isEmpty()) throw NoSuchElementException("No matching user-ids found on the key.") }.forEach { userId -> doRevokeUserId(userId, protector, callback) } return this @@ -348,7 +360,7 @@ class SecretKeyRingEditor( private fun sanitizeUserId(userId: CharSequence): CharSequence = // TODO: Further research how to sanitize user IDs. - // eg. what about newlines? + // e.g. what about newlines? userId.toString().trim() private fun callbackFromRevocationAttributes(attributes: RevocationAttributes?) = @@ -454,6 +466,9 @@ class SecretKeyRingEditor( }.build(secretKeyRing.publicKey) } + private fun selectUserIds(predicate: Predicate): List = + inspectKeyRing(secretKeyRing).validUserIds.filter { predicate.test(it) } + private class WithKeyRingEncryptionSettingsImpl( private val editor: SecretKeyRingEditor, private val keyId: Long?, diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt index 45a09fb2..8a26161b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt @@ -4,11 +4,7 @@ package org.pgpainless.key.modification.secretkeyring -import org.bouncycastle.openpgp.PGPException -import org.bouncycastle.openpgp.PGPKeyPair -import org.bouncycastle.openpgp.PGPPublicKeyRing -import org.bouncycastle.openpgp.PGPSecretKeyRing -import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.* import org.pgpainless.algorithm.KeyFlag import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.generation.KeySpec @@ -23,6 +19,7 @@ import java.io.IOException import java.security.InvalidAlgorithmParameterException import java.security.NoSuchAlgorithmException import java.util.* +import java.util.function.Predicate interface SecretKeyRingEditorInterface { @@ -82,8 +79,25 @@ interface SecretKeyRingEditorInterface { * * @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): SecretKeyRingEditorInterface + 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. @@ -342,9 +356,32 @@ interface SecretKeyRingEditorInterface { * @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?): SecretKeyRingEditorInterface + 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. @@ -364,9 +401,34 @@ interface SecretKeyRingEditorInterface { * @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?): SecretKeyRingEditorInterface + 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. diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/selection/userid/SelectUserId.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/selection/userid/SelectUserId.kt new file mode 100644 index 00000000..3c215d80 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/selection/userid/SelectUserId.kt @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util.selection.userid + +import org.bouncycastle.openpgp.PGPKeyRing +import org.pgpainless.PGPainless +import java.util.function.Predicate + +abstract class SelectUserId : Predicate, (String) -> Boolean { + + /** + * Legacy glue code to forward accept() calls to invoke() instead. + */ + @Deprecated("Use invoke() instead.", ReplaceWith("invoke(userId)")) + protected fun accept(userId: String): Boolean = invoke(userId) + + override fun test(userId: String): Boolean = invoke(userId) + + companion object { + + /** + * Filter for user-ids which match the given [query] exactly. + * + * @param query query + * @return filter + */ + @JvmStatic + fun exactMatch(query: CharSequence) = object : SelectUserId() { + override fun invoke(userId: String): Boolean = + userId == query + } + + /** + * Filter for user-ids which start with the given [substring]. + * + * @param substring substring + * @return filter + */ + @JvmStatic + fun startsWith(substring: CharSequence) = object : SelectUserId() { + override fun invoke(userId: String): Boolean = + userId.startsWith(substring) + } + + /** + * Filter for user-ids which contain the given [substring]. + * + * @param substring query + * @return filter + */ + @JvmStatic + fun containsSubstring(substring: CharSequence) = object : SelectUserId() { + override fun invoke(userId: String): Boolean = + userId.contains(substring) + } + + /** + * Filter for user-ids which contain the given [email] address. + * Note: This only accepts user-ids which properly have the email address surrounded by angle brackets. + * + * The argument [email] can both be a plain email address (`foo@bar.baz`), + * or surrounded by angle brackets (``), the result of the filter will be the same. + * + * @param email email address + * @return filter + */ + @JvmStatic + fun containsEmailAddress(email: CharSequence) = + if (email.startsWith('<') && email.endsWith('>')) + containsSubstring(email) + else + containsSubstring("<$email>") + + @JvmStatic + fun byEmail(email: CharSequence) = or(exactMatch(email), containsEmailAddress(email)) + + @JvmStatic + fun validUserId(keyRing: PGPKeyRing) = object : SelectUserId() { + private val info = PGPainless.inspectKeyRing(keyRing) + override fun invoke(userId: String): Boolean = + info.isUserIdValid(userId) + } + + @JvmStatic + fun and(vararg filters: SelectUserId) = object : SelectUserId() { + override fun invoke(userId: String): Boolean = + filters.all { it.invoke(userId) } + } + + @JvmStatic + fun or(vararg filters: SelectUserId) = object : SelectUserId() { + override fun invoke(userId: String): Boolean = + filters.any { it.invoke(userId) } + } + + @JvmStatic + fun not(filter: SelectUserId) = object : SelectUserId() { + override fun invoke(userId: String): Boolean = + !filter.invoke(userId) + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/selection/userid/SelectUserIdTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/selection/userid/SelectUserIdTest.java index 3a49247c..99a0c87c 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/selection/userid/SelectUserIdTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/selection/userid/SelectUserIdTest.java @@ -4,6 +4,15 @@ package org.pgpainless.util.selection.userid; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.junit.jupiter.api.Test; @@ -11,20 +20,10 @@ import org.pgpainless.PGPainless; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.util.UserId; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - public class SelectUserIdTest { @Test - public void testSelectUserIds() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + public void testSelectUserIds() throws PGPException { PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() .simpleEcKeyRing(""); secretKeys = PGPainless.modifyKeyRing(secretKeys) @@ -34,48 +33,53 @@ public class SelectUserIdTest { SecretKeyRingProtector.unprotectedKeys()) .done(); - List validEmail = SelectUserId.and( + List userIds = PGPainless.inspectKeyRing(secretKeys).getValidUserIds(); + List validEmail = userIds.stream().filter(SelectUserId.and( SelectUserId.validUserId(secretKeys), SelectUserId.containsEmailAddress("alice@wonderland.lit") - ).selectUserIds(secretKeys); + )).collect(Collectors.toList()); assertEquals(Collections.singletonList(""), validEmail); - List startsWithAlice = SelectUserId.startsWith("Alice").selectUserIds(secretKeys); + List startsWithAlice = userIds.stream().filter(SelectUserId.startsWith("Alice")).collect(Collectors.toList()); assertEquals(Collections.singletonList("Alice Liddell "), startsWithAlice); - List exactMatch = SelectUserId.or( + List exactMatch = userIds.stream().filter(SelectUserId.or( SelectUserId.exactMatch(""), SelectUserId.startsWith("Not Found") - ).selectUserIds(secretKeys); + )).collect(Collectors.toList()); assertEquals(Collections.singletonList(""), exactMatch); } @Test - public void testContainsSubstring() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + public void testContainsSubstring() throws PGPException { PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("wine drinker"); secretKeys = PGPainless.modifyKeyRing(secretKeys) .addUserId("this is not a quine", SecretKeyRingProtector.unprotectedKeys()) .addUserId("this is not a crime", SecretKeyRingProtector.unprotectedKeys()) .done(); - List containSubstring = SelectUserId.containsSubstring("ine") - .selectUserIds(secretKeys); + List userIds = PGPainless.inspectKeyRing(secretKeys).getValidUserIds(); + + List containSubstring = userIds.stream().filter(SelectUserId.containsSubstring("ine")).collect(Collectors.toList()); assertEquals(Arrays.asList("wine drinker", "this is not a quine"), containSubstring); } @Test - public void testContainsEmailAddress() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + public void testContainsEmailAddress() { PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("Alice "); + List userIds = PGPainless.inspectKeyRing(secretKeys).getValidUserIds(); - assertEquals("Alice ", SelectUserId.containsEmailAddress("alice@wonderland.lit").firstMatch(secretKeys)); - assertEquals("Alice ", SelectUserId.containsEmailAddress("").firstMatch(secretKeys)); + assertEquals("Alice ", userIds.stream().filter( + SelectUserId.containsEmailAddress("alice@wonderland.lit")).findFirst().get()); + assertEquals("Alice ", userIds.stream().filter( + SelectUserId.containsEmailAddress("")).findFirst().get()); - assertNull(SelectUserId.containsEmailAddress("mad@hatter.lit").firstMatch(secretKeys)); + assertFalse(userIds.stream().anyMatch(SelectUserId.containsEmailAddress("mad@hatter.lit"))); } @Test - public void testAndOrNot() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + public void testAndOrNot() throws PGPException { PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("Alice "); secretKeys = PGPainless.modifyKeyRing(secretKeys) .addUserId("Alice ", SecretKeyRingProtector.unprotectedKeys()) @@ -83,34 +87,32 @@ public class SelectUserIdTest { .addUserId("Crazy Girl ", SecretKeyRingProtector.unprotectedKeys()) .done(); - List or = SelectUserId.or( + List userIds = PGPainless.inspectKeyRing(secretKeys).getValidUserIds(); + + List or = userIds.stream().filter(SelectUserId.or( SelectUserId.containsEmailAddress("alice@wonderland.lit"), - SelectUserId.startsWith("Alice")) - .selectUserIds(secretKeys); + SelectUserId.startsWith("Alice"))).collect(Collectors.toList()); assertEquals(Arrays.asList("Alice ", "Alice ", "Crazy Girl "), or); - List and = SelectUserId.and( + List and = userIds.stream().filter(SelectUserId.and( SelectUserId.containsEmailAddress("alice@wonderland.lit"), - SelectUserId.startsWith("Alice")) - .selectUserIds(secretKeys); + SelectUserId.startsWith("Alice"))).collect(Collectors.toList()); assertEquals(Collections.singletonList("Alice "), and); - List not = SelectUserId.not( - SelectUserId.startsWith("Alice")) - .selectUserIds(secretKeys); + List not = userIds.stream().filter(SelectUserId.not( + SelectUserId.startsWith("Alice"))).collect(Collectors.toList()); assertEquals(Arrays.asList("", "Crazy Girl "), not); } @Test - public void testFirstMatch() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + public void testFirstMatch() throws PGPException { PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("First UserID"); secretKeys = PGPainless.modifyKeyRing(secretKeys) .addUserId("Second UserID", SecretKeyRingProtector.unprotectedKeys()) .done(); - assertEquals("First UserID", SelectUserId.validUserId(secretKeys).firstMatch(secretKeys)); - assertEquals("Second UserID", SelectUserId.containsSubstring("Second").firstMatch( - PGPainless.inspectKeyRing(secretKeys).getUserIds() - )); + List userIds = PGPainless.inspectKeyRing(secretKeys).getValidUserIds(); + assertEquals("First UserID", userIds.stream().filter(SelectUserId.validUserId(secretKeys)).findFirst().get()); + assertEquals("Second UserID", userIds.stream().filter(SelectUserId.containsSubstring("Second")).findFirst().get()); } @Test From 4c237d55ed8743bb8c5767df6baff6290d30055c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Sep 2023 11:29:54 +0200 Subject: [PATCH 129/155] Add note about deprecation to BaseSignatureSubpackets --- .../signature/subpackets/BaseSignatureSubpackets.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.java index 28e99b9e..09fe1a99 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.java @@ -35,6 +35,15 @@ public interface BaseSignatureSubpackets { } + /** + * Add both an {@link IssuerKeyID} and {@link IssuerFingerprint} subpacket pointing to the given key. + * + * @param key key + * @return this + * + * @deprecated this method MUST NOT be used for OpenPGP v6, since v6 signatures MUST NOT contain any + * {@link IssuerKeyID} packets. + */ BaseSignatureSubpackets setIssuerFingerprintAndKeyId(PGPPublicKey key); BaseSignatureSubpackets setIssuerKeyId(long keyId); From 9a917f7fdba603224ee9bbae2b4369224e612ee2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Sep 2023 11:53:30 +0200 Subject: [PATCH 130/155] Kotlin conversion: DateUtil --- .../java/org/pgpainless/util/DateUtil.java | 75 ------------------- .../src/main/kotlin/openpgp/DateExtensions.kt | 61 +++++++++++---- .../kotlin/org/pgpainless/util/DateUtil.kt | 50 +++++++++++++ 3 files changed, 97 insertions(+), 89 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/DateUtil.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/DateUtil.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/DateUtil.java b/pgpainless-core/src/main/java/org/pgpainless/util/DateUtil.java deleted file mode 100644 index f56020d4..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/DateUtil.java +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import javax.annotation.Nonnull; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -public final class DateUtil { - - private DateUtil() { - - } - - // Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every invocation. - @Nonnull - public static SimpleDateFormat getParser() { - SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); - parser.setTimeZone(TimeZone.getTimeZone("UTC")); - return parser; - } - - /** - * Parse a UTC timestamp into a date. - * - * @param dateString timestamp - * @return date - */ - @Nonnull - public static Date parseUTCDate(@Nonnull String dateString) { - try { - return getParser().parse(dateString); - } catch (ParseException e) { - throw new IllegalArgumentException("Malformed UTC timestamp: " + dateString, e); - } - } - - /** - * Format a date as UTC timestamp. - * - * @param date date - * @return timestamp - */ - @Nonnull - public static String formatUTCDate(Date date) { - return getParser().format(date); - } - - /** - * Floor a date down to seconds precision. - * @param date date - * @return floored date - */ - @Nonnull - public static Date toSecondsPrecision(@Nonnull Date date) { - long millis = date.getTime(); - long seconds = millis / 1000; - long floored = seconds * 1000; - return new Date(floored); - } - - /** - * Return the current date "floored" to UTC precision. - * - * @return now - */ - @Nonnull - public static Date now() { - return toSecondsPrecision(new Date()); - } -} diff --git a/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt b/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt index db98fb44..5972cd3a 100644 --- a/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt +++ b/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt @@ -4,20 +4,53 @@ package openpgp +import java.text.ParseException +import java.text.SimpleDateFormat import java.util.* - /** - * Return a new date which represents this date plus the given amount of seconds added. - * - * Since '0' is a special date value in the OpenPGP specification - * (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0. - * - * @param date date - * @param seconds number of seconds to be added - * @return date plus seconds or null if seconds is '0' - */ - fun Date.plusSeconds(seconds: Long): Date? { - require(Long.MAX_VALUE - time > seconds) { "Adding $seconds seconds to this date would cause time to overflow." } - return if (seconds == 0L) null - else Date(this.time + 1000 * seconds) +/** + * Return a new date which represents this date plus the given amount of seconds added. + * + * Since '0' is a special date value in the OpenPGP specification + * (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0. + * + * @param date date + * @param seconds number of seconds to be added + * @return date plus seconds or null if seconds is '0' + */ +fun Date.plusSeconds(seconds: Long): Date? { + require(Long.MAX_VALUE - time > seconds) { "Adding $seconds seconds to this date would cause time to overflow." } + return if (seconds == 0L) null + else Date(this.time + 1000 * seconds) +} + +/** + * Return a new [Date] instance with this instance's time floored down to seconds precision. + */ +fun Date.toSecondsPrecision(): Date { + return Date((time / 1000) * 1000) +} + +internal val parser: SimpleDateFormat + // Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every invocation. + get() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss z") + .apply { timeZone = TimeZone.getTimeZone("UTC") } + +/** + * Format a date as UTC timestamp. + * + * @return timestamp + */ +fun Date.formatUTC(): String = parser.format(this) + +/** + * Parse a UTC timestamp into a date. + * @return date + */ +fun String.parseUTC(): Date { + return try { + parser.parse(this) + } catch (e : ParseException) { + throw IllegalArgumentException("Malformed UTC timestamp: $this", e) } +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/DateUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/DateUtil.kt new file mode 100644 index 00000000..de2052d6 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/DateUtil.kt @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +import openpgp.formatUTC +import openpgp.parseUTC +import openpgp.toSecondsPrecision +import java.util.* + +class DateUtil { + + companion object { + + /** + * Parse a UTC timestamp into a date. + * + * @param dateString timestamp + * @return date + */ + @JvmStatic + fun parseUTCDate(dateString: String): Date = dateString.parseUTC() + + /** + * Format a date as UTC timestamp. + * + * @param date date + * @return timestamp + */ + @JvmStatic + fun formatUTCDate(date: Date): String = date.formatUTC() + + /** + * Floor a date down to seconds precision. + * @param date date + * @return floored date + */ + @JvmStatic + fun toSecondsPrecision(date: Date): Date = date.toSecondsPrecision() + + /** + * Return the current date "floored" to UTC precision. + * + * @return now + */ + @JvmStatic + fun now() = toSecondsPrecision(Date()) + } +} \ No newline at end of file From b324742a623cbaa819b91af4de846ffb56dff1c1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Sep 2023 13:30:39 +0200 Subject: [PATCH 131/155] Kotlin conversion: ArmorUtils --- .../java/org/pgpainless/util/ArmorUtils.java | 603 ------------------ .../main/java/org/pgpainless/util/Tuple.java | 8 + .../kotlin/org/pgpainless/util/ArmorUtils.kt | 422 ++++++++++++ 3 files changed, 430 insertions(+), 603 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java deleted file mode 100644 index 976f6ab2..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java +++ /dev/null @@ -1,603 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.regex.Pattern; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; -import org.bouncycastle.util.io.Streams; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.decryption_verification.OpenPgpInputStream; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.util.KeyRingUtils; - -/** - * Utility class for dealing with ASCII armored OpenPGP data. - */ -public final class ArmorUtils { - - // MessageIDs are 32 printable characters - private static final Pattern PATTERN_MESSAGE_ID = Pattern.compile("^\\S{32}$"); - - /** - * Constant armor key for comments. - */ - public static final String HEADER_COMMENT = "Comment"; - /** - * Constant armor key for program versions. - */ - public static final String HEADER_VERSION = "Version"; - /** - * Constant armor key for message IDs. Useful for split messages. - */ - public static final String HEADER_MESSAGEID = "MessageID"; - /** - * Constant armor key for used hash algorithms in clearsigned messages. - */ - public static final String HEADER_HASH = "Hash"; - /** - * Constant armor key for message character sets. - */ - public static final String HEADER_CHARSET = "Charset"; - - private ArmorUtils() { - - } - - /** - * Return the ASCII armored encoding of the given {@link PGPSecretKey}. - * - * @param secretKey secret key - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull PGPSecretKey secretKey) - throws IOException { - MultiMap header = keyToHeader(secretKey.getPublicKey()); - return toAsciiArmoredString(secretKey.getEncoded(), header); - } - - /** - * Return the ASCII armored encoding of the given {@link PGPPublicKey}. - * - * @param publicKey public key - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull PGPPublicKey publicKey) - throws IOException { - MultiMap header = keyToHeader(publicKey); - return toAsciiArmoredString(publicKey.getEncoded(), header); - } - - /** - * Return the ASCII armored encoding of the given {@link PGPSecretKeyRing}. - * - * @param secretKeys secret key ring - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull PGPSecretKeyRing secretKeys) - throws IOException { - MultiMap header = keysToHeader(secretKeys); - return toAsciiArmoredString(secretKeys.getEncoded(), header); - } - - /** - * Return the ASCII armored encoding of the given {@link PGPPublicKeyRing}. - * - * @param publicKeys public key ring - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull PGPPublicKeyRing publicKeys) - throws IOException { - MultiMap header = keysToHeader(publicKeys); - return toAsciiArmoredString(publicKeys.getEncoded(), header); - } - - /** - * Return the ASCII armored encoding of the given {@link PGPSecretKeyRingCollection}. - * The encoding will use per-key ASCII armors protecting each {@link PGPSecretKeyRing} individually. - * Those armors are then concatenated with newlines in between. - * - * @param secretKeyRings secret key ring collection - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull PGPSecretKeyRingCollection secretKeyRings) - throws IOException { - StringBuilder sb = new StringBuilder(); - for (Iterator iterator = secretKeyRings.iterator(); iterator.hasNext(); ) { - PGPSecretKeyRing secretKeyRing = iterator.next(); - sb.append(toAsciiArmoredString(secretKeyRing)); - if (iterator.hasNext()) { - sb.append('\n'); - } - } - return sb.toString(); - } - - /** - * Return the ASCII armored encoding of the given {@link PGPPublicKeyRingCollection}. - * The encoding will use per-key ASCII armors protecting each {@link PGPPublicKeyRing} individually. - * Those armors are then concatenated with newlines in between. - * - * @param publicKeyRings public key ring collection - * @return ascii armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull PGPPublicKeyRingCollection publicKeyRings) - throws IOException { - StringBuilder sb = new StringBuilder(); - for (Iterator iterator = publicKeyRings.iterator(); iterator.hasNext(); ) { - PGPPublicKeyRing publicKeyRing = iterator.next(); - sb.append(toAsciiArmoredString(publicKeyRing)); - if (iterator.hasNext()) { - sb.append('\n'); - } - } - return sb.toString(); - } - - /** - * Return the ASCII armored representation of the given detached signature. - * The signature will not be stripped of non-exportable subpackets or trust-packets. - * If you need to strip those (e.g. because the signature is intended to be sent to a third party), use - * {@link #toAsciiArmoredString(PGPSignature, boolean)} and provide
true
as boolean value. - * - * @param signature signature - * @return ascii armored string - * - * @throws IOException in case of an error in the {@link ArmoredOutputStream} - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull PGPSignature signature) throws IOException { - return toAsciiArmoredString(signature, false); - } - - /** - * Return the ASCII armored representation of the given detached signature. - * If
export
is true, the signature will be stripped of non-exportable subpackets or trust-packets. - * If it is
false
, the signature will be encoded as-is. - * - * @param signature signature - * @param export whether to exclude non-exportable subpackets or trust-packets. - * @return ascii armored string - * - * @throws IOException in case of an error in the {@link ArmoredOutputStream} - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull PGPSignature signature, boolean export) - throws IOException { - return toAsciiArmoredString(signature.getEncoded(export)); - } - - /** - * Return the ASCII armored encoding of the given OpenPGP data bytes. - * - * @param bytes openpgp data - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull byte[] bytes) - throws IOException { - return toAsciiArmoredString(bytes, null); - } - - /** - * Return the ASCII armored encoding of the given OpenPGP data bytes. - * The ASCII armor will include headers from the header map. - * - * @param bytes OpenPGP data - * @param additionalHeaderValues header map - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull byte[] bytes, - @Nullable MultiMap additionalHeaderValues) - throws IOException { - return toAsciiArmoredString(new ByteArrayInputStream(bytes), additionalHeaderValues); - } - - /** - * Return the ASCII armored encoding of the {@link InputStream} containing OpenPGP data. - * - * @param inputStream input stream of OpenPGP data - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull InputStream inputStream) - throws IOException { - return toAsciiArmoredString(inputStream, null); - } - - /** - * Return the ASCII armored encoding of the OpenPGP data from the given {@link InputStream}. - * The ASCII armor will include armor headers from the given header map. - * - * @param inputStream input stream of OpenPGP data - * @param additionalHeaderValues ASCII armor header map - * @return ASCII armored encoding - * - * @throws IOException in case of an io error - */ - @Nonnull - public static String toAsciiArmoredString(@Nonnull InputStream inputStream, - @Nullable MultiMap additionalHeaderValues) - throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ArmoredOutputStream armor = toAsciiArmoredStream(out, additionalHeaderValues); - Streams.pipeAll(inputStream, armor); - armor.close(); - - return out.toString(); - } - - /** - * Return an {@link ArmoredOutputStream} prepared with headers for the given key ring, which wraps the given - * {@link OutputStream}. - * - * The armored output stream can be used to encode the key ring by calling {@link PGPKeyRing#encode(OutputStream)} - * with the armored output stream as an argument. - * - * @param keyRing key ring - * @param outputStream wrapped output stream - * @return armored output stream - */ - @Nonnull - public static ArmoredOutputStream toAsciiArmoredStream(@Nonnull PGPKeyRing keyRing, - @Nonnull OutputStream outputStream) { - MultiMap header = keysToHeader(keyRing); - return toAsciiArmoredStream(outputStream, header); - } - - /** - * Create an {@link ArmoredOutputStream} wrapping the given {@link OutputStream}. - * The armored output stream will be prepared with armor headers given by header. - * - * Note: Since the armored output stream is retrieved from {@link ArmoredOutputStreamFactory#get(OutputStream)}, - * it may already come with custom headers. Hence, the header entries given by header are appended below those - * already populated headers. - * - * @param outputStream output stream to wrap - * @param header map of header entries - * @return armored output stream - */ - @Nonnull - public static ArmoredOutputStream toAsciiArmoredStream(@Nonnull OutputStream outputStream, - @Nullable MultiMap header) { - ArmoredOutputStream armoredOutputStream = ArmoredOutputStreamFactory.get(outputStream); - if (header != null) { - for (String headerKey : header.keySet()) { - for (String headerValue : header.get(headerKey)) { - armoredOutputStream.addHeader(headerKey, headerValue); - } - } - } - return armoredOutputStream; - } - - /** - * Generate a header map for ASCII armor from the given {@link PGPKeyRing}. - * - * @param keyRing key ring - * @return header map - */ - @Nonnull - private static MultiMap keysToHeader(@Nonnull PGPKeyRing keyRing) { - PGPPublicKey publicKey = keyRing.getPublicKey(); - return keyToHeader(publicKey); - } - - /** - * Generate a header map for ASCII armor from the given {@link PGPPublicKey}. - * The header map consists of a comment field of the keys pretty-printed fingerprint, - * as well as some optional user-id information (see {@link #setUserIdInfoOnHeader(MultiMap, PGPPublicKey)}. - * - * @param publicKey public key - * @return header map - */ - @Nonnull - private static MultiMap keyToHeader(@Nonnull PGPPublicKey publicKey) { - MultiMap header = new MultiMap<>(); - OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(publicKey); - - header.put(HEADER_COMMENT, fingerprint.prettyPrint()); - setUserIdInfoOnHeader(header, publicKey); - return header; - } - - /** - * Add user-id information to the header map. - * If the key is carrying at least one user-id, we add a comment for the probable primary user-id. - * If the key carries more than one user-id, we further add a comment stating how many further identities - * the key has. - * - * @param header header map - * @param publicKey public key - */ - private static void setUserIdInfoOnHeader(@Nonnull MultiMap header, - @Nonnull PGPPublicKey publicKey) { - Tuple idCount = getPrimaryUserIdAndUserIdCount(publicKey); - String primary = idCount.getA(); - int totalCount = idCount.getB(); - if (primary != null) { - header.put(HEADER_COMMENT, primary); - } - if (totalCount == 2) { - header.put(HEADER_COMMENT, "1 further identity"); - } else if (totalCount > 2) { - header.put(HEADER_COMMENT, String.format("%d further identities", totalCount - 1)); - } - } - - /** - * Determine a probable primary user-id, as well as the total number of user-ids on the given {@link PGPPublicKey}. - * This method is trimmed for efficiency and does not do any cryptographic validation of signatures. - * - * The key might not have any user-id at all, in which case {@link Tuple#getA()} will return null. - * The key might have some user-ids, but none of it marked as primary, in which case {@link Tuple#getA()} - * will return the first user-id of the key. - * - * @param publicKey public key - * @return tuple consisting of a primary user-id candidate, and the total number of user-ids on the key. - */ - @Nonnull - private static Tuple getPrimaryUserIdAndUserIdCount(@Nonnull PGPPublicKey publicKey) { - // Quickly determine the primary user-id + number of total user-ids - // NOTE: THIS METHOD DOES NOT CRYPTOGRAPHICALLY VERIFY THE SIGNATURES - // DO NOT RELY ON IT! - List userIds = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey); - int countIdentities = 0; - String first = null; - String primary = null; - for (String userId : userIds) { - countIdentities++; - // remember the first user-id - if (first == null) { - first = userId; - } - - if (primary == null) { - Iterator signatures = publicKey.getSignaturesForID(userId); - while (signatures.hasNext()) { - PGPSignature signature = signatures.next(); - if (signature.getHashedSubPackets().isPrimaryUserID()) { - primary = userId; - break; - } - } - } - } - // It may happen that no user-id is marked as primary - // in that case print the first one - String printed = primary != null ? primary : first; - return new Tuple<>(printed, countIdentities); - } - - /** - * Set the version header entry in the ASCII armor. - * If the version info is null or only contains whitespace characters, then the version header will be removed. - * - * @param armor armored output stream - * @param version version header. - */ - public static void setVersionHeader(@Nonnull ArmoredOutputStream armor, - @Nullable String version) { - if (version == null || version.trim().isEmpty()) { - armor.setHeader(HEADER_VERSION, null); - } else { - armor.setHeader(HEADER_VERSION, version); - } - } - - /** - * Add an ASCII armor header entry about the used hash algorithm into the {@link ArmoredOutputStream}. - * - * @param armor armored output stream - * @param hashAlgorithm hash algorithm - * - * @see - * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor - */ - public static void addHashAlgorithmHeader(@Nonnull ArmoredOutputStream armor, - @Nonnull HashAlgorithm hashAlgorithm) { - armor.addHeader(HEADER_HASH, hashAlgorithm.getAlgorithmName()); - } - - /** - * Add an ASCII armor comment header entry into the {@link ArmoredOutputStream}. - * - * @param armor armored output stream - * @param comment free-text comment - * - * @see - * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor - */ - public static void addCommentHeader(@Nonnull ArmoredOutputStream armor, - @Nonnull String comment) { - armor.addHeader(HEADER_COMMENT, comment); - } - - /** - * Add an ASCII armor message-id header entry into the {@link ArmoredOutputStream}. - * - * @param armor armored output stream - * @param messageId message id - * - * @see - * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor - */ - public static void addMessageIdHeader(@Nonnull ArmoredOutputStream armor, - @Nonnull String messageId) { - if (!PATTERN_MESSAGE_ID.matcher(messageId).matches()) { - throw new IllegalArgumentException("MessageIDs MUST consist of 32 printable characters."); - } - armor.addHeader(HEADER_MESSAGEID, messageId); - } - - /** - * Extract all ASCII armor header values of type comment from the given {@link ArmoredInputStream}. - * - * @param armor armored input stream - * @return list of comment headers - */ - @Nonnull - public static List getCommentHeaderValues(@Nonnull ArmoredInputStream armor) { - return getArmorHeaderValues(armor, HEADER_COMMENT); - } - - /** - * Extract all ASCII armor header values of type message id from the given {@link ArmoredInputStream}. - * - * @param armor armored input stream - * @return list of message-id headers - */ - @Nonnull - public static List getMessageIdHeaderValues(@Nonnull ArmoredInputStream armor) { - return getArmorHeaderValues(armor, HEADER_MESSAGEID); - } - - /** - * Return all ASCII armor header values of type hash-algorithm from the given {@link ArmoredInputStream}. - * - * @param armor armored input stream - * @return list of hash headers - */ - @Nonnull - public static List getHashHeaderValues(@Nonnull ArmoredInputStream armor) { - return getArmorHeaderValues(armor, HEADER_HASH); - } - - /** - * Return a list of {@link HashAlgorithm} enums extracted from the hash header entries of the given - * {@link ArmoredInputStream}. - * - * @param armor armored input stream - * @return list of hash algorithms from the ASCII header - */ - @Nonnull - public static List getHashAlgorithms(@Nonnull ArmoredInputStream armor) { - List algorithmNames = getHashHeaderValues(armor); - List algorithms = new ArrayList<>(); - for (String name : algorithmNames) { - HashAlgorithm algorithm = HashAlgorithm.fromName(name); - if (algorithm != null) { - algorithms.add(algorithm); - } - } - return algorithms; - } - - /** - * Return all ASCII armor header values of type version from the given {@link ArmoredInputStream}. - * - * @param armor armored input stream - * @return list of version headers - */ - @Nonnull - public static List getVersionHeaderValues(@Nonnull ArmoredInputStream armor) { - return getArmorHeaderValues(armor, HEADER_VERSION); - } - - /** - * Return all ASCII armor header values of type charset from the given {@link ArmoredInputStream}. - * - * @param armor armored input stream - * @return list of charset headers - */ - @Nonnull - public static List getCharsetHeaderValues(@Nonnull ArmoredInputStream armor) { - return getArmorHeaderValues(armor, HEADER_CHARSET); - } - - /** - * Return all ASCII armor header values of the given headerKey from the given {@link ArmoredInputStream}. - * - * @param armor armored input stream - * @param headerKey ASCII armor header key - * @return list of values for the header key - */ - @Nonnull - public static List getArmorHeaderValues(@Nonnull ArmoredInputStream armor, - @Nonnull String headerKey) { - String[] header = armor.getArmorHeaders(); - String key = headerKey + ": "; - List values = new ArrayList<>(); - for (String line : header) { - if (line.startsWith(key)) { - values.add(line.substring(key.length())); - } - } - return values; - } - - /** - * Hacky workaround for #96. - * For {@link PGPPublicKeyRingCollection#PGPPublicKeyRingCollection(InputStream, KeyFingerPrintCalculator)} - * or {@link PGPSecretKeyRingCollection#PGPSecretKeyRingCollection(InputStream, KeyFingerPrintCalculator)} - * to read all PGPKeyRings properly, we apparently have to make sure that the {@link InputStream} that is given - * as constructor argument is a PGPUtil.BufferedInputStreamExt. - * Since {@link PGPUtil#getDecoderStream(InputStream)} will return an {@link org.bouncycastle.bcpg.ArmoredInputStream} - * if the underlying input stream contains armored data, we first dearmor the data ourselves to make sure that the - * end-result is a PGPUtil.BufferedInputStreamExt. - * - * @param inputStream input stream - * @return BufferedInputStreamExt - * - * @throws IOException in case of an IO error - */ - @Nonnull - public static InputStream getDecoderStream(@Nonnull InputStream inputStream) - throws IOException { - OpenPgpInputStream openPgpIn = new OpenPgpInputStream(inputStream); - if (openPgpIn.isAsciiArmored()) { - ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(openPgpIn); - return PGPUtil.getDecoderStream(armorIn); - } - - return openPgpIn; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java b/pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java index 27ad6a12..84d7a370 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java @@ -4,6 +4,14 @@ package org.pgpainless.util; +/** + * Helper class pairing together two values. + * @param type of the first value + * @param type of the second value + * @deprecated Scheduled for removal. + * TODO: Remove + */ +@Deprecated public class Tuple { private final A a; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt new file mode 100644 index 00000000..c8ff4da8 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt @@ -0,0 +1,422 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0package org.pgpainless.util + +package org.pgpainless.util + +import org.bouncycastle.bcpg.ArmoredInputStream +import org.bouncycastle.bcpg.ArmoredOutputStream +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection +import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.PGPUtil +import org.bouncycastle.util.io.Streams +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.decryption_verification.OpenPgpInputStream +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.util.KeyRingUtils +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +class ArmorUtils { + + companion object { + // MessageIDs are 32 printable characters + private val PATTER_MESSAGE_ID = "^\\S{32}$".toRegex() + /** + * Constant armor key for comments. + */ + const val HEADER_COMMENT = "Comment" + /** + * Constant armor key for program versions. + */ + const val HEADER_VERSION = "Version" + /** + * Constant armor key for message IDs. Useful for split messages. + */ + const val HEADER_MESSAGEID = "MessageID" + /** + * Constant armor key for used hash algorithms in clearsigned messages. + */ + const val HEADER_HASH = "Hash" + /** + * Constant armor key for message character sets. + */ + const val HEADER_CHARSET = "Charset" + + /** + * Return the ASCII armored encoding of the given [PGPSecretKey]. + * + * @param secretKey secret key + * @return ASCII armored encoding + * + * @throws IOException in case of an io error + */ + @JvmStatic + @Throws(IOException::class) + fun toAsciiArmoredString(secretKey: PGPSecretKey): String = + toAsciiArmoredString(secretKey.encoded, keyToHeader(secretKey.publicKey)) + + /** + * Return the ASCII armored encoding of the given [PGPPublicKey]. + * + * @param publicKey public key + * @return ASCII armored encoding + * + * @throws IOException in case of an io error + */ + @JvmStatic + @Throws(IOException::class) + fun toAsciiArmoredString(publicKey: PGPPublicKey): String = + toAsciiArmoredString(publicKey.encoded, keyToHeader(publicKey)) + + /** + * Return the ASCII armored encoding of the given [PGPSecretKeyRing]. + * + * @param secretKeys secret key ring + * @return ASCII armored encoding + * + * @throws IOException in case of an io error + */ + @JvmStatic + @Throws(IOException::class) + fun toAsciiArmoredString(secretKeys: PGPSecretKeyRing): String = + toAsciiArmoredString(secretKeys.encoded, keyToHeader(secretKeys.publicKey)) + + /** + * Return the ASCII armored encoding of the given [PGPPublicKeyRing]. + * + * @param certificate public key ring + * @return ASCII armored encoding + * + * @throws IOException in case of an io error + */ + @JvmStatic + @Throws(IOException::class) + fun toAsciiArmoredString(certificate: PGPPublicKeyRing): String = + toAsciiArmoredString(certificate.encoded, keyToHeader(certificate.publicKey)) + + /** + * Return the ASCII armored encoding of the given [PGPSecretKeyRingCollection]. + * The encoding will use per-key ASCII armors protecting each [PGPSecretKeyRing] individually. + * Those armors are then concatenated with newlines in between. + * + * @param secretKeysCollection secret key ring collection + * @return ASCII armored encoding + * + * @throws IOException in case of an io error + */ + @JvmStatic + @Throws(IOException::class) + fun toAsciiArmoredString(secretKeysCollection: PGPSecretKeyRingCollection): String = + secretKeysCollection.keyRings.asSequence() + .joinToString("\n") { toAsciiArmoredString(it) } + + /** + * Return the ASCII armored encoding of the given [PGPPublicKeyRingCollection]. + * The encoding will use per-key ASCII armors protecting each [PGPPublicKeyRing] individually. + * Those armors are then concatenated with newlines in between. + * + * @param certificates public key ring collection + * @return ascii armored encoding + * + * @throws IOException in case of an io error + */ + @JvmStatic + @Throws(IOException::class) + fun toAsciiArmoredString(certificates: PGPPublicKeyRingCollection): String = + certificates.joinToString("\n") { toAsciiArmoredString(it) } + + /** + * Return the ASCII armored representation of the given detached signature. + * If [export] is true, the signature will be stripped of non-exportable subpackets or trust-packets. + * If it is false, the signature will be encoded as-is. + * + * @param signature signature + * @param export whether to exclude non-exportable subpackets or trust-packets. + * @return ascii armored string + * + * @throws IOException in case of an error in the [ArmoredOutputStream] + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun toAsciiArmoredString(signature: PGPSignature, export: Boolean = false): String = + toAsciiArmoredString(signature.getEncoded(export)) + + /** + * Return the ASCII armored encoding of the given OpenPGP data bytes. + * The ASCII armor will include headers from the header map. + * + * @param bytes OpenPGP data + * @param header header map + * @return ASCII armored encoding + * + * @throws IOException in case of an io error + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun toAsciiArmoredString(bytes: ByteArray, header: Map>? = null): String = + toAsciiArmoredString(bytes.inputStream(), header) + + /** + * Return the ASCII armored encoding of the OpenPGP data from the given {@link InputStream}. + * The ASCII armor will include armor headers from the given header map. + * + * @param inputStream input stream of OpenPGP data + * @param header ASCII armor header map + * @return ASCII armored encoding + * + * @throws IOException in case of an io error + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun toAsciiArmoredString(inputStream: InputStream, header: Map>? = null): String = + ByteArrayOutputStream().apply { + toAsciiArmoredStream(this, header).run { + Streams.pipeAll(inputStream, this) + this.close() + } + }.toString() + + /** + * Return an [ArmoredOutputStream] prepared with headers for the given key ring, which wraps the given + * {@link OutputStream}. + * + * The armored output stream can be used to encode the key ring by calling [PGPKeyRing.encode] + * with the armored output stream as an argument. + * + * @param keys OpenPGP key or certificate + * @param outputStream wrapped output stream + * @return armored output stream + */ + @JvmStatic + @Throws(IOException::class) + fun toAsciiArmoredStream(keys: PGPKeyRing, outputStream: OutputStream): ArmoredOutputStream = + toAsciiArmoredStream(outputStream, keyToHeader(keys.publicKey)) + + /** + * Create an [ArmoredOutputStream] wrapping the given [OutputStream]. + * The armored output stream will be prepared with armor headers given by header. + * + * Note: Since the armored output stream is retrieved from [ArmoredOutputStreamFactory.get], + * it may already come with custom headers. Hence, the header entries given by header are appended below those + * already populated headers. + * + * @param outputStream output stream to wrap + * @param header map of header entries + * @return armored output stream + */ + @JvmStatic + @JvmOverloads + @Throws(IOException::class) + fun toAsciiArmoredStream(outputStream: OutputStream, header: Map>? = null): ArmoredOutputStream = + ArmoredOutputStreamFactory.get(outputStream).apply { + header?.forEach { entry -> + entry.value.forEach { value -> + addHeader(entry.key, value) + } + } + } + + /** + * Generate a header map for ASCII armor from the given [PGPPublicKey]. + * The header map consists of a comment field of the keys pretty-printed fingerprint, + * as well as the primary or first user-id plus the count of remaining user-ids. + * + * @param publicKey public key + * @return header map + */ + @JvmStatic + private fun keyToHeader(publicKey: PGPPublicKey): Map> { + val headerMap = mutableMapOf>() + val userIds = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey) + val first: String? = userIds.firstOrNull() + val primary: String? = userIds.firstOrNull { + publicKey.getSignaturesForID(it)?.asSequence()?.any { sig -> + sig.hashedSubPackets.isPrimaryUserID + } ?: false + } + + // Fingerprint + headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add(OpenPgpFingerprint.of(publicKey).prettyPrint()) + // Primary / First User ID + (primary ?: first)?.let { headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add(it) } + // X-1 further identities + when (userIds.size) { + 0, 1 -> {} + 2 -> headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add("1 further identity") + else -> headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add("${userIds.size - 1} further identities") + } + return headerMap + } + + /** + * Set the version header entry in the ASCII armor. + * If the version info is null or only contains whitespace characters, then the version header will be removed. + * + * @param armor armored output stream + * @param version version header. + */ + @JvmStatic + @Deprecated("Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + + "Use ArmoredOutputStream builder instead.") + fun setVersionHeader(armor: ArmoredOutputStream, version: String?) = + armor.setHeader(HEADER_VERSION, version?.let { it.ifBlank { null } }) + + /** + * Add an ASCII armor header entry about the used hash algorithm into the [ArmoredOutputStream]. + * + * @param armor armored output stream + * @param hashAlgorithm hash algorithm + * + * @see + * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor + */ + @JvmStatic + @Deprecated("Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + + "Use ArmoredOutputStream builder instead.") + fun addHashAlgorithmHeader(armor: ArmoredOutputStream, hashAlgorithm: HashAlgorithm) = + armor.addHeader(HEADER_HASH, hashAlgorithm.algorithmName) + + /** + * Add an ASCII armor comment header entry into the [ArmoredOutputStream]. + * + * @param armor armored output stream + * @param comment free-text comment + * + * @see + * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor + */ + @JvmStatic + @Deprecated("Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + + "Use ArmoredOutputStream builder instead.") + fun addCommentHeader(armor: ArmoredOutputStream, comment: String) = + armor.addHeader(HEADER_COMMENT, comment) + + /** + * Add an ASCII armor message-id header entry into the [ArmoredOutputStream]. + * + * @param armor armored output stream + * @param messageId message id + * + * @see + * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor + */ + @JvmStatic + @Deprecated("Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + + "Use ArmoredOutputStream builder instead.") + fun addMessageIdHeader(armor: ArmoredOutputStream, messageId: String) { + require(PATTER_MESSAGE_ID.matches(messageId)) { "MessageIDs MUST consist of 32 printable characters." } + armor.addHeader(HEADER_MESSAGEID, messageId) + } + + /** + * Extract all ASCII armor header values of type comment from the given [ArmoredInputStream]. + * + * @param armor armored input stream + * @return list of comment headers + */ + @JvmStatic + fun getCommentHeaderValues(armor: ArmoredInputStream): List = + getArmorHeaderValues(armor, HEADER_COMMENT) + + /** + * Extract all ASCII armor header values of type message id from the given [ArmoredInputStream]. + * + * @param armor armored input stream + * @return list of message-id headers + */ + @JvmStatic + fun getMessageIdHeaderValues(armor: ArmoredInputStream): List = + getArmorHeaderValues(armor, HEADER_MESSAGEID) + + /** + * Return all ASCII armor header values of type hash-algorithm from the given [ArmoredInputStream]. + * + * @param armor armored input stream + * @return list of hash headers + */ + @JvmStatic + fun getHashHeaderValues(armor: ArmoredInputStream): List = + getArmorHeaderValues(armor, HEADER_HASH) + + /** + * Return a list of [HashAlgorithm] enums extracted from the hash header entries of the given [ArmoredInputStream]. + * + * @param armor armored input stream + * @return list of hash algorithms from the ASCII header + */ + @JvmStatic + fun getHashAlgorithms(armor: ArmoredInputStream): List = + getHashHeaderValues(armor).mapNotNull { HashAlgorithm.fromName(it) } + + /** + * Return all ASCII armor header values of type version from the given [ArmoredInputStream]. + * + * @param armor armored input stream + * @return list of version headers + */ + @JvmStatic + fun getVersionHeaderValues(armor: ArmoredInputStream): List = + getArmorHeaderValues(armor, HEADER_VERSION) + + /** + * Return all ASCII armor header values of type charset from the given [ArmoredInputStream]. + * + * @param armor armored input stream + * @return list of charset headers + */ + @JvmStatic + fun getCharsetHeaderValues(armor: ArmoredInputStream): List = + getArmorHeaderValues(armor, HEADER_CHARSET) + + /** + * Return all ASCII armor header values of the given headerKey from the given [ArmoredInputStream]. + * + * @param armor armored input stream + * @param key ASCII armor header key + * @return list of values for the header key + */ + @JvmStatic + fun getArmorHeaderValues(armor: ArmoredInputStream, key: String): List = + armor.armorHeaders + .filter { it.startsWith("$key: ") } + .map { it.substring(key.length + 2) } // key.len + ": ".len + + /** + * Hacky workaround for #96. + * For `PGPPublicKeyRingCollection(InputStream, KeyFingerPrintCalculator)` + * or `PGPSecretKeyRingCollection(InputStream, KeyFingerPrintCalculator)` + * to read all PGPKeyRings properly, we apparently have to make sure that the [InputStream] that is given + * as constructor argument is a [PGPUtil.BufferedInputStreamExt]. + * Since [PGPUtil.getDecoderStream] will return an [org.bouncycastle.bcpg.ArmoredInputStream] + * if the underlying input stream contains armored data, we first dearmor the data ourselves to make sure that the + * end-result is a [PGPUtil.BufferedInputStreamExt]. + * + * @param inputStream input stream + * @return BufferedInputStreamExt + * + * @throws IOException in case of an IO error + */ + @JvmStatic + @Throws(IOException::class) + fun getDecoderStream(inputStream: InputStream): InputStream = + OpenPgpInputStream(inputStream).let { + if (it.isAsciiArmored) { + PGPUtil.getDecoderStream(ArmoredInputStreamFactory.get(it)) + } else { + it + } + } + } +} \ No newline at end of file From 841b386226f278fdde17deb219d022cd7f9bfee7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Sep 2023 14:36:04 +0200 Subject: [PATCH 132/155] Kotlin conversion: MultiMap Warning: This commit changes the semantics of MultiMap.put() put() now replaces values, while plus() adds them. --- .../java/org/pgpainless/util/MultiMap.java | 137 ------------------ .../PublicKeyRingSelectionStrategy.java | 7 +- .../SecretKeyRingSelectionStrategy.java | 7 +- .../kotlin/org/pgpainless/util/MultiMap.kt | 75 ++++++++++ .../org/pgpainless/util/MultiMapTest.java | 84 +++++++---- .../keyring/KeyRingsFromCollectionTest.java | 14 +- 6 files changed, 147 insertions(+), 177 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java b/pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java deleted file mode 100644 index 3b342778..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import javax.annotation.Nonnull; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -public class MultiMap { - - private final Map> map; - - public MultiMap() { - map = new HashMap<>(); - } - - public MultiMap(@Nonnull MultiMap other) { - this.map = new HashMap<>(); - for (K k : other.map.keySet()) { - map.put(k, new LinkedHashSet<>(other.map.get(k))); - } - } - - public MultiMap(@Nonnull Map> content) { - this.map = new HashMap<>(content); - } - - public int size() { - return map.size(); - } - - public boolean isEmpty() { - return map.isEmpty(); - } - - public boolean containsKey(K o) { - return map.containsKey(o); - } - - public boolean containsValue(V o) { - for (Set values : map.values()) { - if (values.contains(o)) return true; - } - return false; - } - - public Set get(K o) { - return map.get(o); - } - - public void put(K k, V v) { - Set values = map.get(k); - if (values == null) { - values = new LinkedHashSet<>(); - map.put(k, values); - } - values.add(v); - } - - public void put(K k, Set vs) { - for (V v : vs) { - put(k, v); - } - } - - public void removeAll(K o) { - map.remove(o); - } - - public void remove(K o, V v) { - Set vs = map.get(o); - if (vs == null) return; - vs.remove(v); - } - - public void putAll(MultiMap other) { - for (K key : other.keySet()) { - put(key, other.get(key)); - } - } - - public void clear() { - map.clear(); - } - - public Set keySet() { - return map.keySet(); - } - - public Collection> values() { - return map.values(); - } - - public Set>> entrySet() { - return map.entrySet(); - } - - /** - * Return all values of the {@link MultiMap} in a single {@link LinkedHashSet}. - * - * @return set of all values - */ - public Set flatten() { - LinkedHashSet flattened = new LinkedHashSet<>(); - for (Set items : map.values()) { - flattened.addAll(items); - } - return flattened; - } - - @Override - public boolean equals(Object o) { - if (o == null) { - return false; - } - - if (!(o instanceof MultiMap)) { - return false; - } - - if (this == o) { - return true; - } - - return map.equals(((MultiMap) o).map); - } - - @Override - public int hashCode() { - return map.hashCode(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/PublicKeyRingSelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/PublicKeyRingSelectionStrategy.java index 68c9d946..7dbf7c93 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/PublicKeyRingSelectionStrategy.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/PublicKeyRingSelectionStrategy.java @@ -7,6 +7,7 @@ package org.pgpainless.util.selection.keyring; import javax.annotation.Nonnull; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -33,9 +34,9 @@ public abstract class PublicKeyRingSelectionStrategy implements KeyRingSelect @Override public MultiMap selectKeyRingsFromCollections(@Nonnull MultiMap keyRingCollections) { MultiMap keyRings = new MultiMap<>(); - for (O identifier : keyRingCollections.keySet()) { - for (PGPPublicKeyRingCollection collection : keyRingCollections.get(identifier)) { - keyRings.put(identifier, selectKeyRingsFromCollection(identifier, collection)); + for (Map.Entry> entry : keyRingCollections.entrySet()) { + for (PGPPublicKeyRingCollection collection : entry.getValue()) { + keyRings.plus(entry.getKey(), selectKeyRingsFromCollection(entry.getKey(), collection)); } } return keyRings; diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/SecretKeyRingSelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/SecretKeyRingSelectionStrategy.java index ac5e8065..9e57b575 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/SecretKeyRingSelectionStrategy.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/SecretKeyRingSelectionStrategy.java @@ -6,6 +6,7 @@ package org.pgpainless.util.selection.keyring; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; @@ -32,9 +33,9 @@ public abstract class SecretKeyRingSelectionStrategy implements KeyRingSelect @Override public MultiMap selectKeyRingsFromCollections(@Nonnull MultiMap keyRingCollections) { MultiMap keyRings = new MultiMap<>(); - for (O identifier : keyRingCollections.keySet()) { - for (PGPSecretKeyRingCollection collection : keyRingCollections.get(identifier)) { - keyRings.put(identifier, selectKeyRingsFromCollection(identifier, collection)); + for (Map.Entry> entry : keyRingCollections.entrySet()) { + for (PGPSecretKeyRingCollection collection : entry.getValue()) { + keyRings.plus(entry.getKey(), selectKeyRingsFromCollection(entry.getKey(), collection)); } } return keyRings; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt new file mode 100644 index 00000000..9ca193a6 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +class MultiMap : Iterable>> { + + private val map: Map> + + constructor(): this(mutableMapOf()) + constructor(other: MultiMap): this(other.map) + constructor(content: Map>) { + map = mutableMapOf() + content.forEach { + map[it.key] = it.value.toMutableSet() + } + } + + override fun iterator(): Iterator>> { + return map.iterator() + } + + val size: Int + get() = map.size + fun size() = size + val keys: Set + get() = map.keys + fun keySet() = keys + val values: Collection> + get() = map.values + fun values() = values + val entries: Set>> + get() = map.entries + fun entrySet() = entries + fun isEmpty(): Boolean = map.isEmpty() + fun containsKey(key: K): Boolean = map.containsKey(key) + fun containsValue(value: V): Boolean = map.values.any { it.contains(value) } + fun contains(key: K, value: V): Boolean = map[key]?.contains(value) ?: false + operator fun get(key: K): Set? = map[key] + fun put(key: K, value: V) = + (map as MutableMap).put(key, mutableSetOf(value)) + fun plus(key: K, value: V) = + (map as MutableMap).getOrPut(key) { mutableSetOf() }.add(value) + fun put(key: K, values: Set) = + (map as MutableMap).put(key, values.toMutableSet()) + fun plus(key: K, values: Set) = + (map as MutableMap).getOrPut(key) { mutableSetOf() }.addAll(values) + fun putAll(other: MultiMap) = other.map.entries.forEach { + put(it.key, it.value) + } + fun plusAll(other: MultiMap) = other.map.entries.forEach { + plus(it.key, it.value) + } + fun removeAll(key: K) = (map as MutableMap).remove(key) + fun remove(key: K, value: V) = (map as MutableMap)[key]?.remove(value) + fun clear() = (map as MutableMap).clear() + fun flatten() = map.flatMap { it.value }.toSet() + + override fun equals(other: Any?): Boolean { + return if (other == null) + false + else if (other !is MultiMap<*, *>) + false + else if (this === other) { + true + } else { + map == other.map + } + } + + override fun hashCode(): Int { + return map.hashCode() + } +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/MultiMapTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/MultiMapTest.java index 98688a94..164befb1 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/MultiMapTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/MultiMapTest.java @@ -41,7 +41,37 @@ public class MultiMapTest { assertTrue(multiMap.containsKey("alice")); assertTrue(multiMap.containsValue("wonderland")); assertNotNull(multiMap.get("alice")); - assertTrue(multiMap.get("alice").contains("wonderland")); + assertTrue(multiMap.contains("alice", "wonderland")); + } + + @Test + public void putOverwritesExistingElements() { + MultiMap map = new MultiMap<>(); + map.put("alice", "wonderland"); + map.put("alice", "whothefrickisalice"); + assertFalse(map.containsValue("wonderland")); + } + + @Test + public void plusDoesNotOverwriteButAdd() { + MultiMap map = new MultiMap<>(); + map.put("alice", "wonderland"); + map.plus("alice", "whothefrickisalice"); + assertTrue(map.containsValue("wonderland")); + assertTrue(map.containsValue("whothefrickisalice")); + } + + @Test + public void containsWorks() { + MultiMap map = new MultiMap<>(); + map.put("alice", "wonderland"); + map.plus("alice", "bar"); + map.put("bob", "builder"); + + assertTrue(map.contains("alice", "wonderland")); + assertTrue(map.contains("alice", "bar")); + assertTrue(map.contains("bob", "builder")); + assertFalse(map.contains("bob", "bar")); } @Test @@ -104,7 +134,7 @@ public class MultiMapTest { @Test public void emptyAfterClear() { MultiMap map = new MultiMap<>(); - map.put("test", "foo"); + map.plus("test", "foo"); assertFalse(map.isEmpty()); map.clear(); assertTrue(map.isEmpty()); @@ -113,8 +143,8 @@ public class MultiMapTest { @Test public void addTwoRemoveOneWorks() { MultiMap map = new MultiMap<>(); - map.put("alice", "wonderland"); - map.put("bob", "builder"); + map.plus("alice", "wonderland"); + map.plus("bob", "builder"); map.removeAll("alice"); assertFalse(map.containsKey("alice")); @@ -125,11 +155,11 @@ public class MultiMapTest { @Test public void addMultiValue() { MultiMap addOneByOne = new MultiMap<>(); - addOneByOne.put("foo", "bar"); - addOneByOne.put("foo", "baz"); + addOneByOne.plus("foo", "bar"); + addOneByOne.plus("foo", "baz"); MultiMap addOnce = new MultiMap<>(); - addOnce.put("foo", new HashSet<>(Arrays.asList("baz", "bar"))); + addOnce.plus("foo", new HashSet<>(Arrays.asList("baz", "bar"))); assertEquals(addOneByOne, addOnce); } @@ -138,7 +168,7 @@ public class MultiMapTest { public void addMultiValueRemoveSingle() { MultiMap map = new MultiMap<>(); map.put("foo", "bar"); - map.put("foo", "baz"); + map.plus("foo", "baz"); map.remove("foo", "bar"); assertFalse(map.isEmpty()); @@ -149,9 +179,9 @@ public class MultiMapTest { @Test public void addMultiValueRemoveAll() { MultiMap map = new MultiMap<>(); - map.put("foo", "bar"); - map.put("foo", "baz"); - map.put("bingo", "bango"); + map.plus("foo", "bar"); + map.plus("foo", "baz"); + map.plus("bingo", "bango"); map.removeAll("foo"); assertFalse(map.isEmpty()); @@ -160,23 +190,23 @@ public class MultiMapTest { } @Test - public void putAll() { + public void plusAll() { MultiMap map = new MultiMap<>(); - map.put("A", "1"); - map.put("A", "2"); - map.put("B", "1"); + map.plus("A", "1"); + map.plus("A", "2"); + map.plus("B", "1"); MultiMap other = new MultiMap<>(); - other.put("A", "1"); - other.put("B", "2"); - other.put("C", "3"); + other.plus("A", "1"); + other.plus("B", "2"); + other.plus("C", "3"); - map.putAll(other); - assertTrue(map.get("A").contains("1")); - assertTrue(map.get("A").contains("2")); - assertTrue(map.get("B").contains("1")); - assertTrue(map.get("B").contains("2")); - assertTrue(map.get("C").contains("3")); + map.plusAll(other); + assertTrue(map.contains("A", "1")); + assertTrue(map.contains("A", "2")); + assertTrue(map.contains("B", "1")); + assertTrue(map.contains("B", "2")); + assertTrue(map.contains("C", "3")); } @Test @@ -188,9 +218,9 @@ public class MultiMapTest { @Test public void flattenMap() { MultiMap map = new MultiMap<>(); - map.put("A", "1"); - map.put("A", "2"); - map.put("B", "1"); + map.plus("A", "1"); + map.plus("A", "2"); + map.plus("B", "1"); Set expected = new LinkedHashSet<>(); expected.add("1"); diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/KeyRingsFromCollectionTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/KeyRingsFromCollectionTest.java index bf4955cd..c7f6d722 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/KeyRingsFromCollectionTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/KeyRingsFromCollectionTest.java @@ -5,7 +5,7 @@ package org.pgpainless.util.selection.keyring; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.Arrays; @@ -19,8 +19,8 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.junit.jupiter.api.Test; import org.pgpainless.key.TestKeys; -import org.pgpainless.util.selection.keyring.impl.ExactUserId; import org.pgpainless.util.MultiMap; +import org.pgpainless.util.selection.keyring.impl.ExactUserId; public class KeyRingsFromCollectionTest { @@ -52,7 +52,7 @@ public class KeyRingsFromCollectionTest { MultiMap selected = strategy.selectKeyRingsFromCollections(map); assertEquals(1, selected.get(TestKeys.JULIET_UID).size()); assertEquals(1, selected.get(TestKeys.EMIL_UID).size()); - assertNull(selected.get("invalidId")); + assertTrue(selected.get("invalidId").isEmpty()); } @Test @@ -73,16 +73,16 @@ public class KeyRingsFromCollectionTest { PGPPublicKeyRing juliet = TestKeys.getJulietPublicKeyRing(); MultiMap map = new MultiMap<>(); PGPPublicKeyRingCollection julietCollection = new PGPPublicKeyRingCollection(Arrays.asList(emil, juliet)); - map.put(TestKeys.JULIET_UID, julietCollection); + map.plus(TestKeys.JULIET_UID, julietCollection); PGPPublicKeyRingCollection emilCollection = new PGPPublicKeyRingCollection(Collections.singletonList(emil)); - map.put(TestKeys.EMIL_UID, emilCollection); + map.plus(TestKeys.EMIL_UID, emilCollection); assertEquals(2, julietCollection.size()); - map.put("invalidId", emilCollection); + map.plus("invalidId", emilCollection); PublicKeyRingSelectionStrategy strategy = new ExactUserId.PubRingSelectionStrategy(); MultiMap selected = strategy.selectKeyRingsFromCollections(map); assertEquals(1, selected.get(TestKeys.JULIET_UID).size()); assertEquals(1, selected.get(TestKeys.EMIL_UID).size()); - assertNull(selected.get("invalidId")); + assertTrue(selected.get("invalidId").isEmpty()); } } From 6b397a0d568488a5935c7516bb676c6b7b01747b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Sep 2023 15:38:30 +0200 Subject: [PATCH 133/155] Kotlin conversion: SignaturePicker --- .../signature/consumer/SignaturePicker.java | 395 ------------------ .../extensions/PGPSignatureExtensions.kt | 4 + .../signature/consumer/SignaturePicker.kt | 310 ++++++++++++++ 3 files changed, 314 insertions(+), 395 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java deleted file mode 100644 index e6f2f755..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java +++ /dev/null @@ -1,395 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.consumer; - -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.policy.Policy; -import org.pgpainless.signature.SignatureUtils; -import org.pgpainless.util.CollectionUtils; - -/** - * Pick signatures from keys. - * - * The format of a V4 OpenPGP key is: - * - * Primary-Key - * [Revocation Self Signature] - * [Direct Key Signature...] - * User ID [Signature ...] - * [User ID [Signature ...] ...] - * [User Attribute [Signature ...] ...] - * [[Subkey [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...] - */ -public final class SignaturePicker { - - private SignaturePicker() { - - } - - /** - * Pick the at validation date most recent valid key revocation signature. - * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after - * validationDate or if it is already expired. - * - * @param keyRing key ring - * @param policy policy - * @param validationDate date of signature validation - * @return most recent, valid key revocation signature - */ - public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - - List signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION); - PGPSignature mostCurrentValidSig = null; - - for (PGPSignature signature : signatures) { - try { - SignatureVerifier.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate); - } catch (SignatureValidationException e) { - // Signature is not valid - continue; - } - mostCurrentValidSig = signature; - } - - return mostCurrentValidSig; - } - - /** - * Pick the at validationDate most recent, valid direct key signature. - * This method might return null, if there is no direct key self-signature which is valid at validationDate. - * - * @param keyRing key ring - * @param policy policy - * @param validationDate validation date - * @return direct-key self-signature - */ - public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, validationDate); - } - - /** - * Pick the at validationDate, latest, valid direct key signature made by signingKey on signedKey. - * This method might return null, if there is no direct key self signature which is valid at validationDate. - * - * @param signingKey key that created the signature - * @param signedKey key that carries the signature - * @param policy policy - * @param validationDate validation date - * @return direct key sig - */ - public static PGPSignature pickCurrentDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) { - List directKeySignatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY); - - PGPSignature mostRecentDirectKeySigBySigningKey = null; - for (PGPSignature signature : directKeySignatures) { - try { - SignatureVerifier.verifyDirectKeySignature(signature, signingKey, signedKey, policy, validationDate); - } catch (SignatureValidationException e) { - // Direct key sig is not valid - continue; - } - mostRecentDirectKeySigBySigningKey = signature; - } - - return mostRecentDirectKeySigBySigningKey; - } - - /** - * Pick the at validationDate latest direct key signature. - * This method might return an expired signature. - * If there are more than one direct-key signature, and some of those are not expired, the latest non-expired - * yet already effective direct-key signature will be returned. - * - * @param keyRing key ring - * @param policy policy - * @param validationDate validation date - * @return latest direct key signature - */ - public static PGPSignature pickLatestDirectKeySignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - return pickLatestDirectKeySignature(primaryKey, primaryKey, policy, validationDate); - } - - /** - * Pick the at validationDate latest direct key signature made by signingKey on signedKey. - * This method might return an expired signature. - * If a non-expired direct-key signature exists, the latest non-expired yet already effective direct-key - * signature will be returned. - * - * @param signingKey signing key (key that made the sig) - * @param signedKey signed key (key that carries the sig) - * @param policy policy - * @param validationDate date of validation - * @return latest direct key sig - */ - public static PGPSignature pickLatestDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) { - List signatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY); - - PGPSignature latestDirectKeySignature = null; - for (PGPSignature signature : signatures) { - try { - SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); - SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); - // if the currently latest signature is not yet expired, check if the next candidate is not yet expired - if (latestDirectKeySignature != null && !SignatureUtils.isSignatureExpired(latestDirectKeySignature, validationDate)) { - SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature); - } - SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(signature); - } catch (SignatureValidationException e) { - // Direct key signature is not valid - continue; - } - latestDirectKeySignature = signature; - } - - return latestDirectKeySignature; - } - - /** - * Pick the at validationDate most recent, valid user-id revocation signature. - * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after - * validationDate or if it is already expired. - * - * @param keyRing key ring - * @param userId user-Id that gets revoked - * @param policy policy - * @param validationDate validation date - * @return revocation signature - */ - public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - List signatures = getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION); - - PGPSignature latestUserIdRevocation = null; - for (PGPSignature signature : signatures) { - PGPPublicKey signer = keyRing.getPublicKey(signature.getKeyID()); - if (signer == null) { - // Signature made by external key. Skip. - continue; - } - try { - SignatureVerifier.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate); - } catch (SignatureValidationException e) { - // User-id revocation is not valid - continue; - } - latestUserIdRevocation = signature; - } - - return latestUserIdRevocation; - } - - /** - * Pick the at validationDate latest, valid certification self-signature for the given user-id. - * This method might return null, if there is no certification self signature for that user-id which is valid - * at validationDate. - * - * @param keyRing keyring - * @param userId userid - * @param policy policy - * @param validationDate validation date - * @return user-id certification - */ - public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - - Iterator userIdSigIterator = primaryKey.getSignaturesForID(userId); - List signatures = CollectionUtils.iteratorToList(userIdSigIterator); - - Collections.sort(signatures, new SignatureCreationDateComparator()); - - PGPSignature mostRecentUserIdCertification = null; - for (PGPSignature signature : signatures) { - if (primaryKey.getKeyID() != signature.getKeyID()) { - // Signature not made by primary key - continue; - } - try { - SignatureVerifier.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate); - } catch (SignatureValidationException e) { - // User-id certification is not valid - continue; - } - mostRecentUserIdCertification = signature; - } - - return mostRecentUserIdCertification; - } - - /** - * Pick the at validationDate latest certification self-signature for the given user-id. - * This method might return an expired signature. - * If a non-expired user-id certification signature exists, the latest non-expired yet already effective - * user-id certification signature for the given user-id will be returned. - * - * @param keyRing keyring - * @param userId userid - * @param policy policy - * @param validationDate validation date - * @return user-id certification - */ - public static PGPSignature pickLatestUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - - Iterator userIdSigIterator = primaryKey.getSignaturesForID(userId); - List signatures = CollectionUtils.iteratorToList(userIdSigIterator); - Collections.sort(signatures, new SignatureCreationDateComparator()); - - PGPSignature latestUserIdCert = null; - for (PGPSignature signature : signatures) { - try { - SignatureValidator.wasPossiblyMadeByKey(primaryKey).verify(signature); - SignatureValidator.signatureIsCertification().verify(signature); - SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); - SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); - SignatureValidator.correctSignatureOverUserId(userId, primaryKey, primaryKey).verify(signature); - } catch (SignatureValidationException e) { - // User-id certification is not valid - continue; - } - - latestUserIdCert = signature; - } - - return latestUserIdCert; - } - - /** - * Pick the at validationDate most recent, valid subkey revocation signature. - * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after - * validationDate or if it is already expired. - * - * @param keyRing keyring - * @param subkey subkey - * @param policy policy - * @param validationDate validation date - * @return subkey revocation signature - */ - public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - if (primaryKey.getKeyID() == subkey.getKeyID()) { - throw new IllegalArgumentException("Primary key cannot have subkey binding revocations."); - } - - List signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_REVOCATION); - PGPSignature latestSubkeyRevocation = null; - - for (PGPSignature signature : signatures) { - try { - SignatureVerifier.verifySubkeyBindingRevocation(signature, primaryKey, subkey, policy, validationDate); - } catch (SignatureValidationException e) { - // subkey binding revocation is not valid - continue; - } - latestSubkeyRevocation = signature; - } - - return latestSubkeyRevocation; - } - - /** - * Pick the at validationDate latest, valid subkey binding signature for the given subkey. - * This method might return null, if there is no subkey binding signature which is valid - * at validationDate. - * - * @param keyRing key ring - * @param subkey subkey - * @param policy policy - * @param validationDate date of validation - * @return most recent valid subkey binding signature - */ - public static PGPSignature pickCurrentSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - if (primaryKey.getKeyID() == subkey.getKeyID()) { - throw new IllegalArgumentException("Primary key cannot have subkey binding signature."); - } - - List subkeyBindingSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING); - PGPSignature mostCurrentValidSig = null; - - for (PGPSignature signature : subkeyBindingSigs) { - try { - SignatureVerifier.verifySubkeyBindingSignature(signature, primaryKey, subkey, policy, validationDate); - } catch (SignatureValidationException validationException) { - // Subkey binding sig is not valid - continue; - } - mostCurrentValidSig = signature; - } - - return mostCurrentValidSig; - } - - /** - * Pick the at validationDate latest subkey binding signature for the given subkey. - * This method might return an expired signature. - * If a non-expired subkey binding signature exists, the latest non-expired yet already effective - * subkey binding signature for the given subkey will be returned. - * - * @param keyRing key ring - * @param subkey subkey - * @param policy policy - * @param validationDate validationDate - * @return subkey binding signature - */ - public static PGPSignature pickLatestSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) { - PGPPublicKey primaryKey = keyRing.getPublicKey(); - if (primaryKey.getKeyID() == subkey.getKeyID()) { - throw new IllegalArgumentException("Primary key cannot have subkey binding signature."); - } - - List signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING); - PGPSignature latestSubkeyBinding = null; - - for (PGPSignature signature : signatures) { - try { - SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); - SignatureValidator.signatureDoesNotPredateSignee(subkey).verify(signature); - SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); - // if the currently latest signature is not yet expired, check if the next candidate is not yet expired - if (latestSubkeyBinding != null && !SignatureUtils.isSignatureExpired(latestSubkeyBinding, validationDate)) { - SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature); - } - SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(signature); - } catch (SignatureValidationException e) { - // Subkey binding sig is not valid - continue; - } - latestSubkeyBinding = signature; - } - - return latestSubkeyBinding; - } - - /** - * Return a list of all signatures of the given {@link SignatureType} on the given key, sorted using a - * {@link SignatureCreationDateComparator}. - * - * The returned list will be sorted first by ascending signature creation time. - * - * @param key key - * @param type type of signatures which shall be collected and sorted - * @return sorted list of signatures - */ - private static List getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) { - Iterator signaturesOfType = key.getSignaturesOfType(type.getCode()); - List signatureList = CollectionUtils.iteratorToList(signaturesOfType); - Collections.sort(signatureList, new SignatureCreationDateComparator()); - return signatureList; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt index a27e68e0..4fe97bc7 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -5,6 +5,7 @@ package org.bouncycastle.extensions import openpgp.plusSeconds +import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.algorithm.RevocationState import org.pgpainless.algorithm.SignatureType @@ -69,6 +70,9 @@ fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean = false } +fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = + wasIssuedBy(OpenPgpFingerprint.of(key)) + /** * Return true, if this signature is a hard revocation. */ diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt new file mode 100644 index 00000000..9c5b9a8d --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt @@ -0,0 +1,310 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import org.bouncycastle.extensions.getPublicKeyFor +import org.bouncycastle.extensions.hasPublicKey +import org.bouncycastle.extensions.isExpired +import org.bouncycastle.extensions.wasIssuedBy +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.algorithm.SignatureType +import org.pgpainless.exception.SignatureValidationException +import org.pgpainless.policy.Policy +import java.util.Date +import kotlin.math.sign + +/** + * Pick signatures from keys. + * + * The format of a V4 OpenPGP key is: + * + * Primary-Key + * [Revocation Self Signature] + * [Direct Key Signature...] + * User ID [Signature ...] + * [User ID [Signature ...] ...] + * [User Attribute [Signature ...] ...] + * [[Subkey [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...] + */ +class SignaturePicker { + + companion object { + + /** + * Pick the at validation date most recent valid key revocation signature. + * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after + * validationDate or if it is already expired. + * + * @param keyRing key ring + * @param policy policy + * @param referenceTime date of signature validation + * @return most recent, valid key revocation signature + */ + @JvmStatic + fun pickCurrentRevocationSelfSignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION).lastOrNull { + return@lastOrNull try { + SignatureVerifier.verifyKeyRevocationSignature(it, primaryKey, policy, referenceTime) + true // valid + } catch (e : SignatureValidationException) { + false // not valid + } + } + } + + /** + * Pick the at validationDate most recent, valid direct key signature. + * This method might return null, if there is no direct key self-signature which is valid at validationDate. + * + * @param keyRing key ring + * @param policy policy + * @param referenceTime validation date + * @return direct-key self-signature + */ + @JvmStatic + fun pickCurrentDirectKeySelfSignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, referenceTime) + } + + @JvmStatic + fun pickCurrentDirectKeySignature(signingKey: PGPPublicKey, signedKey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + return getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY).lastOrNull { + return@lastOrNull try { + SignatureVerifier.verifyDirectKeySignature(it, signingKey, signedKey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * Pick the at validationDate latest direct key signature. + * This method might return an expired signature. + * If there are more than one direct-key signature, and some of those are not expired, the latest non-expired + * yet already effective direct-key signature will be returned. + * + * @param keyRing key ring + * @param policy policy + * @param referenceTime validation date + * @return latest direct key signature + */ + @JvmStatic + fun pickLatestDirectKeySignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { + return pickLatestDirectKeySignature(keyRing.publicKey, keyRing.publicKey, policy, referenceTime) + } + + /** + * Pick the at validationDate latest direct key signature made by signingKey on signedKey. + * This method might return an expired signature. + * If a non-expired direct-key signature exists, the latest non-expired yet already effective direct-key + * signature will be returned. + * + * @param signingKey signing key (key that made the sig) + * @param signedKey signed key (key that carries the sig) + * @param policy policy + * @param referenceTime date of validation + * @return latest direct key sig + */ + @JvmStatic + fun pickLatestDirectKeySignature(signingKey: PGPPublicKey, signedKey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + var latest: PGPSignature? = null + return getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY).lastOrNull { + try { + SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(it) + SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(it) + SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) + if (latest != null && !latest!!.isExpired(referenceTime)) { + SignatureValidator.signatureIsNotYetExpired(referenceTime).verify(it) + } + SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(it) + latest = it + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * Pick the at validationDate most recent, valid user-id revocation signature. + * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after + * validationDate or if it is already expired. + * + * @param keyRing key ring + * @param userId user-Id that gets revoked + * @param policy policy + * @param referenceTime validation date + * @return revocation signature + */ + @JvmStatic + fun pickCurrentUserIdRevocationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION).lastOrNull { + keyRing.getPublicKeyFor(it) ?: return@lastOrNull false // signature made by external key. skip. + return@lastOrNull try { + SignatureVerifier.verifyUserIdRevocation(userId.toString(), it, primaryKey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false // signature not valid + } + } + } + + /** + * Pick the at validationDate latest, valid certification self-signature for the given user-id. + * This method might return null, if there is no certification self signature for that user-id which is valid + * at validationDate. + * + * @param keyRing keyring + * @param userId userid + * @param policy policy + * @param referenceTime validation date + * @return user-id certification + */ + @JvmStatic + fun pickCurrentUserIdCertificationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return primaryKey.getSignaturesForID(userId.toString()).asSequence() + .sortedWith(SignatureCreationDateComparator()) + .lastOrNull { + return@lastOrNull it.wasIssuedBy(primaryKey) && try { + SignatureVerifier.verifyUserIdCertification(userId.toString(), it, primaryKey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * Pick the at validationDate latest certification self-signature for the given user-id. + * This method might return an expired signature. + * If a non-expired user-id certification signature exists, the latest non-expired yet already effective + * user-id certification signature for the given user-id will be returned. + * + * @param keyRing keyring + * @param userId userid + * @param policy policy + * @param referenceTime validation date + * @return user-id certification + */ + @JvmStatic + fun pickLatestUserIdCertificationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return primaryKey.getSignaturesForID(userId.toString()).asSequence() + .sortedWith(SignatureCreationDateComparator()) + .lastOrNull { + return@lastOrNull try { + SignatureValidator.wasPossiblyMadeByKey(primaryKey).verify(it) + SignatureValidator.signatureIsCertification().verify(it) + SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(it) + SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) + SignatureValidator.correctSignatureOverUserId(userId.toString(), primaryKey, primaryKey).verify(it) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * Pick the at validationDate most recent, valid subkey revocation signature. + * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after + * validationDate or if it is already expired. + * + * @param keyRing keyring + * @param subkey subkey + * @param policy policy + * @param referenceTime validation date + * @return subkey revocation signature + */ + @JvmStatic + fun pickCurrentSubkeyBindingRevocationSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding revocations." } + return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_REVOCATION).lastOrNull { + return@lastOrNull try { + SignatureVerifier.verifySubkeyBindingRevocation(it, primaryKey, subkey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * Pick the at validationDate latest, valid subkey binding signature for the given subkey. + * This method might return null, if there is no subkey binding signature which is valid + * at validationDate. + * + * @param keyRing key ring + * @param subkey subkey + * @param policy policy + * @param referenceTime date of validation + * @return most recent valid subkey binding signature + */ + @JvmStatic + fun pickCurrentSubkeyBindingSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding signatures." } + return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING).lastOrNull { + return@lastOrNull try { + SignatureVerifier.verifySubkeyBindingSignature(it, primaryKey, subkey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * Pick the at validationDate latest subkey binding signature for the given subkey. + * This method might return an expired signature. + * If a non-expired subkey binding signature exists, the latest non-expired yet already effective + * subkey binding signature for the given subkey will be returned. + * + * @param keyRing key ring + * @param subkey subkey + * @param policy policy + * @param referenceTime validationDate + * @return subkey binding signature + */ + @JvmStatic + fun pickLatestSubkeyBindingSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding signatures." } + var latest: PGPSignature? = null + return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING).lastOrNull { + return@lastOrNull try { + SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(it) + SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(it) + SignatureValidator.signatureDoesNotPredateSignee(subkey).verify(it) + SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) + // if the currently latest signature is not yet expired, check if the next candidate is not yet expired + if (latest != null && !latest!!.isExpired(referenceTime)) { + SignatureValidator.signatureIsNotYetExpired(referenceTime).verify(it) + } + SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(it) + latest = it + true + } catch (e : SignatureValidationException) { + false + } + } + } + + @JvmStatic + private fun getSortedSignaturesOfType(key: PGPPublicKey, type: SignatureType): List = + key.getSignaturesOfType(type.code).asSequence() + .sortedWith(SignatureCreationDateComparator()) + .toList() + } + +} \ No newline at end of file From e16376ca68fdfcb641903f7b7a414c0ab97ccb53 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 29 Sep 2023 14:05:21 +0200 Subject: [PATCH 134/155] Kotlin conversion: ArmoredInputStreamFactory --- .../util/ArmoredInputStreamFactory.java | 43 ------------------- .../util/ArmoredInputStreamFactory.kt | 36 ++++++++++++++++ 2 files changed, 36 insertions(+), 43 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/ArmoredInputStreamFactory.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredInputStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredInputStreamFactory.java deleted file mode 100644 index 77e9c236..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredInputStreamFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import java.io.IOException; -import java.io.InputStream; - -import org.bouncycastle.bcpg.ArmoredInputStream; - -import javax.annotation.Nonnull; - -/** - * Factory class for instantiating preconfigured {@link ArmoredInputStream ArmoredInputStreams}. - * {@link #get(InputStream)} will return an {@link ArmoredInputStream} that is set up to properly detect CRC errors. - */ -public final class ArmoredInputStreamFactory { - - private ArmoredInputStreamFactory() { - - } - - /** - * Return an instance of {@link ArmoredInputStream} which will detect CRC errors. - * - * @param inputStream input stream - * @return armored input stream - * @throws IOException in case of an IO error - */ - @Nonnull - public static ArmoredInputStream get(@Nonnull InputStream inputStream) throws IOException { - if (inputStream instanceof CRCingArmoredInputStreamWrapper) { - return (ArmoredInputStream) inputStream; - } - if (inputStream instanceof ArmoredInputStream) { - return new CRCingArmoredInputStreamWrapper((ArmoredInputStream) inputStream); - } - - ArmoredInputStream armorIn = new ArmoredInputStream(inputStream); - return new CRCingArmoredInputStreamWrapper(armorIn); - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt new file mode 100644 index 00000000..82d8f248 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +import org.bouncycastle.bcpg.ArmoredInputStream +import java.io.IOException +import java.io.InputStream + +/** + * Factory class for instantiating preconfigured [ArmoredInputStream] instances. + * [get] will return an [ArmoredInputStream] that is set up to properly detect CRC errors v4 style. + */ +class ArmoredInputStreamFactory { + + companion object { + + /** + * Return an instance of [ArmoredInputStream] which will detect CRC errors. + * + * @param inputStream input stream + * @return armored input stream + * @throws IOException in case of an IO error + */ + @JvmStatic + @Throws(IOException::class) + fun get(inputStream: InputStream): ArmoredInputStream { + return when (inputStream) { + is CRCingArmoredInputStreamWrapper -> inputStream + is ArmoredInputStream -> CRCingArmoredInputStreamWrapper(inputStream) + else -> CRCingArmoredInputStreamWrapper(ArmoredInputStream(inputStream)) + } + } + } +} \ No newline at end of file From aca884e9366b0e60654db4185db280fe262ac500 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 29 Sep 2023 14:57:48 +0200 Subject: [PATCH 135/155] Kotlin conversion: ArmoredOutputStreamFactory Also allow configuration of CRC calculation for both input and output streams --- .../util/ArmoredOutputStreamFactory.java | 132 ------------------ .../ConsumerOptions.kt | 1 + .../encryption_signing/ProducerOptions.kt | 1 + .../util/ArmoredInputStreamFactory.kt | 12 +- .../util/ArmoredOutputStreamFactory.kt | 102 ++++++++++++++ 5 files changed, 114 insertions(+), 134 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java deleted file mode 100644 index 269f8674..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import java.io.OutputStream; - -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.pgpainless.encryption_signing.ProducerOptions; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Factory to create configured {@link ArmoredOutputStream ArmoredOutputStreams}. - * The configuration entails setting custom version and comment headers. - */ -public final class ArmoredOutputStreamFactory { - - /** - * Name of the program. - */ - public static final String PGPAINLESS = "PGPainless"; - private static String version = PGPAINLESS; - private static String[] comment = new String[0]; - - private ArmoredOutputStreamFactory() { - - } - - private static ArmoredOutputStream.Builder getBuilder() { - ArmoredOutputStream.Builder builder = ArmoredOutputStream.builder(); - builder.clearHeaders(); - if (version != null && !version.isEmpty()) { - builder.setVersion(version); - } - for (String comment : comment) { - builder.addComment(comment); - } - return builder; - } - - /** - * Wrap an {@link OutputStream} inside a preconfigured {@link ArmoredOutputStream}. - * - * @param outputStream inner stream - * @return armored output stream - */ - @Nonnull - public static ArmoredOutputStream get(@Nonnull OutputStream outputStream) { - return getBuilder().build(outputStream); - } - - /** - * Return an instance of the {@link ArmoredOutputStream} which might have pre-populated armor headers. - * - * @param outputStream output stream - * @param options options - * @return armored output stream - */ - @Nonnull - public static ArmoredOutputStream get(@Nonnull OutputStream outputStream, @Nonnull ProducerOptions options) { - ArmoredOutputStream.Builder builder = getBuilder(); - if (options.isHideArmorHeaders()) { - builder.clearHeaders(); - } - if (options.hasVersion()) { - builder.setVersion(options.getVersion()); - } - if (options.hasComment()) { - builder.setComment(options.getComment()); - } - return builder.build(outputStream); - } - - /** - * Overwrite the version header of ASCII armors with a custom value. - * Newlines in the version info string result in multiple version header entries. - * If this is set to
null
, then the version header is omitted altogether. - * - * @param versionString version string - */ - public static void setVersionInfo(@Nullable String versionString) { - if (versionString == null) { - version = null; - return; - } - String trimmed = versionString.trim(); - if (trimmed.isEmpty()) { - version = null; - } else { - version = trimmed; - } - } - - /** - * Reset the version header to its default value of {@link #PGPAINLESS}. - */ - public static void resetVersionInfo() { - version = PGPAINLESS; - } - - /** - * Set a comment header value in the ASCII armor header. - * If the comment contains newlines, it will be split into multiple header entries. - * - * @see org.pgpainless.encryption_signing.ProducerOptions#setComment(String) for how to set comments for - * individual messages. - * - * @param commentString comment - */ - public static void setComment(@Nullable String commentString) { - if (commentString == null) { - throw new IllegalArgumentException("Comment cannot be null."); - } - String trimmed = commentString.trim(); - if (trimmed.isEmpty()) { - throw new IllegalArgumentException("Comment cannot be empty."); - } - - String[] lines = commentString.split("\n"); - comment = lines; - } - - /** - * Reset to the default of no comment headers. - */ - public static void resetComment() { - comment = new String[0]; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt index dbff0551..e0ec1fd5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt @@ -24,6 +24,7 @@ import java.util.* class ConsumerOptions { private var ignoreMDCErrors = false + var isDisableAsciiArmorCRC = false private var forceNonOpenPgpData = false private var verifyNotBefore: Date? = null private var verifyNotAfter: Date? = Date() diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt index bdc153e9..88345fd9 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt @@ -20,6 +20,7 @@ class ProducerOptions private constructor( private var applyCRLFEncoding = false private var cleartextSigned = false private var _hideArmorHeaders = false + var isDisableAsciiArmorCRC = false private var _compressionAlgorithmOverride: CompressionAlgorithm = PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm private var asciiArmor = true diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt index 82d8f248..254b5c88 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt @@ -5,6 +5,7 @@ package org.pgpainless.util import org.bouncycastle.bcpg.ArmoredInputStream +import org.pgpainless.decryption_verification.ConsumerOptions import java.io.IOException import java.io.InputStream @@ -24,12 +25,19 @@ class ArmoredInputStreamFactory { * @throws IOException in case of an IO error */ @JvmStatic + @JvmOverloads @Throws(IOException::class) - fun get(inputStream: InputStream): ArmoredInputStream { + fun get(inputStream: InputStream, options: ConsumerOptions? = null): ArmoredInputStream { return when (inputStream) { is CRCingArmoredInputStreamWrapper -> inputStream is ArmoredInputStream -> CRCingArmoredInputStreamWrapper(inputStream) - else -> CRCingArmoredInputStreamWrapper(ArmoredInputStream(inputStream)) + else -> CRCingArmoredInputStreamWrapper( + ArmoredInputStream.builder().apply { + setParseForHeaders(true) + options?.let { + setIgnoreCRC(it.isDisableAsciiArmorCRC) + } + }.build(inputStream)) } } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt new file mode 100644 index 00000000..69a5520f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +import org.bouncycastle.bcpg.ArmoredOutputStream +import org.pgpainless.encryption_signing.ProducerOptions +import java.io.OutputStream + +/** + * Factory to create configured [ArmoredOutputStream] instances. + * The configuration entails setting custom version and comment headers. + */ +class ArmoredOutputStreamFactory { + + companion object { + private const val PGPAINLESS = "PGPainless" + + @JvmStatic + private var version: String? = PGPAINLESS + private var comment: String? = null + + /** + * Return an instance of the [ArmoredOutputStream] which might have pre-populated armor headers. + * + * @param outputStream output stream + * @param options options + * @return armored output stream + */ + @JvmStatic + @JvmOverloads + fun get(outputStream: OutputStream, options: ProducerOptions? = null): ArmoredOutputStream { + val builder = ArmoredOutputStream.builder().apply { + // set fields defined in ArmoredOutputStreamFactory + if (!version.isNullOrBlank()) setVersion(version) + if (!comment.isNullOrBlank()) setComment(comment) + + // set (and potentially overwrite with) values from ProducerOptions + options?.let { + enableCRC(!it.isDisableAsciiArmorCRC) + if (it.isHideArmorHeaders) clearHeaders() + if (it.hasVersion()) setVersion(it.version) + if (it.hasComment()) addComment(it.comment) + // TODO: configure CRC + } + } + return get(outputStream, builder) + } + + /** + * Build an [ArmoredOutputStream] around the given [outputStream], configured according to the passed in + * [ArmoredOutputStream.Builder] instance. + * + * @param outputStream output stream + * @param builder builder instance + */ + @JvmStatic + fun get(outputStream: OutputStream, builder: ArmoredOutputStream.Builder): ArmoredOutputStream { + return builder.build(outputStream) + } + + /** + * Overwrite the version header of ASCII armors with a custom value. + * Newlines in the version info string result in multiple version header entries. + * If this is set to
null
, then the version header is omitted altogether. + * + * @param versionString version string + */ + @JvmStatic + fun setVersionInfo(versionString: String?) { + version = if (versionString.isNullOrBlank()) null else versionString.trim() + } + + /** + * Reset the version header to its default value of [PGPAINLESS]. + */ + @JvmStatic + fun resetVersionInfo() { + version = PGPAINLESS + } + + /** + * Set a comment header value in the ASCII armor header. + * If the comment contains newlines, it will be split into multiple header entries. + * + * @see [ProducerOptions.setComment] for how to set comments for individual messages. + * + * @param commentString comment + */ + @JvmStatic + fun setComment(commentString: String) { + require(commentString.isNotBlank()) { "Comment cannot be empty. See resetComment() to clear the comment." } + comment = commentString.trim() + } + + @JvmStatic + fun resetComment() { + comment = null + } + } +} \ No newline at end of file From 8382da923d931bfc904a6eea6dda46d15cc84260 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 29 Sep 2023 14:58:25 +0200 Subject: [PATCH 136/155] Add TODO to CRCinArmoredInputStreamWrapper --- .../org/pgpainless/util/CRCingArmoredInputStreamWrapper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/CRCingArmoredInputStreamWrapper.java b/pgpainless-core/src/main/java/org/pgpainless/util/CRCingArmoredInputStreamWrapper.java index 2c43339b..d2393be3 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/CRCingArmoredInputStreamWrapper.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/CRCingArmoredInputStreamWrapper.java @@ -17,6 +17,8 @@ import javax.annotation.Nonnull; * * Furthermore, this class swallows exceptions from BC's ArmoredInputStream that are caused * by missing CRC checksums. + * + * TODO: Validate whether this class is still needed. */ public class CRCingArmoredInputStreamWrapper extends ArmoredInputStream { From d707dcf74a8aa78b8c5e524f5481220aa5ba96a8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 4 Oct 2023 13:16:06 +0200 Subject: [PATCH 137/155] Move now unused utility classes to test directory --- .../{main => test}/java/org/pgpainless/util/CollectionUtils.java | 0 .../src/{main => test}/java/org/pgpainless/util/Tuple.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename pgpainless-core/src/{main => test}/java/org/pgpainless/util/CollectionUtils.java (100%) rename pgpainless-core/src/{main => test}/java/org/pgpainless/util/Tuple.java (100%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/CollectionUtils.java b/pgpainless-core/src/test/java/org/pgpainless/util/CollectionUtils.java similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/util/CollectionUtils.java rename to pgpainless-core/src/test/java/org/pgpainless/util/CollectionUtils.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java b/pgpainless-core/src/test/java/org/pgpainless/util/Tuple.java similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java rename to pgpainless-core/src/test/java/org/pgpainless/util/Tuple.java From 1cdce5c93aca3d259079d1c8e4c428c24914a520 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 4 Oct 2023 14:18:26 +0200 Subject: [PATCH 138/155] Kotlin conversion: ImplementationFactory classes --- .../BcImplementationFactory.java | 154 ------------------ .../implementation/ImplementationFactory.java | 119 -------------- .../JceImplementationFactory.java | 141 ---------------- .../implementation/package-info.java | 8 - .../PublicKeyParameterValidationUtil.java | 2 +- .../consumer/SignatureValidator.java | 10 +- .../signature/consumer/SignatureVerifier.java | 2 +- .../implementation/BcImplementationFactory.kt | 96 +++++++++++ .../implementation/ImplementationFactory.kt | 93 +++++++++++ .../JceImplementationFactory.kt | 102 ++++++++++++ ...artyCertificationSignatureBuilderTest.java | 2 +- 11 files changed, 299 insertions(+), 430 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/implementation/BcImplementationFactory.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/implementation/JceImplementationFactory.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/implementation/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/implementation/BcImplementationFactory.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/implementation/ImplementationFactory.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/implementation/JceImplementationFactory.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/BcImplementationFactory.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/BcImplementationFactory.java deleted file mode 100644 index cbc320e6..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/implementation/BcImplementationFactory.java +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.implementation; - -import java.io.InputStream; -import java.security.KeyPair; -import java.util.Date; - -import org.bouncycastle.crypto.AsymmetricCipherKeyPair; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyPair; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSessionKey; -import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; -import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; -import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; -import org.bouncycastle.openpgp.operator.PGPDigestCalculator; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; -import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter; -import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.util.Passphrase; - -public class BcImplementationFactory extends ImplementationFactory { - - @Override - public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm, - PGPDigestCalculator digestCalculator, - Passphrase passphrase) { - return new BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId(), digestCalculator) - .build(passphrase.getChars()); - } - - @Override - public PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) { - return new BcPBESecretKeyDecryptorBuilder(getPGPDigestCalculatorProvider()) - .build(passphrase.getChars()); - } - - @Override - public BcPGPDigestCalculatorProvider getPGPDigestCalculatorProvider() { - return new BcPGPDigestCalculatorProvider(); - } - - @Override - public PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider() { - return new BcPGPContentVerifierBuilderProvider(); - } - - @Override - public PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) { - return new BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm); - } - - @Override - public KeyFingerPrintCalculator getKeyFingerprintCalculator() { - return new BcKeyFingerprintCalculator(); - } - - @Override - public PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase) { - return new BcPBEDataDecryptorFactory(passphrase.getChars(), getPGPDigestCalculatorProvider()); - } - - @Override - public PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey) { - return new BcPublicKeyDataDecryptorFactory(privateKey); - } - - @Override - public SessionKeyDataDecryptorFactory getSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) { - return new BcSessionKeyDataDecryptorFactory(sessionKey); - } - - @Override - public PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) { - return new BcPublicKeyKeyEncryptionMethodGenerator(key); - } - - @Override - public PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase) { - return new BcPBEKeyEncryptionMethodGenerator(passphrase.getChars()); - } - - @Override - public PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm) { - return new BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm); - } - - @Override - public PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate) - throws PGPException { - return new BcPGPKeyPair(algorithm.getAlgorithmId(), jceToBcKeyPair(algorithm, keyPair, creationDate), creationDate); - } - - @Override - public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm, HashAlgorithm hashAlgorithm, int s2kCount, Passphrase passphrase) throws PGPException { - return new BcPBESecretKeyEncryptorBuilder( - encryptionAlgorithm.getAlgorithmId(), - getPGPDigestCalculator(hashAlgorithm), - s2kCount) - .build(passphrase.getChars()); - } - - @Override - public PGPObjectFactory getPGPObjectFactory(byte[] bytes) { - return new BcPGPObjectFactory(bytes); - } - - @Override - public PGPObjectFactory getPGPObjectFactory(InputStream inputStream) { - return new BcPGPObjectFactory(inputStream); - } - - private AsymmetricCipherKeyPair jceToBcKeyPair(PublicKeyAlgorithm algorithm, - KeyPair keyPair, - Date creationDate) throws PGPException { - BcPGPKeyConverter converter = new BcPGPKeyConverter(); - - PGPKeyPair pair = new JcaPGPKeyPair(algorithm.getAlgorithmId(), keyPair, creationDate); - AsymmetricKeyParameter publicKey = converter.getPublicKey(pair.getPublicKey()); - AsymmetricKeyParameter privateKey = converter.getPrivateKey(pair.getPrivateKey()); - - return new AsymmetricCipherKeyPair(publicKey, privateKey); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java deleted file mode 100644 index 90d1330f..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/implementation/ImplementationFactory.java +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.implementation; - -import java.io.InputStream; -import java.security.KeyPair; -import java.util.Date; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyPair; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSessionKey; -import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; -import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; -import org.bouncycastle.openpgp.operator.PGPDigestCalculator; -import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.util.Passphrase; -import org.pgpainless.util.SessionKey; - -public abstract class ImplementationFactory { - - private static ImplementationFactory FACTORY_IMPLEMENTATION; - - public static void setFactoryImplementation(ImplementationFactory implementation) { - FACTORY_IMPLEMENTATION = implementation; - } - - public static ImplementationFactory getInstance() { - if (FACTORY_IMPLEMENTATION == null) { - FACTORY_IMPLEMENTATION = new BcImplementationFactory(); - } - return FACTORY_IMPLEMENTATION; - } - - public abstract PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm, - PGPDigestCalculator digestCalculator, - Passphrase passphrase); - - public abstract PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException; - - public PGPDigestCalculator getV4FingerprintCalculator() throws PGPException { - return getPGPDigestCalculator(HashAlgorithm.SHA1); - } - - public PGPDigestCalculator getPGPDigestCalculator(HashAlgorithm algorithm) throws PGPException { - return getPGPDigestCalculator(algorithm.getAlgorithmId()); - } - - public PGPDigestCalculator getPGPDigestCalculator(int algorithm) throws PGPException { - return getPGPDigestCalculatorProvider().get(algorithm); - } - - public abstract PGPDigestCalculatorProvider getPGPDigestCalculatorProvider() throws PGPException; - - public abstract PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider(); - - public PGPContentSignerBuilder getPGPContentSignerBuilder(PublicKeyAlgorithm keyAlgorithm, HashAlgorithm hashAlgorithm) { - return getPGPContentSignerBuilder(keyAlgorithm.getAlgorithmId(), hashAlgorithm.getAlgorithmId()); - } - - public abstract PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm); - - public abstract KeyFingerPrintCalculator getKeyFingerprintCalculator(); - - public abstract PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase) throws PGPException; - - public abstract PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey); - - public SessionKeyDataDecryptorFactory getSessionKeyDataDecryptorFactory(SessionKey sessionKey) { - PGPSessionKey pgpSessionKey = new PGPSessionKey( - sessionKey.getAlgorithm().getAlgorithmId(), - sessionKey.getKey() - ); - return getSessionKeyDataDecryptorFactory(pgpSessionKey); - } - - public abstract SessionKeyDataDecryptorFactory getSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey); - - public abstract PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key); - - public abstract PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase); - - public PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(SymmetricKeyAlgorithm symmetricKeyAlgorithm) { - return getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId()); - } - - public abstract PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm); - - public abstract PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate) throws PGPException; - - public abstract PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm, - HashAlgorithm hashAlgorithm, int s2kCount, - Passphrase passphrase) throws PGPException; - - public abstract PGPObjectFactory getPGPObjectFactory(InputStream inputStream); - - public abstract PGPObjectFactory getPGPObjectFactory(byte[] bytes); - - @Override - public String toString() { - return getClass().getSimpleName(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/JceImplementationFactory.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/JceImplementationFactory.java deleted file mode 100644 index 10760de2..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/implementation/JceImplementationFactory.java +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.implementation; - -import java.io.InputStream; -import java.security.KeyPair; -import java.util.Date; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyPair; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSessionKey; -import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; -import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; -import org.bouncycastle.openpgp.operator.PGPDigestCalculator; -import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; -import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; -import org.bouncycastle.openpgp.operator.jcajce.JceSessionKeyDataDecryptorFactoryBuilder; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.provider.ProviderFactory; -import org.pgpainless.util.Passphrase; - -public class JceImplementationFactory extends ImplementationFactory { - - public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm symmetricKeyAlgorithm, PGPDigestCalculator digestCalculator, Passphrase passphrase) { - return new JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId(), digestCalculator) - .setProvider(ProviderFactory.getProvider()) - .build(passphrase.getChars()); - } - - public PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException { - return new JcePBESecretKeyDecryptorBuilder(getPGPDigestCalculatorProvider()) - .setProvider(ProviderFactory.getProvider()) - .build(passphrase.getChars()); - } - - public PGPDigestCalculatorProvider getPGPDigestCalculatorProvider() - throws PGPException { - return new JcaPGPDigestCalculatorProviderBuilder() - .setProvider(ProviderFactory.getProvider()) - .build(); - } - - public PGPContentVerifierBuilderProvider getPGPContentVerifierBuilderProvider() { - return new JcaPGPContentVerifierBuilderProvider() - .setProvider(ProviderFactory.getProvider()); - } - - public PGPContentSignerBuilder getPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) { - return new JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) - .setProvider(ProviderFactory.getProvider()); - } - - public KeyFingerPrintCalculator getKeyFingerprintCalculator() { - return new JcaKeyFingerprintCalculator() - .setProvider(ProviderFactory.getProvider()); - } - - public PBEDataDecryptorFactory getPBEDataDecryptorFactory(Passphrase passphrase) - throws PGPException { - return new JcePBEDataDecryptorFactoryBuilder(getPGPDigestCalculatorProvider()) - .setProvider(ProviderFactory.getProvider()) - .build(passphrase.getChars()); - } - - public PublicKeyDataDecryptorFactory getPublicKeyDataDecryptorFactory(PGPPrivateKey privateKey) { - return new JcePublicKeyDataDecryptorFactoryBuilder() - .setProvider(ProviderFactory.getProvider()) - .build(privateKey); - } - - @Override - public SessionKeyDataDecryptorFactory getSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) { - return new JceSessionKeyDataDecryptorFactoryBuilder() - .build(sessionKey); - } - - public PublicKeyKeyEncryptionMethodGenerator getPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) { - return new JcePublicKeyKeyEncryptionMethodGenerator(key) - .setProvider(ProviderFactory.getProvider()); - } - - public PBEKeyEncryptionMethodGenerator getPBEKeyEncryptionMethodGenerator(Passphrase passphrase) { - return new JcePBEKeyEncryptionMethodGenerator(passphrase.getChars()) - .setProvider(ProviderFactory.getProvider()); - } - - public PGPDataEncryptorBuilder getPGPDataEncryptorBuilder(int symmetricKeyAlgorithm) { - return new JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm) - .setProvider(ProviderFactory.getProvider()); - } - - public PGPKeyPair getPGPKeyPair(PublicKeyAlgorithm algorithm, KeyPair keyPair, Date creationDate) throws PGPException { - return new JcaPGPKeyPair(algorithm.getAlgorithmId(), keyPair, creationDate); - } - - public PBESecretKeyEncryptor getPBESecretKeyEncryptor(SymmetricKeyAlgorithm encryptionAlgorithm, HashAlgorithm hashAlgorithm, int s2kCount, Passphrase passphrase) throws PGPException { - return new JcePBESecretKeyEncryptorBuilder( - encryptionAlgorithm.getAlgorithmId(), - getPGPDigestCalculator(hashAlgorithm), - s2kCount) - .setProvider(ProviderFactory.getProvider()) - .build(passphrase.getChars()); - } - - @Override - public PGPObjectFactory getPGPObjectFactory(InputStream inputStream) { - return new PGPObjectFactory(inputStream, ImplementationFactory.getInstance().getKeyFingerprintCalculator()); - } - - @Override - public PGPObjectFactory getPGPObjectFactory(byte[] bytes) { - return new PGPObjectFactory(bytes, ImplementationFactory.getInstance().getKeyFingerprintCalculator()); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/implementation/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/implementation/package-info.java deleted file mode 100644 index 3ce87531..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/implementation/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Implementation factory classes to be able to switch out the underlying crypto engine implementation. - */ -package org.pgpainless.implementation; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java index 344f063b..1649f578 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java @@ -120,7 +120,7 @@ public class PublicKeyParameterValidationUtil { signatureGenerator.update(data); PGPSignature sig = signatureGenerator.generate(); - sig.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), publicKey); + sig.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), publicKey); sig.update(data); return sig.verify(); } catch (PGPException e) { diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java index 8ab31b2f..18bf5883 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java @@ -495,7 +495,7 @@ public abstract class SignatureValidator { } try { signature.init(ImplementationFactory.getInstance() - .getPGPContentVerifierBuilderProvider(), primaryKey); + .getPgpContentVerifierBuilderProvider(), primaryKey); boolean valid = signature.verifyCertification(primaryKey, subkey); if (!valid) { throw new SignatureValidationException("Signature is not correct."); @@ -519,7 +519,7 @@ public abstract class SignatureValidator { @Override public void verify(PGPSignature signature) throws SignatureValidationException { try { - signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), subkey); + signature.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), subkey); boolean valid = signature.verifyCertification(primaryKey, subkey); if (!valid) { throw new SignatureValidationException("Primary Key Binding Signature is not correct."); @@ -544,7 +544,7 @@ public abstract class SignatureValidator { @Override public void verify(PGPSignature signature) throws SignatureValidationException { try { - signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signer); + signature.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), signer); boolean valid; if (signer.getKeyID() == signee.getKeyID() || signature.getSignatureType() == PGPSignature.DIRECT_KEY) { valid = signature.verifyCertification(signee); @@ -615,7 +615,7 @@ public abstract class SignatureValidator { public void verify(PGPSignature signature) throws SignatureValidationException { try { signature.init(ImplementationFactory.getInstance() - .getPGPContentVerifierBuilderProvider(), certifyingKey); + .getPgpContentVerifierBuilderProvider(), certifyingKey); boolean valid = signature.verifyCertification(userId, certifiedKey); if (!valid) { throw new SignatureValidationException("Signature over user-id '" + userId + @@ -645,7 +645,7 @@ public abstract class SignatureValidator { public void verify(PGPSignature signature) throws SignatureValidationException { try { signature.init(ImplementationFactory.getInstance() - .getPGPContentVerifierBuilderProvider(), certifyingKey); + .getPgpContentVerifierBuilderProvider(), certifyingKey); boolean valid = signature.verifyCertification(userAttributes, certifiedKey); if (!valid) { throw new SignatureValidationException("Signature over user-attribute vector is not correct."); diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureVerifier.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureVerifier.java index c4565197..a55037e5 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureVerifier.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureVerifier.java @@ -384,7 +384,7 @@ public final class SignatureVerifier { PGPPublicKey signingKey) throws SignatureValidationException { try { - signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); + signature.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), signingKey); int read; byte[] buf = new byte[8192]; byte lastByte = -1; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/BcImplementationFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/BcImplementationFactory.kt new file mode 100644 index 00000000..fec3a550 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/BcImplementationFactory.kt @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.implementation + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory +import org.bouncycastle.openpgp.operator.* +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator +import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory +import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder +import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider +import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator +import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.util.Passphrase +import java.io.InputStream +import java.security.KeyPair +import java.util.* + +class BcImplementationFactory : ImplementationFactory() { + override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider = BcPGPDigestCalculatorProvider() + override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider = BcPGPContentVerifierBuilderProvider() + override val keyFingerprintCalculator: BcKeyFingerprintCalculator = BcKeyFingerprintCalculator() + + override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, + digestCalculator: PGPDigestCalculator, + passphrase: Passphrase): PBESecretKeyEncryptor = + BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator) + .build(passphrase.getChars()) + + override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + s2kCount: Int, + passphrase: Passphrase): PBESecretKeyEncryptor = + BcPBESecretKeyEncryptorBuilder( + encryptionAlgorithm.algorithmId, + getPGPDigestCalculator(hashAlgorithm), + s2kCount) + .build(passphrase.getChars()) + + override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor = + BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider) + .build(passphrase.getChars()) + + override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder = + BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) + + override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory = + BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider) + + override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory = + BcPublicKeyDataDecryptorFactory(privateKey) + + override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory = + BcSessionKeyDataDecryptorFactory(sessionKey) + + override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator = + BcPublicKeyKeyEncryptionMethodGenerator(key) + + override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator = + BcPBEKeyEncryptionMethodGenerator(passphrase.getChars()) + + override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder = + BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm) + + override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair = + BcPGPKeyPair( + publicKeyAlgorithm.algorithmId, + jceToBcKeyPair(publicKeyAlgorithm, keyPair, creationDate), + creationDate) + + override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory = BcPGPObjectFactory(inputStream) + + private fun jceToBcKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, + keyPair: KeyPair, + creationDate: Date): AsymmetricCipherKeyPair = + BcPGPKeyConverter().let { converter -> + JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair -> + AsymmetricCipherKeyPair(converter.getPublicKey(pair.publicKey), converter.getPrivateKey(pair.privateKey)) + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/ImplementationFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/ImplementationFactory.kt new file mode 100644 index 00000000..5ae653b4 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/ImplementationFactory.kt @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.implementation + +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.operator.* +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.util.Passphrase +import org.pgpainless.util.SessionKey +import java.io.InputStream +import java.security.KeyPair +import java.util.* + +abstract class ImplementationFactory { + + companion object { + @JvmStatic + private var instance: ImplementationFactory = BcImplementationFactory() + + @JvmStatic + fun getInstance() = instance + + @JvmStatic + fun setFactoryImplementation(implementation: ImplementationFactory) = apply { + instance = implementation + } + } + + abstract val pgpDigestCalculatorProvider: PGPDigestCalculatorProvider + abstract val pgpContentVerifierBuilderProvider: PGPContentVerifierBuilderProvider + abstract val keyFingerprintCalculator: KeyFingerPrintCalculator + + val v4FingerprintCalculator: PGPDigestCalculator + get() = getPGPDigestCalculator(HashAlgorithm.SHA1) + + @Throws(PGPException::class) + abstract fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, + digestCalculator: PGPDigestCalculator, + passphrase: Passphrase): PBESecretKeyEncryptor + + @Throws(PGPException::class) + abstract fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor + + @Throws(PGPException::class) + abstract fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, hashAlgorithm: HashAlgorithm, + s2kCount: Int, passphrase: Passphrase): PBESecretKeyEncryptor + + fun getPGPDigestCalculator(hashAlgorithm: HashAlgorithm): PGPDigestCalculator = + getPGPDigestCalculator(hashAlgorithm.algorithmId) + + fun getPGPDigestCalculator(hashAlgorithm: Int): PGPDigestCalculator = + pgpDigestCalculatorProvider.get(hashAlgorithm) + + fun getPGPContentSignerBuilder(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm): PGPContentSignerBuilder = + getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId) + + abstract fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder + + @Throws(PGPException::class) + abstract fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory + + abstract fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory + + fun getSessionKeyDataDecryptorFactory(sessionKey: SessionKey): SessionKeyDataDecryptorFactory = + getSessionKeyDataDecryptorFactory(PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key)) + + abstract fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory + + abstract fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator + + abstract fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator + + fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: SymmetricKeyAlgorithm): PGPDataEncryptorBuilder = + getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId) + + abstract fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder + + @Throws(PGPException::class) + abstract fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair + + fun getPGPObjectFactory(bytes: ByteArray): PGPObjectFactory = + getPGPObjectFactory(bytes.inputStream()) + + abstract fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory + + override fun toString(): String { + return javaClass.simpleName + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/JceImplementationFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/JceImplementationFactory.kt new file mode 100644 index 00000000..0684fa24 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/JceImplementationFactory.kt @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.implementation + +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.operator.* +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair +import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder +import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder +import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator +import org.bouncycastle.openpgp.operator.jcajce.JceSessionKeyDataDecryptorFactoryBuilder +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.algorithm.SymmetricKeyAlgorithm +import org.pgpainless.provider.ProviderFactory +import org.pgpainless.util.Passphrase +import java.io.InputStream +import java.security.KeyPair +import java.util.* + +class JceImplementationFactory : ImplementationFactory() { + override val pgpDigestCalculatorProvider: PGPDigestCalculatorProvider = + JcaPGPDigestCalculatorProviderBuilder() + .setProvider(ProviderFactory.getProvider()) + .build() + override val pgpContentVerifierBuilderProvider: PGPContentVerifierBuilderProvider = + JcaPGPContentVerifierBuilderProvider() + .setProvider(ProviderFactory.getProvider()) + override val keyFingerprintCalculator: KeyFingerPrintCalculator = + JcaKeyFingerprintCalculator() + .setProvider(ProviderFactory.getProvider()) + + override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, + digestCalculator: PGPDigestCalculator, + passphrase: Passphrase): PBESecretKeyEncryptor = + JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()) + + override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + s2kCount: Int, + passphrase: Passphrase): PBESecretKeyEncryptor = + JcePBESecretKeyEncryptorBuilder( + encryptionAlgorithm.algorithmId, + getPGPDigestCalculator(hashAlgorithm), + s2kCount) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()) + + override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor = + JcePBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()) + + override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder = + JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) + .setProvider(ProviderFactory.getProvider()) + + override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory = + JcePBEDataDecryptorFactoryBuilder(pgpDigestCalculatorProvider) + .setProvider(ProviderFactory.getProvider()) + .build(passphrase.getChars()) + + override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory = + JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(ProviderFactory.getProvider()) + .build(privateKey) + + override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory = + JceSessionKeyDataDecryptorFactoryBuilder() + .setProvider(ProviderFactory.getProvider()) + .build(sessionKey) + + override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator = + JcePublicKeyKeyEncryptionMethodGenerator(key) + .setProvider(ProviderFactory.getProvider()) + + override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator = + JcePBEKeyEncryptionMethodGenerator(passphrase.getChars()) + .setProvider(ProviderFactory.getProvider()) + + override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder = + JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm) + .setProvider(ProviderFactory.getProvider()) + + override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair = + JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate) + + override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory = + PGPObjectFactory(inputStream, keyFingerprintCalculator) +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyCertificationSignatureBuilderTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyCertificationSignatureBuilderTest.java index bf1cb694..2b0f4d35 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyCertificationSignatureBuilderTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyCertificationSignatureBuilderTest.java @@ -68,7 +68,7 @@ public class ThirdPartyCertificationSignatureBuilderTest { assertFalse(exportable.isExportable()); // test sig correctness - certification.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), secretKeys.getPublicKey()); + certification.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), secretKeys.getPublicKey()); assertTrue(certification.verifyCertification("Bob", bobsPublicKeys.getPublicKey())); } } From 83512236148db656e8afaf9298e28e17a0d11596 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 4 Oct 2023 14:45:45 +0200 Subject: [PATCH 139/155] Kotlin conversion: PublicKeyParameterValidationUtil --- .../PublicKeyParameterValidationUtil.java | 291 ------------------ .../util/PublicKeyParameterValidationUtil.kt | 246 +++++++++++++++ 2 files changed, 246 insertions(+), 291 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/util/PublicKeyParameterValidationUtil.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java deleted file mode 100644 index 1649f578..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java +++ /dev/null @@ -1,291 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigInteger; -import java.security.SecureRandom; - -import org.bouncycastle.bcpg.BCPGKey; -import org.bouncycastle.bcpg.DSAPublicBCPGKey; -import org.bouncycastle.bcpg.DSASecretBCPGKey; -import org.bouncycastle.bcpg.EdDSAPublicBCPGKey; -import org.bouncycastle.bcpg.EdSecretBCPGKey; -import org.bouncycastle.bcpg.ElGamalPublicBCPGKey; -import org.bouncycastle.bcpg.ElGamalSecretBCPGKey; -import org.bouncycastle.bcpg.RSAPublicBCPGKey; -import org.bouncycastle.bcpg.RSASecretBCPGKey; -import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.io.Streams; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.exception.KeyIntegrityException; -import org.pgpainless.implementation.ImplementationFactory; - -/** - * Utility class to verify keys against Key Overwriting (KO) attacks. - * This class of attacks is only possible if the attacker has access to the (encrypted) secret key material. - * To execute the attack, they would modify the unauthenticated parameters of the users public key. - * Using the modified public key in combination with the unmodified secret key material can then lead to the - * extraction of secret key parameters via weakly crafted messages. - * - * @see Key Overwriting (KO) Attacks against OpenPGP - */ -public class PublicKeyParameterValidationUtil { - - public static void verifyPublicKeyParameterIntegrity(PGPPrivateKey privateKey, PGPPublicKey publicKey) - throws KeyIntegrityException { - PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(publicKey.getAlgorithm()); - boolean valid = true; - - // Algorithm specific validations - BCPGKey key = privateKey.getPrivateKeyDataPacket(); - if (key instanceof RSASecretBCPGKey) { - valid = verifyRSAKeyIntegrity( - (RSASecretBCPGKey) key, - (RSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) - && valid; - } else if (key instanceof EdSecretBCPGKey) { - valid = verifyEdDsaKeyIntegrity( - (EdSecretBCPGKey) key, - (EdDSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) - && valid; - } else if (key instanceof DSASecretBCPGKey) { - valid = verifyDsaKeyIntegrity( - (DSASecretBCPGKey) key, - (DSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) - && valid; - } else if (key instanceof ElGamalSecretBCPGKey) { - valid = verifyElGamalKeyIntegrity( - (ElGamalSecretBCPGKey) key, - (ElGamalPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) - && valid; - } - - if (!valid) { - throw new KeyIntegrityException(); - } - - // Additional to the algorithm-specific tests further above, we also perform - // generic functionality tests with the key, such as whether it is able to decrypt encrypted data - // or verify signatures. - // These tests should be more or less constant time. - if (publicKeyAlgorithm.isSigningCapable()) { - valid = verifyCanSign(privateKey, publicKey); - } - if (publicKeyAlgorithm.isEncryptionCapable()) { - valid = verifyCanDecrypt(privateKey, publicKey) && valid; - } - - if (!valid) { - throw new KeyIntegrityException(); - } - } - - /** - * Verify that the public key can be used to successfully verify a signature made by the private key. - * @param privateKey private key - * @param publicKey public key - * @return false if signature verification fails - */ - private static boolean verifyCanSign(PGPPrivateKey privateKey, PGPPublicKey publicKey) { - SecureRandom random = new SecureRandom(); - PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(publicKey.getAlgorithm()); - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( - ImplementationFactory.getInstance().getPGPContentSignerBuilder(publicKeyAlgorithm, HashAlgorithm.SHA256) - ); - - try { - signatureGenerator.init(SignatureType.TIMESTAMP.getCode(), privateKey); - - byte[] data = new byte[512]; - random.nextBytes(data); - - signatureGenerator.update(data); - PGPSignature sig = signatureGenerator.generate(); - - sig.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), publicKey); - sig.update(data); - return sig.verify(); - } catch (PGPException e) { - return false; - } - } - - /** - * Verify that the public key can be used to encrypt a message which can successfully be - * decrypted using the private key. - * @param privateKey private key - * @param publicKey public key - * @return false if decryption of a message encrypted with the public key fails - */ - private static boolean verifyCanDecrypt(PGPPrivateKey privateKey, PGPPublicKey publicKey) { - SecureRandom random = new SecureRandom(); - PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator( - ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(SymmetricKeyAlgorithm.AES_256) - ); - encryptedDataGenerator.addMethod( - ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(publicKey)); - - byte[] data = new byte[1024]; - random.nextBytes(data); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - OutputStream outputStream = encryptedDataGenerator.open(out, new byte[1024]); - outputStream.write(data); - encryptedDataGenerator.close(); - PGPEncryptedDataList encryptedDataList = new PGPEncryptedDataList(out.toByteArray()); - PublicKeyDataDecryptorFactory decryptorFactory = - ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey); - PGPPublicKeyEncryptedData encryptedData = - (PGPPublicKeyEncryptedData) encryptedDataList.getEncryptedDataObjects().next(); - InputStream decrypted = encryptedData.getDataStream(decryptorFactory); - out = new ByteArrayOutputStream(); - Streams.pipeAll(decrypted, out); - decrypted.close(); - } catch (IOException | PGPException e) { - return false; - } - - return Arrays.constantTimeAreEqual(data, out.toByteArray()); - } - - private static boolean verifyEdDsaKeyIntegrity(EdSecretBCPGKey privateKey, EdDSAPublicBCPGKey publicKey) - throws KeyIntegrityException { - // TODO: Implement - return true; - } - - private static boolean verifyDsaKeyIntegrity(DSASecretBCPGKey privateKey, DSAPublicBCPGKey publicKey) - throws KeyIntegrityException { - // Not sure what value to put here in order to have a "robust" primality check - // I went with 40, since that's what SO recommends: - // https://stackoverflow.com/a/6330138 - final int certainty = 40; - BigInteger pG = publicKey.getG(); - BigInteger pP = publicKey.getP(); - BigInteger pQ = publicKey.getQ(); - BigInteger pY = publicKey.getY(); - BigInteger sX = privateKey.getX(); - - boolean pPrime = pP.isProbablePrime(certainty); - if (!pPrime) { - return false; - } - - boolean qPrime = pQ.isProbablePrime(certainty); - if (!qPrime) { - return false; - } - - // q > 160 bits - boolean qLarge = pQ.bitLength() > 160; - if (!qLarge) { - return false; - } - - // q divides p - 1 - boolean qDividesPminus1 = pP.subtract(BigInteger.ONE).mod(pQ).equals(BigInteger.ZERO); - if (!qDividesPminus1) { - return false; - } - - // 1 < g < p - boolean gInBounds = BigInteger.ONE.max(pG).equals(pG) && pG.max(pP).equals(pP); - if (!gInBounds) { - return false; - } - - // g^q = 1 mod p - boolean gPowXModPEquals1 = pG.modPow(pQ, pP).equals(BigInteger.ONE); - if (!gPowXModPEquals1) { - return false; - } - - // y = g^x mod p - boolean yEqualsGPowXModP = pY.equals(pG.modPow(sX, pP)); - if (!yEqualsGPowXModP) { - return false; - } - - return true; - } - - private static boolean verifyRSAKeyIntegrity(RSASecretBCPGKey secretKey, RSAPublicBCPGKey publicKey) - throws KeyIntegrityException { - // Verify that the public keys N is equal to private keys p*q - return publicKey.getModulus().equals(secretKey.getPrimeP().multiply(secretKey.getPrimeQ())); - } - - /** - * Validate ElGamal public key parameters. - * - * Original implementation by the openpgpjs authors: - * Key Overwriting (KO) Attacks against OpenPGP + */ +class PublicKeyParameterValidationUtil { + + companion object { + @JvmStatic + @Throws(KeyIntegrityException::class) + fun verifyPublicKeyParameterIntegrity(privateKey: PGPPrivateKey, publicKey: PGPPublicKey) { + val algorithm = publicKey.publicKeyAlgorithm + var valid = true + + val key = privateKey.privateKeyDataPacket + when (privateKey.privateKeyDataPacket) { + is RSASecretBCPGKey -> + valid = verifyRSAKeyIntegrity(key as RSASecretBCPGKey, publicKey.publicKeyPacket.key as RSAPublicBCPGKey) + is EdSecretBCPGKey -> + valid = verifyEdDsaKeyIntegrity(key as EdSecretBCPGKey, publicKey.publicKeyPacket.key as EdDSAPublicBCPGKey) + is DSASecretBCPGKey -> + valid = verifyDsaKeyIntegrity(key as DSASecretBCPGKey, publicKey.publicKeyPacket.key as DSAPublicBCPGKey) + is ElGamalSecretBCPGKey -> + valid = verifyElGamalKeyIntegrity(key as ElGamalSecretBCPGKey, publicKey.publicKeyPacket.key as ElGamalPublicBCPGKey) + } + + if (!valid) throw KeyIntegrityException() + + // Additional to the algorithm-specific tests further above, we also perform + // generic functionality tests with the key, such as whether it is able to decrypt encrypted data + // or verify signatures. + // These tests should be more or less constant time. + if (algorithm.isSigningCapable()) { + valid = verifyCanSign(privateKey, publicKey) + } + if (algorithm.isEncryptionCapable()) { + valid = valid and verifyCanDecrypt(privateKey, publicKey) + } + + if (!valid) throw KeyIntegrityException() + } + + @JvmStatic + @Throws(KeyIntegrityException::class) + private fun verifyRSAKeyIntegrity(secretKey: RSASecretBCPGKey, publicKey: RSAPublicBCPGKey): Boolean { + // Verify that the public keys N is equal to private keys p*q + return publicKey.modulus.equals(secretKey.primeP.multiply(secretKey.primeQ)) + } + + @JvmStatic + @Throws(KeyIntegrityException::class) + private fun verifyEdDsaKeyIntegrity(secretKey: EdSecretBCPGKey, publicKey: EdDSAPublicBCPGKey): Boolean { + // TODO: Implement + return true + } + + @JvmStatic + @Throws(KeyIntegrityException::class) + private fun verifyDsaKeyIntegrity(privateKey: DSASecretBCPGKey, publicKey: DSAPublicBCPGKey): Boolean { + // Not sure what value to put here in order to have a "robust" primality check + // I went with 40, since that's what SO recommends: + // https://stackoverflow.com/a/6330138 + val certainty = 40 + val pG = publicKey.g + val pP = publicKey.p + val pQ = publicKey.q + val pY = publicKey.y + val sX = privateKey.x + + val pPrime = pP.isProbablePrime(certainty) + if (!pPrime) { + return false + } + + val qPrime = pQ.isProbablePrime(certainty) + if (!qPrime) { + return false + } + + // q > 160 bits + val qLarge = pQ.bitLength() > 160 + if (!qLarge) { + return false + } + + // q divides p - 1 + val qDividesPminus1 = pP.subtract(BigInteger.ONE).mod(pQ) == BigInteger.ZERO + if (!qDividesPminus1) { + return false + } + + // 1 < g < p + val gInBounds = BigInteger.ONE.max(pG) == pG && pG.max(pP) == pP + if (!gInBounds) { + return false + } + + // g^q = 1 mod p + val gPowXModPEquals1 = pG.modPow(pQ, pP) == BigInteger.ONE + if (!gPowXModPEquals1) { + return false + } + + // y = g^x mod p + return pY == pG.modPow(sX, pP) + } + + /** + * Validate ElGamal public key parameters. + * + * Original implementation by the openpgpjs authors: + * Date: Tue, 10 Oct 2023 13:00:01 +0200 Subject: [PATCH 141/155] Clean up unused casts from EncryptionOptions --- .../encryption_signing/EncryptionOptions.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt index 74d2b3ca..00b1359a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt @@ -10,6 +10,7 @@ import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator import org.pgpainless.algorithm.EncryptionPurpose import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.authentication.CertificateAuthority +import org.pgpainless.encryption_signing.EncryptionOptions.EncryptionKeySelector import org.pgpainless.exception.KeyException import org.pgpainless.exception.KeyException.* import org.pgpainless.implementation.ImplementationFactory @@ -19,7 +20,6 @@ import org.pgpainless.key.info.KeyAccessor import org.pgpainless.key.info.KeyRingInfo import org.pgpainless.util.Passphrase import java.util.* -import javax.annotation.Nonnull class EncryptionOptions( @@ -144,8 +144,8 @@ class EncryptionOptions( for (subkey in subkeys) { val keyId = SubkeyIdentifier(key, subkey.keyID) - (_keyRingInfo as MutableMap)[keyId] = info - (_keyViews as MutableMap)[keyId] = KeyAccessor.ViaUserId(info, keyId, userId.toString()) + _keyRingInfo[keyId] = info + _keyViews[keyId] = KeyAccessor.ViaUserId(info, keyId, userId.toString()) addRecipientKey(key, subkey, false) } } @@ -188,8 +188,8 @@ class EncryptionOptions( for (subkey in encryptionSubkeys) { val keyId = SubkeyIdentifier(key, subkey.keyID) - (_keyRingInfo as MutableMap)[keyId] = info - (_keyViews as MutableMap)[keyId] = KeyAccessor.ViaKeyId(info, keyId) + _keyRingInfo[keyId] = info + _keyViews[keyId] = KeyAccessor.ViaKeyId(info, keyId) addRecipientKey(key, subkey, wildcardKeyId) } } @@ -197,7 +197,7 @@ class EncryptionOptions( private fun addRecipientKey(certificate: PGPPublicKeyRing, key: PGPPublicKey, wildcardKeyId: Boolean) { - (_encryptionKeyIdentifiers as MutableSet).add(SubkeyIdentifier(certificate, key.keyID)) + _encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID)) addEncryptionMethod(ImplementationFactory.getInstance() .getPublicKeyKeyEncryptionMethodGenerator(key) .also { it.setUseWildcardKeyID(wildcardKeyId) }) @@ -228,7 +228,7 @@ class EncryptionOptions( * @return this */ fun addEncryptionMethod(encryptionMethod: PGPKeyEncryptionMethodGenerator) = apply { - (_encryptionMethods as MutableSet).add(encryptionMethod) + _encryptionMethods.add(encryptionMethod) } /** From efae652a662f95f57ad28d27c44cd40c38bb77bf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Oct 2023 13:38:45 +0200 Subject: [PATCH 142/155] Kotlin conversion: CertificateValidator --- .../consumer/CertificateValidator.java | 299 ------------------ .../consumer/CertificateValidator.kt | 249 +++++++++++++++ 2 files changed, 249 insertions(+), 299 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/CertificateValidator.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/CertificateValidator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/CertificateValidator.java deleted file mode 100644 index f22e057a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/CertificateValidator.java +++ /dev/null @@ -1,299 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.consumer; - -import static org.pgpainless.signature.consumer.SignatureVerifier.verifyOnePassSignature; - -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.SignerUserID; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.key.util.KeyRingUtils; -import org.pgpainless.policy.Policy; -import org.pgpainless.signature.SignatureUtils; -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A collection of static methods that validate signing certificates (public keys) and verify signature correctness. - */ -public final class CertificateValidator { - - private CertificateValidator() { - - } - - private static final Logger LOGGER = LoggerFactory.getLogger(CertificateValidator.class); - - /** - * Check if the signing key was eligible to create the provided signature. - * - * That entails: - * - Check, if the primary key is being revoked via key-revocation signatures. - * - Check, if the keys user-ids are revoked or not bound. - * - Check, if the signing subkey is revoked or expired. - * - Check, if the signing key is not capable of signing - * - * @param signature signature - * @param signingKeyRing signing key ring - * @param policy validation policy - * @return true if the signing key was eligible to create the signature - * @throws SignatureValidationException in case of a validation constraint violation - */ - public static boolean validateCertificate(PGPSignature signature, PGPPublicKeyRing signingKeyRing, Policy policy) - throws SignatureValidationException { - - Map rejections = new ConcurrentHashMap<>(); - long keyId = SignatureUtils.determineIssuerKeyId(signature); - PGPPublicKey signingSubkey = signingKeyRing.getPublicKey(keyId); - if (signingSubkey == null) { - throw new SignatureValidationException("Provided key ring does not contain a subkey with id " + Long.toHexString(keyId)); - } - - PGPPublicKey primaryKey = signingKeyRing.getPublicKey(); - - // Key-Revocation Signatures - List directKeySignatures = new ArrayList<>(); - Iterator primaryKeyRevocationIterator = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode()); - while (primaryKeyRevocationIterator.hasNext()) { - PGPSignature revocation = primaryKeyRevocationIterator.next(); - if (revocation.getKeyID() != primaryKey.getKeyID()) { - // Revocation was not made by primary key, skip - continue; - // TODO: What about external revocation keys? - } - try { - if (SignatureVerifier.verifyKeyRevocationSignature(revocation, primaryKey, policy, signature.getCreationTime())) { - directKeySignatures.add(revocation); - } - } catch (SignatureValidationException e) { - rejections.put(revocation, e); - LOGGER.debug("Rejecting key revocation signature: {}", e.getMessage(), e); - } - } - - // Direct-Key Signatures - Iterator keySignatures = primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode()); - while (keySignatures.hasNext()) { - PGPSignature keySignature = keySignatures.next(); - if (keySignature.getKeyID() != primaryKey.getKeyID()) { - // Signature was not made by primary key, skip - continue; - } - try { - if (SignatureVerifier.verifyDirectKeySignature(keySignature, primaryKey, policy, signature.getCreationTime())) { - directKeySignatures.add(keySignature); - } - } catch (SignatureValidationException e) { - rejections.put(keySignature, e); - LOGGER.debug("Rejecting key signature: {}", e.getMessage(), e); - } - } - - Collections.sort(directKeySignatures, new SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)); - if (!directKeySignatures.isEmpty()) { - if (directKeySignatures.get(0).getSignatureType() == SignatureType.KEY_REVOCATION.getCode()) { - throw new SignatureValidationException("Primary key has been revoked."); - } - } - - // User-ID signatures (certifications, revocations) - List userIds = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(primaryKey); - Map> userIdSignatures = new ConcurrentHashMap<>(); - for (String userId : userIds) { - List signaturesOnUserId = new ArrayList<>(); - Iterator userIdSigs = primaryKey.getSignaturesForID(userId); - while (userIdSigs.hasNext()) { - PGPSignature userIdSig = userIdSigs.next(); - if (userIdSig.getKeyID() != primaryKey.getKeyID()) { - // Sig was made by external key, skip - continue; - } - try { - if (SignatureVerifier.verifySignatureOverUserId(userId, userIdSig, primaryKey, policy, signature.getCreationTime())) { - signaturesOnUserId.add(userIdSig); - } - } catch (SignatureValidationException e) { - rejections.put(userIdSig, e); - LOGGER.debug("Rejecting user-id signature: {}", e.getMessage(), e); - } - } - Collections.sort(signaturesOnUserId, new SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)); - userIdSignatures.put(userId, signaturesOnUserId); - } - - boolean anyUserIdValid = false; - boolean hasAnyUserIds = !userIdSignatures.keySet().isEmpty(); - for (String userId : userIdSignatures.keySet()) { - if (!userIdSignatures.get(userId).isEmpty()) { - PGPSignature current = userIdSignatures.get(userId).get(0); - if (current.getSignatureType() == SignatureType.CERTIFICATION_REVOCATION.getCode()) { - LOGGER.debug("User-ID '{}' is revoked.", userId); - } else { - anyUserIdValid = true; - } - } - } - - if (hasAnyUserIds && !anyUserIdValid) { - throw new SignatureValidationException("No valid user-id found.", rejections); - } - - // Specific signer user-id - SignerUserID signerUserID = SignatureSubpacketsUtil.getSignerUserID(signature); - if (signerUserID != null && policy.getSignerUserIdValidationLevel() == Policy.SignerUserIdValidationLevel.STRICT) { - List signerUserIdSigs = userIdSignatures.get(signerUserID.getID()); - if (signerUserIdSigs == null || signerUserIdSigs.isEmpty()) { - throw new SignatureValidationException("Signature was allegedly made by user-id '" + signerUserID.getID() + - "' but we have no valid signatures for that on the certificate."); - } - - PGPSignature userIdSig = signerUserIdSigs.get(0); - if (userIdSig.getSignatureType() == SignatureType.CERTIFICATION_REVOCATION.getCode()) { - throw new SignatureValidationException("Signature was made with user-id '" + signerUserID.getID() + "' which is revoked."); - } - } - - if (signingSubkey == primaryKey) { - if (!directKeySignatures.isEmpty()) { - PGPSignature directKeySignature = directKeySignatures.get(0); - KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(directKeySignature); - if (keyFlags != null && KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.SIGN_DATA)) { - return true; - } - } - } // Subkey Binding Signatures / Subkey Revocation Signatures - else { - List subkeySigs = new ArrayList<>(); - Iterator bindingRevocations = signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.getCode()); - while (bindingRevocations.hasNext()) { - PGPSignature revocation = bindingRevocations.next(); - if (revocation.getKeyID() != primaryKey.getKeyID()) { - // Subkey Revocation was not made by primary key, skip - continue; - } - try { - if (SignatureVerifier.verifySubkeyBindingRevocation(revocation, primaryKey, signingSubkey, policy, signature.getCreationTime())) { - subkeySigs.add(revocation); - } - } catch (SignatureValidationException e) { - rejections.put(revocation, e); - LOGGER.debug("Rejecting subkey revocation signature: {}", e.getMessage(), e); - } - } - - Iterator bindingSigs = signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode()); - while (bindingSigs.hasNext()) { - PGPSignature bindingSig = bindingSigs.next(); - try { - if (SignatureVerifier.verifySubkeyBindingSignature(bindingSig, primaryKey, signingSubkey, policy, signature.getCreationTime())) { - subkeySigs.add(bindingSig); - } - } catch (SignatureValidationException e) { - rejections.put(bindingSig, e); - LOGGER.debug("Rejecting subkey binding signature: {}", e.getMessage(), e); - } - } - - Collections.sort(subkeySigs, new SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)); - if (subkeySigs.isEmpty()) { - throw new SignatureValidationException("Subkey is not bound.", rejections); - } - - PGPSignature currentSig = subkeySigs.get(0); - if (currentSig.getSignatureType() == SignatureType.SUBKEY_REVOCATION.getCode()) { - throw new SignatureValidationException("Subkey is revoked."); - } - - KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(currentSig); - if (keyFlags == null) { - if (directKeySignatures.isEmpty()) { - throw new SignatureValidationException("Signature was made by key which is not capable of signing (no keyflags on binding sig, no direct-key sig)."); - } - PGPSignature directKeySig = directKeySignatures.get(0); - KeyFlags directKeyFlags = SignatureSubpacketsUtil.getKeyFlags(directKeySig); - if (directKeyFlags == null || !KeyFlag.hasKeyFlag(directKeyFlags.getFlags(), KeyFlag.SIGN_DATA)) { - throw new SignatureValidationException("Signature was made by key which is not capable of signing (no keyflags on binding sig, no SIGN flag on direct-key sig)."); - } - } else if (!KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.SIGN_DATA)) { - throw new SignatureValidationException("Signature was made by key which is not capable of signing (no SIGN flag on binding sig)."); - } - } - return true; - } - - /** - * Validate the given signing key and then verify the given signature while parsing out the signed data. - * Uninitialized means that no signed data has been read and the hash generators state has not yet been updated. - * - * @param signature uninitialized signature - * @param signedData input stream containing signed data - * @param signingKeyRing key ring containing signing key - * @param policy validation policy - * @param validationDate date of validation - * @return true if the signature is valid, false otherwise - * @throws SignatureValidationException for validation constraint violations - */ - public static boolean validateCertificateAndVerifyUninitializedSignature(PGPSignature signature, - InputStream signedData, - PGPPublicKeyRing signingKeyRing, - Policy policy, - Date validationDate) - throws SignatureValidationException { - validateCertificate(signature, signingKeyRing, policy); - long keyId = SignatureUtils.determineIssuerKeyId(signature); - return SignatureVerifier.verifyUninitializedSignature(signature, signedData, signingKeyRing.getPublicKey(keyId), policy, validationDate); - } - - /** - * Validate the signing key and the given initialized signature. - * Initialized means that the signatures hash generator has already been updated by reading the signed data completely. - * - * @param signature initialized signature - * @param verificationKeys key ring containing the verification key - * @param policy validation policy - * @return true if the signature is valid, false otherwise - * @throws SignatureValidationException in case of a validation constraint violation - */ - public static boolean validateCertificateAndVerifyInitializedSignature(PGPSignature signature, PGPPublicKeyRing verificationKeys, Policy policy) - throws SignatureValidationException { - validateCertificate(signature, verificationKeys, policy); - long keyId = SignatureUtils.determineIssuerKeyId(signature); - PGPPublicKey signingKey = verificationKeys.getPublicKey(keyId); - SignatureVerifier.verifyInitializedSignature(signature, signingKey, policy, signature.getCreationTime()); - return true; - } - - /** - * Validate the signing key certificate and the given {@link OnePassSignatureCheck}. - * - * @param onePassSignature corresponding one-pass-signature - * @param policy policy - * @return true if the certificate is valid and the signature is correct, false otherwise. - * @throws SignatureValidationException in case of a validation error - */ - public static boolean validateCertificateAndVerifyOnePassSignature(OnePassSignatureCheck onePassSignature, Policy policy) - throws SignatureValidationException { - PGPSignature signature = onePassSignature.getSignature(); - validateCertificate(signature, onePassSignature.getVerificationKeys(), policy); - PGPPublicKey signingKey = onePassSignature.getVerificationKeys().getPublicKey(signature.getKeyID()); - verifyOnePassSignature(signature, signingKey, onePassSignature, policy); - return true; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt new file mode 100644 index 00000000..126803d4 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt @@ -0,0 +1,249 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import openpgp.openPgpKeyId +import org.bouncycastle.extensions.issuerKeyId +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPPublicKeyRing +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.algorithm.SignatureType +import org.pgpainless.exception.SignatureValidationException +import org.pgpainless.key.util.KeyRingUtils +import org.pgpainless.policy.Policy +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil +import org.slf4j.LoggerFactory +import java.io.InputStream +import java.util.* + +/** + * A collection of static methods that validate signing certificates (public keys) and verify signature correctness. + */ +class CertificateValidator { + + companion object { + + @JvmStatic + private val LOGGER = LoggerFactory.getLogger(CertificateValidator::class.java) + + /** + * Check if the signing key was eligible to create the provided signature. + * + * That entails: + * - Check, if the primary key is being revoked via key-revocation signatures. + * - Check, if the keys user-ids are revoked or not bound. + * - Check, if the signing subkey is revoked or expired. + * - Check, if the signing key is not capable of signing + * + * @param signature signature + * @param signingKeyRing signing key ring + * @param policy validation policy + * @return true if the signing key was eligible to create the signature + * @throws SignatureValidationException in case of a validation constraint violation + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun validateCertificate(signature: PGPSignature, + signingKeyRing: PGPPublicKeyRing, + policy: Policy = PGPainless.getPolicy()): Boolean { + val signingSubkey: PGPPublicKey = signingKeyRing.getPublicKey(signature.issuerKeyId) + ?: throw SignatureValidationException("Provided key ring does not contain a subkey with id ${signature.issuerKeyId.openPgpKeyId()}.") + val primaryKey = signingKeyRing.publicKey!! + val directKeyAndRevSigs = mutableListOf() + val rejections = mutableMapOf() + // revocations + primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.code).asSequence() + .filter { it.issuerKeyId == primaryKey.keyID } // We do not support external rev keys + .forEach { + try { + if (SignatureVerifier.verifyKeyRevocationSignature(it, primaryKey, policy, signature.creationTime)) { + directKeyAndRevSigs.add(it) + } + } catch (e: SignatureValidationException) { + rejections[it] = e + LOGGER.debug("Rejecting key revocation signature: ${e.message}", e) + } + } + + // direct-key sigs + primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.code).asSequence() + .filter { it.issuerKeyId == primaryKey.keyID } + .forEach { + try { + if (SignatureVerifier.verifyDirectKeySignature(it, primaryKey, policy, signature.creationTime)) { + directKeyAndRevSigs.add(it) + } + } catch (e: SignatureValidationException) { + rejections[it] = e + LOGGER.debug("Rejecting key signature: ${e.message}, e") + } + } + + directKeyAndRevSigs.sortWith(SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)) + if (directKeyAndRevSigs.isNotEmpty()) { + if (directKeyAndRevSigs[0].signatureType == SignatureType.KEY_REVOCATION.code) { + throw SignatureValidationException("Primary key has been revoked.") + } + } + + // UserID signatures + val userIdSignatures = mutableMapOf>() + KeyRingUtils.getUserIdsIgnoringInvalidUTF8(primaryKey).forEach { userId -> + buildList { + primaryKey.getSignaturesForID(userId) + .asSequence() + .filter { it.issuerKeyId == primaryKey.keyID } + .forEach { uidSig -> + try { + if (SignatureVerifier.verifySignatureOverUserId(userId, uidSig, primaryKey, policy, signature.creationTime)) { + add(uidSig) + } + } catch (e: SignatureValidationException) { + rejections[uidSig] = e + LOGGER.debug("Rejecting user-id signature: ${e.message}", e) + } + } + }.sortedWith(SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)) + .let { userIdSignatures[userId] = it } + } + + val hasAnyUserIds = userIdSignatures.isNotEmpty() + val isAnyUserIdValid = userIdSignatures.any { entry -> + entry.value.isNotEmpty() && entry.value[0].signatureType != SignatureType.CERTIFICATION_REVOCATION.code + } + + if (hasAnyUserIds && !isAnyUserIdValid) { + throw SignatureValidationException("No valid user-id found.", rejections) + } + + // Specific signer user-id + if (policy.signerUserIdValidationLevel == Policy.SignerUserIdValidationLevel.STRICT) { + SignatureSubpacketsUtil.getSignerUserID(signature)?.let { + if (userIdSignatures[it.id] == null || userIdSignatures[it.id]!!.isEmpty()) { + throw SignatureValidationException("Signature was allegedly made by user-id '${it.id}'," + + " but we have no valid signatures for that on the certificate.") + } + + if (userIdSignatures[it.id]!![0].signatureType == SignatureType.CERTIFICATION_REVOCATION.code) { + throw SignatureValidationException("Signature was made with user-id '${it.id}' which is revoked.") + } + } + } + + if (signingSubkey.keyID == primaryKey.keyID) { // signing key is primary key + if (directKeyAndRevSigs.isNotEmpty()) { + val directKeySig = directKeyAndRevSigs[0]!! + val flags = SignatureSubpacketsUtil.getKeyFlags(directKeySig) + if (flags != null && KeyFlag.hasKeyFlag(flags.flags, KeyFlag.SIGN_DATA)) { + return true + } + } + } else { // signing key is subkey + val subkeySigs = mutableListOf() + signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.code).asSequence() + .filter { it.issuerKeyId == primaryKey.keyID } + .forEach { + try { + if (SignatureVerifier.verifySubkeyBindingRevocation(it, primaryKey, signingSubkey, policy, signature.creationTime)) { + subkeySigs.add(it) + } + } catch (e : SignatureValidationException) { + rejections[it] = e + LOGGER.debug("REjecting subkey revocation signature: ${e.message}", e) + } + } + + signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.code).asSequence() + .forEach { + try { + if (SignatureVerifier.verifySubkeyBindingSignature(it, primaryKey, signingSubkey, policy, signature.creationTime)) { + subkeySigs.add(it) + } + } catch (e : SignatureValidationException) { + rejections[it] = e + LOGGER.debug("Rejecting subkey binding signature: ${e.message}", e) + } + } + + subkeySigs.sortWith(SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)) + if (subkeySigs.isEmpty()) { + throw SignatureValidationException("Subkey is not bound.", rejections) + } + + if (subkeySigs[0].signatureType == SignatureType.SUBKEY_REVOCATION.code) { + throw SignatureValidationException("Subkey is revoked.") + } + + val keyFlags = SignatureSubpacketsUtil.getKeyFlags(subkeySigs[0]) + if (keyFlags == null || !KeyFlag.hasKeyFlag(keyFlags.flags, KeyFlag.SIGN_DATA)) { + throw SignatureValidationException("Signature was made by key which is not capable of signing (no keyflag).") + } + } + return true + } + + /** + * Validate the given signing key and then verify the given signature while parsing out the signed data. + * Uninitialized means that no signed data has been read and the hash generators state has not yet been updated. + * + * @param signature uninitialized signature + * @param signedData input stream containing signed data + * @param signingKeyRing key ring containing signing key + * @param policy validation policy + * @param validationDate date of validation + * @return true if the signature is valid, false otherwise + * @throws SignatureValidationException for validation constraint violations + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun validateCertificateAndVerifyUninitializedSignature(signature: PGPSignature, + signedData: InputStream, + signingKeyRing: PGPPublicKeyRing, + policy: Policy, + referenceTime: Date = signature.creationTime): Boolean { + return validateCertificate(signature, signingKeyRing, policy) + && SignatureVerifier.verifyUninitializedSignature(signature, signedData, signingKeyRing.getPublicKey(signature.issuerKeyId)!!, policy, referenceTime) + } + + /** + * Validate the signing key and the given initialized signature. + * Initialized means that the signatures hash generator has already been updated by reading the signed data completely. + * + * @param signature initialized signature + * @param verificationKeys key ring containing the verification key + * @param policy validation policy + * @return true if the signature is valid, false otherwise + * @throws SignatureValidationException in case of a validation constraint violation + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun validateCertificateAndVerifyInitializedSignature(signature: PGPSignature, + verificationKeys: PGPPublicKeyRing, + policy: Policy): Boolean { + return validateCertificate(signature, verificationKeys, policy) && + SignatureVerifier.verifyInitializedSignature(signature, verificationKeys.getPublicKey(signature.issuerKeyId), policy, signature.creationTime) + } + + /** + * Validate the signing key certificate and the given [OnePassSignatureCheck]. + * + * @param onePassSignature corresponding one-pass-signature + * @param policy policy + * @return true if the certificate is valid and the signature is correct, false otherwise. + * @throws SignatureValidationException in case of a validation error + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun validateCertificateAndVerifyOnePassSignature(onePassSignature: OnePassSignatureCheck, + policy: Policy): Boolean { + return validateCertificate(onePassSignature.signature!!, onePassSignature.verificationKeys, policy) && + SignatureVerifier.verifyOnePassSignature(onePassSignature.signature!!, + onePassSignature.verificationKeys.getPublicKey(onePassSignature.signature!!.issuerKeyId), + onePassSignature, policy) + } + } +} \ No newline at end of file From 70e1b40cd20e8a3d5a5caff899b412fe6b9f2c1f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Oct 2023 13:41:50 +0200 Subject: [PATCH 143/155] Fix ArmorUtil header --- .../src/main/kotlin/org/pgpainless/util/ArmorUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt index c8ff4da8..53bdca0b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 Paul Schaub // -// SPDX-License-Identifier: Apache-2.0package org.pgpainless.util +// SPDX-License-Identifier: Apache-2.0 package org.pgpainless.util From 4fc513fa25c9c5a332e868f8d6bcba32642e8dbe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Oct 2023 18:58:37 +0200 Subject: [PATCH 144/155] Kotlin conversion: SignatureCreationDateComparator, SignatureValidityComparator --- .../SignatureCreationDateComparator.java | 53 ------------------- .../consumer/SignatureValidityComparator.java | 53 ------------------- .../SignatureCreationDateComparator.kt | 34 ++++++++++++ .../consumer/SignatureValidityComparator.kt | 31 +++++++++++ 4 files changed, 65 insertions(+), 106 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureCreationDateComparator.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidityComparator.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCreationDateComparator.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidityComparator.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureCreationDateComparator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureCreationDateComparator.java deleted file mode 100644 index 7996e33b..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureCreationDateComparator.java +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.consumer; - -import java.util.Comparator; - -import org.bouncycastle.openpgp.PGPSignature; - -/** - * Comparator which can be used to sort signatures with regard to their creation time. - */ -public class SignatureCreationDateComparator implements Comparator { - - public static final Order DEFAULT_ORDER = Order.OLD_TO_NEW; - - public enum Order { - /** - * Oldest signatures first. - */ - OLD_TO_NEW, - - /** - * Newest signatures first. - */ - NEW_TO_OLD - } - - private final Order order; - - /** - * Create a new comparator which sorts signatures old to new. - */ - public SignatureCreationDateComparator() { - this(DEFAULT_ORDER); - } - - /** - * Create a new comparator which sorts signatures according to the passed ordering. - * @param order ordering - */ - public SignatureCreationDateComparator(Order order) { - this.order = order; - } - - @Override - public int compare(PGPSignature one, PGPSignature two) { - return order == Order.OLD_TO_NEW - ? one.getCreationTime().compareTo(two.getCreationTime()) - : two.getCreationTime().compareTo(one.getCreationTime()); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidityComparator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidityComparator.java deleted file mode 100644 index 94ebd5b2..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidityComparator.java +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.consumer; - -import java.util.Comparator; - -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.signature.SignatureUtils; - -/** - * Comparator which sorts signatures based on an ordering and on revocation hardness. - * - * If a list of signatures gets ordered using this comparator, hard revocations will always - * come first. - * Further, signatures are ordered by date according to the {@link SignatureCreationDateComparator.Order}. - */ -public class SignatureValidityComparator implements Comparator { - - private final SignatureCreationDateComparator creationDateComparator; - - /** - * Create a new {@link SignatureValidityComparator} which orders signatures the oldest first. - * Still, hard revocations will come first. - */ - public SignatureValidityComparator() { - this(SignatureCreationDateComparator.DEFAULT_ORDER); - } - - /** - * Create a new {@link SignatureValidityComparator} which orders signatures following the passed ordering. - * Still, hard revocations will come first. - * - * @param order order of creation dates - */ - public SignatureValidityComparator(SignatureCreationDateComparator.Order order) { - this.creationDateComparator = new SignatureCreationDateComparator(order); - } - - @Override - public int compare(PGPSignature one, PGPSignature two) { - boolean oneIsHard = SignatureUtils.isHardRevocation(one); - boolean twoIsHard = SignatureUtils.isHardRevocation(two); - - // both have same "hardness", so compare creation time - if (oneIsHard == twoIsHard) { - return creationDateComparator.compare(one, two); - } - // favor the "harder" signature - return oneIsHard ? -1 : 1; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCreationDateComparator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCreationDateComparator.kt new file mode 100644 index 00000000..75cd274c --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCreationDateComparator.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import org.bouncycastle.openpgp.PGPSignature + +/** + * Create a new comparator which sorts signatures according to the passed ordering. + * @param order ordering + */ +class SignatureCreationDateComparator( + private val order: Order = Order.OLD_TO_NEW +) : Comparator { + + enum class Order { + /** + * Oldest signatures first. + */ + OLD_TO_NEW, + /** + * Newest signatures first. + */ + NEW_TO_OLD + } + + override fun compare(one: PGPSignature, two: PGPSignature): Int { + return when(order) { + Order.OLD_TO_NEW -> one.creationTime.compareTo(two.creationTime) + Order.NEW_TO_OLD -> two.creationTime.compareTo(one.creationTime) + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidityComparator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidityComparator.kt new file mode 100644 index 00000000..f2b586ae --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidityComparator.kt @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import org.bouncycastle.extensions.isHardRevocation +import org.bouncycastle.openpgp.PGPSignature + +/** + * Comparator which sorts signatures based on an ordering and on revocation hardness. + * + * If a list of signatures gets ordered using this comparator, hard revocations will always + * come first. + * Further, signatures are ordered by date according to the [SignatureCreationDateComparator.Order]. + */ +class SignatureValidityComparator( + order: SignatureCreationDateComparator.Order = SignatureCreationDateComparator.Order.OLD_TO_NEW +) : Comparator { + + private val creationDateComparator: SignatureCreationDateComparator = SignatureCreationDateComparator(order) + override fun compare(one: PGPSignature, two: PGPSignature): Int { + return if (one.isHardRevocation == two.isHardRevocation) { + // Both have the same hardness, so compare creation time + creationDateComparator.compare(one, two) + } + // else favor the "harder" signature + else if (one.isHardRevocation) -1 else 1 + } + +} \ No newline at end of file From 0effc84fac73a99fdff183e83b89c6f4f47bcf4e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 20 Oct 2023 14:10:37 +0200 Subject: [PATCH 145/155] Kotlin conversion: SignatureSubpackets + subclasses --- .../subpackets/BaseSignatureSubpackets.java | 140 ---- .../subpackets/CertificationSubpackets.java | 12 - .../RevocationSignatureSubpackets.java | 26 - .../subpackets/SelfSignatureSubpackets.java | 98 --- .../SignatureSubpacketCallback.java | 26 - .../subpackets/SignatureSubpackets.java | 735 ------------------ .../subpackets/SignatureSubpacketsHelper.java | 4 +- .../src/main/kotlin/openpgp/DateExtensions.kt | 12 +- .../secretkeyring/SecretKeyRingEditor.kt | 6 +- .../subpackets/BaseSignatureSubpackets.kt | 128 +++ .../subpackets/CertificationSubpackets.kt | 11 + .../RevocationSignatureSubpackets.kt | 21 + .../subpackets/SelfSignatureSubpackets.kt | 84 ++ .../subpackets/SignatureSubpacketCallback.kt | 26 + .../subpackets/SignatureSubpackets.kt | 491 ++++++++++++ 15 files changed, 777 insertions(+), 1043 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/CertificationSubpackets.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpackets.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/CertificationSubpackets.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.java deleted file mode 100644 index 09fe1a99..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.java +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -import java.io.IOException; -import java.net.URL; -import java.util.Date; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.sig.EmbeddedSignature; -import org.bouncycastle.bcpg.sig.Exportable; -import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint; -import org.bouncycastle.bcpg.sig.IssuerFingerprint; -import org.bouncycastle.bcpg.sig.IssuerKeyID; -import org.bouncycastle.bcpg.sig.NotationData; -import org.bouncycastle.bcpg.sig.PolicyURI; -import org.bouncycastle.bcpg.sig.RegularExpression; -import org.bouncycastle.bcpg.sig.Revocable; -import org.bouncycastle.bcpg.sig.SignatureCreationTime; -import org.bouncycastle.bcpg.sig.SignatureExpirationTime; -import org.bouncycastle.bcpg.sig.SignatureTarget; -import org.bouncycastle.bcpg.sig.SignerUserID; -import org.bouncycastle.bcpg.sig.TrustSignature; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.PublicKeyAlgorithm; - -public interface BaseSignatureSubpackets { - - interface Callback extends SignatureSubpacketCallback { - - } - - /** - * Add both an {@link IssuerKeyID} and {@link IssuerFingerprint} subpacket pointing to the given key. - * - * @param key key - * @return this - * - * @deprecated this method MUST NOT be used for OpenPGP v6, since v6 signatures MUST NOT contain any - * {@link IssuerKeyID} packets. - */ - BaseSignatureSubpackets setIssuerFingerprintAndKeyId(PGPPublicKey key); - - BaseSignatureSubpackets setIssuerKeyId(long keyId); - - BaseSignatureSubpackets setIssuerKeyId(boolean isCritical, long keyId); - - BaseSignatureSubpackets setIssuerKeyId(@Nullable IssuerKeyID issuerKeyID); - - BaseSignatureSubpackets setIssuerFingerprint(@Nonnull PGPPublicKey key); - - BaseSignatureSubpackets setIssuerFingerprint(boolean isCritical, @Nonnull PGPPublicKey key); - - BaseSignatureSubpackets setIssuerFingerprint(@Nullable IssuerFingerprint fingerprint); - - BaseSignatureSubpackets setSignatureCreationTime(@Nonnull Date creationTime); - - BaseSignatureSubpackets setSignatureCreationTime(boolean isCritical, @Nonnull Date creationTime); - - BaseSignatureSubpackets setSignatureCreationTime(@Nullable SignatureCreationTime signatureCreationTime); - - BaseSignatureSubpackets setSignatureExpirationTime(@Nonnull Date creationTime, @Nonnull Date expirationTime); - - BaseSignatureSubpackets setSignatureExpirationTime(boolean isCritical, @Nonnull Date creationTime, @Nonnull Date expirationTime); - - BaseSignatureSubpackets setSignatureExpirationTime(boolean isCritical, long seconds); - - BaseSignatureSubpackets setSignatureExpirationTime(@Nullable SignatureExpirationTime expirationTime); - - BaseSignatureSubpackets setSignerUserId(@Nonnull String userId); - - BaseSignatureSubpackets setSignerUserId(boolean isCritical, @Nonnull String userId); - - BaseSignatureSubpackets setSignerUserId(@Nullable SignerUserID signerUserId); - - BaseSignatureSubpackets addNotationData(boolean isCritical, @Nonnull String notationName, @Nonnull String notationValue); - - BaseSignatureSubpackets addNotationData(boolean isCritical, boolean isHumanReadable, @Nonnull String notationName, @Nonnull String notationValue); - - BaseSignatureSubpackets addNotationData(@Nonnull NotationData notationData); - - BaseSignatureSubpackets clearNotationData(); - - BaseSignatureSubpackets addIntendedRecipientFingerprint(@Nonnull PGPPublicKey recipient); - - BaseSignatureSubpackets addIntendedRecipientFingerprint(boolean isCritical, @Nonnull PGPPublicKey recipient); - - BaseSignatureSubpackets addIntendedRecipientFingerprint(IntendedRecipientFingerprint intendedRecipientFingerprint); - - BaseSignatureSubpackets clearIntendedRecipientFingerprints(); - - BaseSignatureSubpackets setExportable(boolean isExportable); - - BaseSignatureSubpackets setExportable(boolean isCritical, boolean isExportable); - - BaseSignatureSubpackets setExportable(@Nullable Exportable exportable); - - BaseSignatureSubpackets setPolicyUrl(@Nonnull URL policyUrl); - - BaseSignatureSubpackets setPolicyUrl(boolean isCritical, @Nonnull URL policyUrl); - - BaseSignatureSubpackets setPolicyUrl(@Nullable PolicyURI policyUrl); - - BaseSignatureSubpackets setRegularExpression(@Nonnull String regex); - - BaseSignatureSubpackets setRegularExpression(boolean isCritical, @Nonnull String regex); - - BaseSignatureSubpackets setRegularExpression(@Nullable RegularExpression regex); - - BaseSignatureSubpackets setRevocable(boolean revocable); - - BaseSignatureSubpackets setRevocable(boolean isCritical, boolean isRevocable); - - BaseSignatureSubpackets setRevocable(@Nullable Revocable revocable); - - BaseSignatureSubpackets setSignatureTarget(@Nonnull PublicKeyAlgorithm keyAlgorithm, @Nonnull HashAlgorithm hashAlgorithm, @Nonnull byte[] hashData); - - BaseSignatureSubpackets setSignatureTarget(boolean isCritical, @Nonnull PublicKeyAlgorithm keyAlgorithm, @Nonnull HashAlgorithm hashAlgorithm, @Nonnull byte[] hashData); - - BaseSignatureSubpackets setSignatureTarget(@Nullable SignatureTarget signatureTarget); - - BaseSignatureSubpackets setTrust(int depth, int amount); - - BaseSignatureSubpackets setTrust(boolean isCritical, int depth, int amount); - - BaseSignatureSubpackets setTrust(@Nullable TrustSignature trust); - - BaseSignatureSubpackets addEmbeddedSignature(@Nonnull PGPSignature signature) throws IOException; - - BaseSignatureSubpackets addEmbeddedSignature(boolean isCritical, @Nonnull PGPSignature signature) throws IOException; - - BaseSignatureSubpackets addEmbeddedSignature(@Nonnull EmbeddedSignature embeddedSignature); - - BaseSignatureSubpackets clearEmbeddedSignatures(); -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/CertificationSubpackets.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/CertificationSubpackets.java deleted file mode 100644 index 24614882..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/CertificationSubpackets.java +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -public interface CertificationSubpackets extends BaseSignatureSubpackets { - - interface Callback extends SignatureSubpacketCallback { - - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.java deleted file mode 100644 index 358437dc..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.sig.RevocationReason; -import org.pgpainless.key.util.RevocationAttributes; - -public interface RevocationSignatureSubpackets extends BaseSignatureSubpackets { - - interface Callback extends SignatureSubpacketCallback { - - } - - RevocationSignatureSubpackets setRevocationReason(RevocationAttributes revocationAttributes); - - RevocationSignatureSubpackets setRevocationReason(boolean isCritical, RevocationAttributes revocationAttributes); - - RevocationSignatureSubpackets setRevocationReason(boolean isCritical, RevocationAttributes.Reason reason, @Nonnull String description); - - RevocationSignatureSubpackets setRevocationReason(@Nullable RevocationReason reason); -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.java deleted file mode 100644 index 02cc5e93..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.java +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -import java.util.Date; -import java.util.List; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.sig.Features; -import org.bouncycastle.bcpg.sig.KeyExpirationTime; -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.PreferredAlgorithms; -import org.bouncycastle.bcpg.sig.PrimaryUserID; -import org.bouncycastle.bcpg.sig.RevocationKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.Feature; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; - -public interface SelfSignatureSubpackets extends BaseSignatureSubpackets { - - interface Callback extends SignatureSubpacketCallback { - - } - - SelfSignatureSubpackets setKeyFlags(KeyFlag... keyFlags); - - default SelfSignatureSubpackets setKeyFlags(List keyFlags) { - KeyFlag[] flags = keyFlags.toArray(new KeyFlag[0]); - return setKeyFlags(flags); - } - - SelfSignatureSubpackets setKeyFlags(boolean isCritical, KeyFlag... keyFlags); - - SelfSignatureSubpackets setKeyFlags(@Nullable KeyFlags keyFlags); - - SelfSignatureSubpackets setPrimaryUserId(); - - SelfSignatureSubpackets setPrimaryUserId(boolean isCritical); - - SelfSignatureSubpackets setPrimaryUserId(@Nullable PrimaryUserID primaryUserId); - - SelfSignatureSubpackets setKeyExpirationTime(@Nonnull PGPPublicKey key, @Nonnull Date keyExpirationTime); - - SelfSignatureSubpackets setKeyExpirationTime(@Nonnull Date keyCreationTime, @Nonnull Date keyExpirationTime); - - SelfSignatureSubpackets setKeyExpirationTime(boolean isCritical, @Nonnull Date keyCreationTime, @Nonnull Date keyExpirationTime); - - SelfSignatureSubpackets setKeyExpirationTime(boolean isCritical, long secondsFromCreationToExpiration); - - SelfSignatureSubpackets setKeyExpirationTime(@Nullable KeyExpirationTime keyExpirationTime); - - SelfSignatureSubpackets setPreferredCompressionAlgorithms(CompressionAlgorithm... algorithms); - - SelfSignatureSubpackets setPreferredCompressionAlgorithms(Set algorithms); - - SelfSignatureSubpackets setPreferredCompressionAlgorithms(boolean isCritical, Set algorithms); - - SelfSignatureSubpackets setPreferredCompressionAlgorithms(@Nullable PreferredAlgorithms algorithms); - - SelfSignatureSubpackets setPreferredSymmetricKeyAlgorithms(SymmetricKeyAlgorithm... algorithms); - - SelfSignatureSubpackets setPreferredSymmetricKeyAlgorithms(Set algorithms); - - SelfSignatureSubpackets setPreferredSymmetricKeyAlgorithms(boolean isCritical, Set algorithms); - - SelfSignatureSubpackets setPreferredSymmetricKeyAlgorithms(@Nullable PreferredAlgorithms algorithms); - - SelfSignatureSubpackets setPreferredHashAlgorithms(HashAlgorithm... algorithms); - - SelfSignatureSubpackets setPreferredHashAlgorithms(Set algorithms); - - SelfSignatureSubpackets setPreferredHashAlgorithms(boolean isCritical, Set algorithms); - - SelfSignatureSubpackets setPreferredHashAlgorithms(@Nullable PreferredAlgorithms algorithms); - - SelfSignatureSubpackets addRevocationKey(@Nonnull PGPPublicKey revocationKey); - - SelfSignatureSubpackets addRevocationKey(boolean isCritical, @Nonnull PGPPublicKey revocationKey); - - SelfSignatureSubpackets addRevocationKey(boolean isCritical, boolean isSensitive, @Nonnull PGPPublicKey revocationKey); - - SelfSignatureSubpackets addRevocationKey(@Nonnull RevocationKey revocationKey); - - SelfSignatureSubpackets clearRevocationKeys(); - - SelfSignatureSubpackets setFeatures(Feature... features); - - SelfSignatureSubpackets setFeatures(boolean isCritical, Feature... features); - - SelfSignatureSubpackets setFeatures(@Nullable Features features); -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.java deleted file mode 100644 index 11650ea3..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -public interface SignatureSubpacketCallback { - - /** - * Callback method that can be used to modify the hashed subpackets of a signature. - * - * @param hashedSubpackets hashed subpackets - */ - default void modifyHashedSubpackets(S hashedSubpackets) { - - } - - /** - * Callback method that can be used to modify the unhashed subpackets of a signature. - * - * @param unhashedSubpackets unhashed subpackets - */ - default void modifyUnhashedSubpackets(S unhashedSubpackets) { - - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpackets.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpackets.java deleted file mode 100644 index d0466b6a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpackets.java +++ /dev/null @@ -1,735 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.SignatureSubpacket; -import org.bouncycastle.bcpg.SignatureSubpacketTags; -import org.bouncycastle.bcpg.sig.EmbeddedSignature; -import org.bouncycastle.bcpg.sig.Exportable; -import org.bouncycastle.bcpg.sig.Features; -import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint; -import org.bouncycastle.bcpg.sig.IssuerFingerprint; -import org.bouncycastle.bcpg.sig.IssuerKeyID; -import org.bouncycastle.bcpg.sig.KeyExpirationTime; -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.NotationData; -import org.bouncycastle.bcpg.sig.PolicyURI; -import org.bouncycastle.bcpg.sig.PreferredAlgorithms; -import org.bouncycastle.bcpg.sig.PrimaryUserID; -import org.bouncycastle.bcpg.sig.RegularExpression; -import org.bouncycastle.bcpg.sig.Revocable; -import org.bouncycastle.bcpg.sig.RevocationKey; -import org.bouncycastle.bcpg.sig.RevocationReason; -import org.bouncycastle.bcpg.sig.SignatureCreationTime; -import org.bouncycastle.bcpg.sig.SignatureExpirationTime; -import org.bouncycastle.bcpg.sig.SignatureTarget; -import org.bouncycastle.bcpg.sig.SignerUserID; -import org.bouncycastle.bcpg.sig.TrustSignature; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.Feature; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.key.util.RevocationAttributes; - -public class SignatureSubpackets - implements BaseSignatureSubpackets, SelfSignatureSubpackets, CertificationSubpackets, RevocationSignatureSubpackets { - - private SignatureCreationTime signatureCreationTime; - private SignatureExpirationTime signatureExpirationTime; - private IssuerKeyID issuerKeyID; - private IssuerFingerprint issuerFingerprint; - private final List notationDataList = new ArrayList<>(); - private final List intendedRecipientFingerprintList = new ArrayList<>(); - private final List revocationKeyList = new ArrayList<>(); - private Exportable exportable; - private SignatureTarget signatureTarget; - private Features features; - private KeyFlags keyFlags; - private TrustSignature trust; - private PreferredAlgorithms preferredCompressionAlgorithms; - private PreferredAlgorithms preferredSymmetricKeyAlgorithms; - private PreferredAlgorithms preferredHashAlgorithms; - private final List embeddedSignatureList = new ArrayList<>(); - private SignerUserID signerUserId; - private KeyExpirationTime keyExpirationTime; - private PolicyURI policyURI; - private PrimaryUserID primaryUserId; - private RegularExpression regularExpression; - private Revocable revocable; - private RevocationReason revocationReason; - private final List residualSubpackets = new ArrayList<>(); - - public SignatureSubpackets() { - - } - - public interface Callback extends SignatureSubpacketCallback { - - } - - public static SignatureSubpackets refreshHashedSubpackets(PGPPublicKey issuer, PGPSignature oldSignature) { - return createHashedSubpacketsFrom(issuer, oldSignature.getHashedSubPackets()); - } - - public static SignatureSubpackets refreshUnhashedSubpackets(PGPSignature oldSignature) { - return createSubpacketsFrom(oldSignature.getUnhashedSubPackets()); - } - - public static SignatureSubpackets createHashedSubpacketsFrom(PGPPublicKey issuer, PGPSignatureSubpacketVector base) { - SignatureSubpackets wrapper = createSubpacketsFrom(base); - wrapper.setIssuerFingerprintAndKeyId(issuer); - return wrapper; - } - - public static SignatureSubpackets createSubpacketsFrom(PGPSignatureSubpacketVector base) { - SignatureSubpackets wrapper = new SignatureSubpackets(); - SignatureSubpacketsHelper.applyFrom(base, wrapper); - return wrapper; - } - - public static SignatureSubpackets createHashedSubpackets(PGPPublicKey issuer) { - SignatureSubpackets wrapper = new SignatureSubpackets(); - wrapper.setIssuerFingerprintAndKeyId(issuer); - return wrapper; - } - - public static SignatureSubpackets createEmptySubpackets() { - return new SignatureSubpackets(); - } - - @Override - public SignatureSubpackets setIssuerFingerprintAndKeyId(PGPPublicKey key) { - setIssuerKeyId(key.getKeyID()); - setIssuerFingerprint(key); - return this; - } - - @Override - public SignatureSubpackets setIssuerKeyId(long keyId) { - return setIssuerKeyId(false, keyId); - } - - @Override - public SignatureSubpackets setIssuerKeyId(boolean isCritical, long keyId) { - return setIssuerKeyId(new IssuerKeyID(isCritical, keyId)); - } - - @Override - public SignatureSubpackets setIssuerKeyId(@Nullable IssuerKeyID issuerKeyID) { - this.issuerKeyID = issuerKeyID; - return this; - } - - public IssuerKeyID getIssuerKeyIdSubpacket() { - return issuerKeyID; - } - - @Override - public SignatureSubpackets setIssuerFingerprint(@Nonnull PGPPublicKey key) { - return setIssuerFingerprint(false, key); - } - - @Override - public SignatureSubpackets setIssuerFingerprint(boolean isCritical, @Nonnull PGPPublicKey key) { - return setIssuerFingerprint(new IssuerFingerprint(isCritical, key.getVersion(), key.getFingerprint())); - } - - @Override - public SignatureSubpackets setIssuerFingerprint(@Nullable IssuerFingerprint fingerprint) { - this.issuerFingerprint = fingerprint; - return this; - } - - public IssuerFingerprint getIssuerFingerprintSubpacket() { - return issuerFingerprint; - } - - @Override - public SignatureSubpackets setKeyFlags(KeyFlag... keyFlags) { - return setKeyFlags(true, keyFlags); - } - - @Override - public SignatureSubpackets setKeyFlags(boolean isCritical, KeyFlag... keyFlags) { - int bitmask = KeyFlag.toBitmask(keyFlags); - return setKeyFlags(new KeyFlags(isCritical, bitmask)); - } - - @Override - public SignatureSubpackets setKeyFlags(@Nullable KeyFlags keyFlags) { - this.keyFlags = keyFlags; - return this; - } - - public KeyFlags getKeyFlagsSubpacket() { - return keyFlags; - } - - @Override - public SignatureSubpackets setSignatureCreationTime(@Nonnull Date creationTime) { - return setSignatureCreationTime(true, creationTime); - } - - @Override - public SignatureSubpackets setSignatureCreationTime(boolean isCritical, @Nonnull Date creationTime) { - return setSignatureCreationTime(new SignatureCreationTime(isCritical, creationTime)); - } - - @Override - public SignatureSubpackets setSignatureCreationTime(@Nullable SignatureCreationTime signatureCreationTime) { - this.signatureCreationTime = signatureCreationTime; - return this; - } - - public SignatureCreationTime getSignatureCreationTimeSubpacket() { - return signatureCreationTime; - } - - @Override - public SignatureSubpackets setSignatureExpirationTime(@Nonnull Date creationTime, @Nonnull Date expirationTime) { - return setSignatureExpirationTime(true, creationTime, expirationTime); - } - - @Override - public SignatureSubpackets setSignatureExpirationTime(boolean isCritical, @Nonnull Date creationTime, @Nonnull Date expirationTime) { - return setSignatureExpirationTime(isCritical, (expirationTime.getTime() / 1000) - (creationTime.getTime() / 1000)); - } - - @Override - public SignatureSubpackets setSignatureExpirationTime(boolean isCritical, long seconds) { - enforceBounds(seconds); - return setSignatureExpirationTime(new SignatureExpirationTime(isCritical, seconds)); - } - - @Override - public SignatureSubpackets setSignatureExpirationTime(@Nullable SignatureExpirationTime expirationTime) { - this.signatureExpirationTime = expirationTime; - return this; - } - - public SignatureExpirationTime getSignatureExpirationTimeSubpacket() { - return signatureExpirationTime; - } - - @Override - public SignatureSubpackets setSignerUserId(@Nonnull String userId) { - return setSignerUserId(false, userId); - } - - @Override - public SignatureSubpackets setSignerUserId(boolean isCritical, @Nonnull String userId) { - return setSignerUserId(new SignerUserID(isCritical, userId)); - } - - @Override - public SignatureSubpackets setSignerUserId(@Nullable SignerUserID signerUserId) { - this.signerUserId = signerUserId; - return this; - } - - public SignerUserID getSignerUserIdSubpacket() { - return signerUserId; - } - - @Override - public SignatureSubpackets setPrimaryUserId() { - return setPrimaryUserId(true); - } - - @Override - public SignatureSubpackets setPrimaryUserId(boolean isCritical) { - return setPrimaryUserId(new PrimaryUserID(isCritical, true)); - } - - @Override - public SignatureSubpackets setPrimaryUserId(@Nullable PrimaryUserID primaryUserId) { - this.primaryUserId = primaryUserId; - return this; - } - - public PrimaryUserID getPrimaryUserIdSubpacket() { - return primaryUserId; - } - - @Override - public SignatureSubpackets setKeyExpirationTime(@Nonnull PGPPublicKey key, @Nonnull Date keyExpirationTime) { - return setKeyExpirationTime(key.getCreationTime(), keyExpirationTime); - } - - @Override - public SignatureSubpackets setKeyExpirationTime(@Nonnull Date keyCreationTime, @Nonnull Date keyExpirationTime) { - return setKeyExpirationTime(true, keyCreationTime, keyExpirationTime); - } - - @Override - public SignatureSubpackets setKeyExpirationTime(boolean isCritical, @Nonnull Date keyCreationTime, @Nonnull Date keyExpirationTime) { - return setKeyExpirationTime(isCritical, (keyExpirationTime.getTime() / 1000) - (keyCreationTime.getTime() / 1000)); - } - - @Override - public SignatureSubpackets setKeyExpirationTime(boolean isCritical, long secondsFromCreationToExpiration) { - enforceBounds(secondsFromCreationToExpiration); - return setKeyExpirationTime(new KeyExpirationTime(isCritical, secondsFromCreationToExpiration)); - } - - /** - * Enforce that
secondsFromCreationToExpiration
is within bounds of an unsigned 32bit number. - * Values less than 0 are illegal, as well as values greater 0xffffffff. - * - * @param secondsFromCreationToExpiration number to check - * @throws IllegalArgumentException in case of an under- or overflow - */ - private void enforceBounds(long secondsFromCreationToExpiration) { - if (secondsFromCreationToExpiration < 0) { - throw new IllegalArgumentException("Seconds from creation to expiration cannot be less than 0."); - } - if (secondsFromCreationToExpiration > 0xffffffffL) { - throw new IllegalArgumentException("Integer overflow. Seconds from creation to expiration cannot be larger than 0xffffffff"); - } - } - - @Override - public SignatureSubpackets setKeyExpirationTime(@Nullable KeyExpirationTime keyExpirationTime) { - this.keyExpirationTime = keyExpirationTime; - return this; - } - - public KeyExpirationTime getKeyExpirationTimeSubpacket() { - return keyExpirationTime; - } - - @Override - public SignatureSubpackets setPreferredCompressionAlgorithms(CompressionAlgorithm... algorithms) { - return setPreferredCompressionAlgorithms(new LinkedHashSet<>(Arrays.asList(algorithms))); - } - - @Override - public SignatureSubpackets setPreferredCompressionAlgorithms(Set algorithms) { - return setPreferredCompressionAlgorithms(false, algorithms); - } - - @Override - public SignatureSubpackets setPreferredCompressionAlgorithms(boolean isCritical, Set algorithms) { - int[] ids = new int[algorithms.size()]; - Iterator iterator = algorithms.iterator(); - for (int i = 0; i < algorithms.size(); i++) { - ids[i] = iterator.next().getAlgorithmId(); - } - return setPreferredCompressionAlgorithms(new PreferredAlgorithms( - SignatureSubpacketTags.PREFERRED_COMP_ALGS, isCritical, ids)); - } - - @Override - public SignatureSubpackets setPreferredCompressionAlgorithms(@Nullable PreferredAlgorithms algorithms) { - if (algorithms == null) { - this.preferredCompressionAlgorithms = null; - return this; - } - - if (algorithms.getType() != SignatureSubpacketTags.PREFERRED_COMP_ALGS) { - throw new IllegalArgumentException("Invalid preferred compression algorithms type."); - } - this.preferredCompressionAlgorithms = algorithms; - return this; - } - - public PreferredAlgorithms getPreferredCompressionAlgorithmsSubpacket() { - return preferredCompressionAlgorithms; - } - - @Override - public SignatureSubpackets setPreferredSymmetricKeyAlgorithms(SymmetricKeyAlgorithm... algorithms) { - return setPreferredSymmetricKeyAlgorithms(new LinkedHashSet<>(Arrays.asList(algorithms))); - } - - @Override - public SignatureSubpackets setPreferredSymmetricKeyAlgorithms(Set algorithms) { - return setPreferredSymmetricKeyAlgorithms(false, algorithms); - } - - @Override - public SignatureSubpackets setPreferredSymmetricKeyAlgorithms(boolean isCritical, Set algorithms) { - int[] ids = new int[algorithms.size()]; - Iterator iterator = algorithms.iterator(); - for (int i = 0; i < algorithms.size(); i++) { - ids[i] = iterator.next().getAlgorithmId(); - } - return setPreferredSymmetricKeyAlgorithms(new PreferredAlgorithms( - SignatureSubpacketTags.PREFERRED_SYM_ALGS, isCritical, ids)); - } - - @Override - public SignatureSubpackets setPreferredSymmetricKeyAlgorithms(@Nullable PreferredAlgorithms algorithms) { - if (algorithms == null) { - this.preferredSymmetricKeyAlgorithms = null; - return this; - } - - if (algorithms.getType() != SignatureSubpacketTags.PREFERRED_SYM_ALGS) { - throw new IllegalArgumentException("Invalid preferred symmetric key algorithms type."); - } - this.preferredSymmetricKeyAlgorithms = algorithms; - return this; - } - - public PreferredAlgorithms getPreferredSymmetricKeyAlgorithmsSubpacket() { - return preferredSymmetricKeyAlgorithms; - } - - @Override - public SignatureSubpackets setPreferredHashAlgorithms(HashAlgorithm... algorithms) { - return setPreferredHashAlgorithms(new LinkedHashSet<>(Arrays.asList(algorithms))); - } - - @Override - public SignatureSubpackets setPreferredHashAlgorithms(Set algorithms) { - return setPreferredHashAlgorithms(false, algorithms); - } - - @Override - public SignatureSubpackets setPreferredHashAlgorithms(boolean isCritical, Set algorithms) { - int[] ids = new int[algorithms.size()]; - Iterator iterator = algorithms.iterator(); - for (int i = 0; i < ids.length; i++) { - ids[i] = iterator.next().getAlgorithmId(); - } - return setPreferredHashAlgorithms(new PreferredAlgorithms( - SignatureSubpacketTags.PREFERRED_HASH_ALGS, isCritical, ids)); - } - - @Override - public SignatureSubpackets setPreferredHashAlgorithms(@Nullable PreferredAlgorithms algorithms) { - if (algorithms == null) { - preferredHashAlgorithms = null; - return this; - } - - if (algorithms.getType() != SignatureSubpacketTags.PREFERRED_HASH_ALGS) { - throw new IllegalArgumentException("Invalid preferred hash algorithms type."); - } - this.preferredHashAlgorithms = algorithms; - return this; - } - - public PreferredAlgorithms getPreferredHashAlgorithmsSubpacket() { - return preferredHashAlgorithms; - } - - @Override - public SignatureSubpackets addNotationData(boolean isCritical, @Nonnull String notationName, @Nonnull String notationValue) { - return addNotationData(isCritical, true, notationName, notationValue); - } - - @Override - public SignatureSubpackets addNotationData(boolean isCritical, boolean isHumanReadable, @Nonnull String notationName, @Nonnull String notationValue) { - return addNotationData(new NotationData(isCritical, isHumanReadable, notationName, notationValue)); - } - - @Override - public SignatureSubpackets addNotationData(@Nonnull NotationData notationData) { - notationDataList.add(notationData); - return this; - } - - @Override - public SignatureSubpackets clearNotationData() { - notationDataList.clear(); - return this; - } - - public List getNotationDataSubpackets() { - return new ArrayList<>(notationDataList); - } - - @Override - public SignatureSubpackets addIntendedRecipientFingerprint(@Nonnull PGPPublicKey recipient) { - return addIntendedRecipientFingerprint(false, recipient); - } - - @Override - public SignatureSubpackets addIntendedRecipientFingerprint(boolean isCritical, @Nonnull PGPPublicKey recipient) { - return addIntendedRecipientFingerprint(new IntendedRecipientFingerprint(isCritical, recipient.getVersion(), recipient.getFingerprint())); - } - - @Override - public SignatureSubpackets addIntendedRecipientFingerprint(IntendedRecipientFingerprint intendedRecipientFingerprint) { - this.intendedRecipientFingerprintList.add(intendedRecipientFingerprint); - return this; - } - - @Override - public SignatureSubpackets clearIntendedRecipientFingerprints() { - intendedRecipientFingerprintList.clear(); - return this; - } - - public List getIntendedRecipientFingerprintSubpackets() { - return new ArrayList<>(intendedRecipientFingerprintList); - } - - @Override - public SignatureSubpackets setExportable(boolean exportable) { - return setExportable(true, exportable); - } - - @Override - public SignatureSubpackets setExportable(boolean isCritical, boolean isExportable) { - return setExportable(new Exportable(isCritical, isExportable)); - } - - @Override - public SignatureSubpackets setExportable(@Nullable Exportable exportable) { - this.exportable = exportable; - return this; - } - - public Exportable getExportableSubpacket() { - return exportable; - } - - @Override - public BaseSignatureSubpackets setPolicyUrl(@Nonnull URL policyUrl) { - return setPolicyUrl(false, policyUrl); - } - - @Override - public BaseSignatureSubpackets setPolicyUrl(boolean isCritical, @Nonnull URL policyUrl) { - return setPolicyUrl(new PolicyURI(isCritical, policyUrl.toString())); - } - - @Override - public BaseSignatureSubpackets setPolicyUrl(@Nullable PolicyURI policyUrl) { - this.policyURI = policyUrl; - return this; - } - - public PolicyURI getPolicyURI() { - return policyURI; - } - - @Override - public BaseSignatureSubpackets setRegularExpression(@Nonnull String regex) { - return setRegularExpression(false, regex); - } - - @Override - public BaseSignatureSubpackets setRegularExpression(boolean isCritical, @Nonnull String regex) { - return setRegularExpression(new RegularExpression(isCritical, regex)); - } - - @Override - public BaseSignatureSubpackets setRegularExpression(@Nullable RegularExpression regex) { - this.regularExpression = regex; - return this; - } - - public RegularExpression getRegularExpression() { - return regularExpression; - } - - @Override - public SignatureSubpackets setRevocable(boolean revocable) { - return setRevocable(true, revocable); - } - - @Override - public SignatureSubpackets setRevocable(boolean isCritical, boolean isRevocable) { - return setRevocable(new Revocable(isCritical, isRevocable)); - } - - @Override - public SignatureSubpackets setRevocable(@Nullable Revocable revocable) { - this.revocable = revocable; - return this; - } - - public Revocable getRevocableSubpacket() { - return revocable; - } - - @Override - public SignatureSubpackets addRevocationKey(@Nonnull PGPPublicKey revocationKey) { - return addRevocationKey(true, revocationKey); - } - - @Override - public SignatureSubpackets addRevocationKey(boolean isCritical, @Nonnull PGPPublicKey revocationKey) { - return addRevocationKey(isCritical, false, revocationKey); - } - - @Override - public SignatureSubpackets addRevocationKey(boolean isCritical, boolean isSensitive, @Nonnull PGPPublicKey revocationKey) { - byte clazz = (byte) 0x80; - clazz |= (isSensitive ? 0x40 : 0x00); - return addRevocationKey(new RevocationKey(isCritical, clazz, revocationKey.getAlgorithm(), revocationKey.getFingerprint())); - } - - @Override - public SignatureSubpackets addRevocationKey(@Nonnull RevocationKey revocationKey) { - this.revocationKeyList.add(revocationKey); - return this; - } - - @Override - public SignatureSubpackets clearRevocationKeys() { - revocationKeyList.clear(); - return this; - } - - public List getRevocationKeySubpackets() { - return new ArrayList<>(revocationKeyList); - } - - @Override - public SignatureSubpackets setRevocationReason(RevocationAttributes revocationAttributes) { - return setRevocationReason(false, revocationAttributes); - } - - @Override - public SignatureSubpackets setRevocationReason(boolean isCritical, RevocationAttributes revocationAttributes) { - return setRevocationReason(isCritical, revocationAttributes.getReason(), revocationAttributes.getDescription()); - } - - @Override - public SignatureSubpackets setRevocationReason(boolean isCritical, RevocationAttributes.Reason reason, @Nonnull String description) { - return setRevocationReason(new RevocationReason(isCritical, reason.code(), description)); - } - - @Override - public SignatureSubpackets setRevocationReason(@Nullable RevocationReason reason) { - this.revocationReason = reason; - return this; - } - - public RevocationReason getRevocationReasonSubpacket() { - return revocationReason; - } - - @Override - public SignatureSubpackets setSignatureTarget(@Nonnull PublicKeyAlgorithm keyAlgorithm, @Nonnull HashAlgorithm hashAlgorithm, @Nonnull byte[] hashData) { - return setSignatureTarget(true, keyAlgorithm, hashAlgorithm, hashData); - } - - @Override - public SignatureSubpackets setSignatureTarget(boolean isCritical, @Nonnull PublicKeyAlgorithm keyAlgorithm, @Nonnull HashAlgorithm hashAlgorithm, @Nonnull byte[] hashData) { - return setSignatureTarget(new SignatureTarget(isCritical, keyAlgorithm.getAlgorithmId(), hashAlgorithm.getAlgorithmId(), hashData)); - } - - @Override - public SignatureSubpackets setSignatureTarget(@Nullable SignatureTarget signatureTarget) { - this.signatureTarget = signatureTarget; - return this; - } - - public SignatureTarget getSignatureTargetSubpacket() { - return signatureTarget; - } - - @Override - public SignatureSubpackets setFeatures(Feature... features) { - return setFeatures(true, features); - } - - @Override - public SignatureSubpackets setFeatures(boolean isCritical, Feature... features) { - byte bitmask = Feature.toBitmask(features); - return setFeatures(new Features(isCritical, bitmask)); - } - - @Override - public SignatureSubpackets setFeatures(@Nullable Features features) { - this.features = features; - return this; - } - - public Features getFeaturesSubpacket() { - return features; - } - - @Override - public SignatureSubpackets setTrust(int depth, int amount) { - return setTrust(true, depth, amount); - } - - @Override - public SignatureSubpackets setTrust(boolean isCritical, int depth, int amount) { - return setTrust(new TrustSignature(isCritical, depth, amount)); - } - - @Override - public SignatureSubpackets setTrust(@Nullable TrustSignature trust) { - this.trust = trust; - return this; - } - - public TrustSignature getTrustSubpacket() { - return trust; - } - - @Override - public SignatureSubpackets addEmbeddedSignature(@Nonnull PGPSignature signature) throws IOException { - return addEmbeddedSignature(true, signature); - } - - @Override - public SignatureSubpackets addEmbeddedSignature(boolean isCritical, @Nonnull PGPSignature signature) throws IOException { - byte[] sig = signature.getEncoded(); - byte[] data; - - if (sig.length - 1 > 256) { - data = new byte[sig.length - 3]; - } - else { - data = new byte[sig.length - 2]; - } - - System.arraycopy(sig, sig.length - data.length, data, 0, data.length); - - return addEmbeddedSignature(new EmbeddedSignature(isCritical, false, data)); - } - - @Override - public SignatureSubpackets addEmbeddedSignature(@Nonnull EmbeddedSignature embeddedSignature) { - this.embeddedSignatureList.add(embeddedSignature); - return this; - } - - @Override - public SignatureSubpackets clearEmbeddedSignatures() { - this.embeddedSignatureList.clear(); - return this; - } - - public List getEmbeddedSignatureSubpackets() { - return new ArrayList<>(embeddedSignatureList); - } - - public SignatureSubpackets addResidualSubpacket(SignatureSubpacket subpacket) { - this.residualSubpackets.add(subpacket); - return this; - } - - public List getResidualSubpackets() { - return new ArrayList<>(residualSubpackets); - } - -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.java index 8af60a03..2a7d4f83 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.java @@ -143,8 +143,8 @@ public class SignatureSubpacketsHelper { addSubpacket(generator, subpackets.getSignatureCreationTimeSubpacket()); addSubpacket(generator, subpackets.getSignatureExpirationTimeSubpacket()); addSubpacket(generator, subpackets.getExportableSubpacket()); - addSubpacket(generator, subpackets.getPolicyURI()); - addSubpacket(generator, subpackets.getRegularExpression()); + addSubpacket(generator, subpackets.getPolicyURISubpacket()); + addSubpacket(generator, subpackets.getRegularExpressionSubpacket()); for (NotationData notationData : subpackets.getNotationDataSubpackets()) { addSubpacket(generator, notationData); } diff --git a/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt b/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt index 5972cd3a..a1c80710 100644 --- a/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt +++ b/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt @@ -24,11 +24,21 @@ fun Date.plusSeconds(seconds: Long): Date? { else Date(this.time + 1000 * seconds) } +val Date.asSeconds: Long + get() = time / 1000 + +fun Date.secondsTill(later: Date): Long { + require(this <= later) { + "Timestamp MUST be before the later timestamp." + } + return later.asSeconds - this.asSeconds +} + /** * Return a new [Date] instance with this instance's time floored down to seconds precision. */ fun Date.toSecondsPrecision(): Date { - return Date((time / 1000) * 1000) + return Date(asSeconds * 1000) } internal val parser: SimpleDateFormat diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt index b219a739..3425145b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt @@ -144,14 +144,14 @@ class SecretKeyRingEditor( ?: throw AssertionError("Certification for old user-ID MUST NOT be null.") addUserId(newUID, object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets?) { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { SignatureSubpacketsHelper.applyFrom(oldCertification.hashedSubPackets, hashedSubpackets as SignatureSubpackets) if (oldUID == info.primaryUserId && !oldCertification.hashedSubPackets.isPrimaryUserID) { hashedSubpackets.setPrimaryUserId() } } - override fun modifyUnhashedSubpackets(unhashedSubpackets: SelfSignatureSubpackets?) { + override fun modifyUnhashedSubpackets(unhashedSubpackets: SelfSignatureSubpackets) { SignatureSubpacketsHelper.applyFrom(oldCertification.unhashedSubPackets, unhashedSubpackets as SignatureSubpackets) } }, protector) @@ -163,7 +163,7 @@ class SecretKeyRingEditor( subkeyPassphrase: Passphrase, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { val callback = object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets?) { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { SignatureSubpacketsHelper.applyFrom(keySpec.subpackets, hashedSubpackets as SignatureSubpackets) } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt new file mode 100644 index 00000000..66618b23 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +import org.bouncycastle.bcpg.sig.* +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.PublicKeyAlgorithm +import java.io.IOException +import java.net.URL +import java.util.* + +interface BaseSignatureSubpackets { + + interface Callback : SignatureSubpacketCallback + + /** + * Add both an [IssuerKeyID] and [IssuerFingerprint] subpacket pointing to the given key. + * + * @param key key + * @return this + * + * @deprecated this method MUST NOT be used for OpenPGP v6, since v6 signatures MUST NOT contain any + * [IssuerKeyID] packets. + */ + fun setIssuerFingerprintAndKeyId(key: PGPPublicKey): BaseSignatureSubpackets + + fun setIssuerKeyId(keyId: Long): BaseSignatureSubpackets + + fun setIssuerKeyId(isCritical: Boolean, keyId: Long): BaseSignatureSubpackets + + fun setIssuerKeyId(issuerKeyID: IssuerKeyID?): BaseSignatureSubpackets + + fun setIssuerFingerprint(isCritical: Boolean, issuer: PGPPublicKey): BaseSignatureSubpackets + + fun setIssuerFingerprint(issuer: PGPPublicKey): BaseSignatureSubpackets + + fun setIssuerFingerprint(fingerprint: IssuerFingerprint?): BaseSignatureSubpackets + + fun setSignatureCreationTime(creationTime: Date): BaseSignatureSubpackets + + fun setSignatureCreationTime(isCritical: Boolean, creationTime: Date): BaseSignatureSubpackets + + fun setSignatureCreationTime(creationTime: SignatureCreationTime?): BaseSignatureSubpackets + + fun setSignatureExpirationTime(creationTime: Date, expirationTime: Date?): BaseSignatureSubpackets + + fun setSignatureExpirationTime(isCritical: Boolean, creationTime: Date, expirationTime: Date?): BaseSignatureSubpackets + + fun setSignatureExpirationTime(isCritical: Boolean, seconds: Long): BaseSignatureSubpackets + + fun setSignatureExpirationTime(expirationTime: SignatureExpirationTime?): BaseSignatureSubpackets + + fun setSignerUserId(userId: CharSequence): BaseSignatureSubpackets + + fun setSignerUserId(isCritical: Boolean, userId: CharSequence): BaseSignatureSubpackets + + fun setSignerUserId(signerUserID: SignerUserID?): BaseSignatureSubpackets + + fun addNotationData(isCritical: Boolean, notationName: String, notationValue: String): BaseSignatureSubpackets + + fun addNotationData(isCritical: Boolean, isHumanReadable: Boolean, notationName: String, notationValue: String): BaseSignatureSubpackets + + fun addNotationData(notationData: NotationData): BaseSignatureSubpackets + + fun clearNotationData(): BaseSignatureSubpackets + + fun addIntendedRecipientFingerprint(recipientKey: PGPPublicKey): BaseSignatureSubpackets + + fun addIntendedRecipientFingerprint(isCritical: Boolean, recipientKey: PGPPublicKey): BaseSignatureSubpackets + + fun addIntendedRecipientFingerprint(intendedRecipient: IntendedRecipientFingerprint): BaseSignatureSubpackets + + fun clearIntendedRecipientFingerprints(): BaseSignatureSubpackets + + fun setExportable(): BaseSignatureSubpackets + + fun setExportable(isExportable: Boolean): BaseSignatureSubpackets + + fun setExportable(isCritical: Boolean, isExportable: Boolean): BaseSignatureSubpackets + + fun setExportable(exportable: Exportable?): BaseSignatureSubpackets + + fun setPolicyUrl(policyUrl: URL): BaseSignatureSubpackets + + fun setPolicyUrl(isCritical: Boolean, policyUrl: URL): BaseSignatureSubpackets + + fun setPolicyUrl(policyUrl: PolicyURI?): BaseSignatureSubpackets + + fun setRegularExpression(regex: CharSequence): BaseSignatureSubpackets + + fun setRegularExpression(isCritical: Boolean, regex: CharSequence): BaseSignatureSubpackets + + fun setRegularExpression(regex: RegularExpression?): BaseSignatureSubpackets + + fun setRevocable(): BaseSignatureSubpackets + + fun setRevocable(isRevocable: Boolean): BaseSignatureSubpackets + + fun setRevocable(isCritical: Boolean, isRevocable: Boolean): BaseSignatureSubpackets + + fun setRevocable(revocable: Revocable?): BaseSignatureSubpackets + + fun setSignatureTarget(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm, hashData: ByteArray): BaseSignatureSubpackets + + fun setSignatureTarget(isCritical: Boolean, keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm, hashData: ByteArray): BaseSignatureSubpackets + + fun setSignatureTarget(signatureTarget: SignatureTarget?): BaseSignatureSubpackets + + fun setTrust(depth: Int, amount: Int): BaseSignatureSubpackets + + fun setTrust(isCritical: Boolean, depth: Int, amount: Int): BaseSignatureSubpackets + + fun setTrust(trust: TrustSignature?): BaseSignatureSubpackets + + @Throws(IOException::class) + fun addEmbeddedSignature(signature: PGPSignature): BaseSignatureSubpackets + + @Throws(IOException::class) + fun addEmbeddedSignature(isCritical: Boolean, signature: PGPSignature): BaseSignatureSubpackets + + fun addEmbeddedSignature(embeddedSignature: EmbeddedSignature): BaseSignatureSubpackets + + fun clearEmbeddedSignatures(): BaseSignatureSubpackets +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/CertificationSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/CertificationSubpackets.kt new file mode 100644 index 00000000..423edd8e --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/CertificationSubpackets.kt @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +interface CertificationSubpackets : BaseSignatureSubpackets { + + interface Callback : SignatureSubpacketCallback + +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.kt new file mode 100644 index 00000000..327ed4a9 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.kt @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +import org.bouncycastle.bcpg.sig.RevocationReason +import org.pgpainless.key.util.RevocationAttributes + +interface RevocationSignatureSubpackets : BaseSignatureSubpackets { + + interface Callback : SignatureSubpacketCallback + + fun setRevocationReason(revocationAttributes: RevocationAttributes): RevocationSignatureSubpackets + + fun setRevocationReason(isCritical: Boolean, revocationAttributes: RevocationAttributes): RevocationSignatureSubpackets + + fun setRevocationReason(isCritical: Boolean, reason: RevocationAttributes.Reason, description: CharSequence): RevocationSignatureSubpackets + + fun setRevocationReason(reason: RevocationReason?): RevocationSignatureSubpackets +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt new file mode 100644 index 00000000..ce8e09a9 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +import org.bouncycastle.bcpg.sig.Features +import org.bouncycastle.bcpg.sig.KeyExpirationTime +import org.bouncycastle.bcpg.sig.KeyFlags +import org.bouncycastle.bcpg.sig.PreferredAlgorithms +import org.bouncycastle.bcpg.sig.PrimaryUserID +import org.bouncycastle.bcpg.sig.RevocationKey +import org.bouncycastle.openpgp.PGPPublicKey +import org.pgpainless.algorithm.* +import java.util.* + +interface SelfSignatureSubpackets : BaseSignatureSubpackets { + + interface Callback : SignatureSubpacketCallback + + fun setKeyFlags(vararg keyflags: KeyFlag): SelfSignatureSubpackets + + fun setKeyFlags(keyFlags: List): SelfSignatureSubpackets + + fun setKeyFlags(isCritical: Boolean, vararg keyFlags: KeyFlag): SelfSignatureSubpackets + + fun setKeyFlags(keyFlags: KeyFlags?): SelfSignatureSubpackets + + fun setPrimaryUserId(): SelfSignatureSubpackets + + fun setPrimaryUserId(isCritical: Boolean): SelfSignatureSubpackets + + fun setPrimaryUserId(primaryUserID: PrimaryUserID?): SelfSignatureSubpackets + + fun setKeyExpirationTime(key: PGPPublicKey, keyExpirationTime: Date?): SelfSignatureSubpackets + + fun setKeyExpirationTime(keyCreationTime: Date, keyExpirationTime: Date?): SelfSignatureSubpackets + + fun setKeyExpirationTime(isCritical: Boolean, keyCreationTime: Date, keyExpirationTime: Date?): SelfSignatureSubpackets + + fun setKeyExpirationTime(isCritical: Boolean, secondsFromCreationToExpiration: Long): SelfSignatureSubpackets + + fun setKeyExpirationTime(keyExpirationTime: KeyExpirationTime?): SelfSignatureSubpackets + + fun setPreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): SelfSignatureSubpackets + + fun setPreferredCompressionAlgorithms(algorithms: Collection): SelfSignatureSubpackets + + fun setPreferredCompressionAlgorithms(isCritical: Boolean, algorithms: Collection): SelfSignatureSubpackets + + fun setPreferredCompressionAlgorithms(preferredAlgorithms: PreferredAlgorithms?): SelfSignatureSubpackets + + fun setPreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): SelfSignatureSubpackets + + fun setPreferredSymmetricKeyAlgorithms(algorithms: Collection): SelfSignatureSubpackets + + fun setPreferredSymmetricKeyAlgorithms(isCritical: Boolean, algorithms: Collection): SelfSignatureSubpackets + + fun setPreferredSymmetricKeyAlgorithms(algorithms: PreferredAlgorithms?): SelfSignatureSubpackets + + fun setPreferredHashAlgorithms(vararg algorithms: HashAlgorithm): SelfSignatureSubpackets + + fun setPreferredHashAlgorithms(algorithms: Collection): SelfSignatureSubpackets + + fun setPreferredHashAlgorithms(isCritical: Boolean, algorithms: Collection): SelfSignatureSubpackets + + fun setPreferredHashAlgorithms(algorithms: PreferredAlgorithms?): SelfSignatureSubpackets + + fun addRevocationKey(revocationKey: PGPPublicKey): SelfSignatureSubpackets + + fun addRevocationKey(isCritical: Boolean, revocationKey: PGPPublicKey): SelfSignatureSubpackets + + fun addRevocationKey(isCritical: Boolean, isSensitive: Boolean, revocationKey: PGPPublicKey): SelfSignatureSubpackets + + fun addRevocationKey(revocationKey: RevocationKey): SelfSignatureSubpackets + + fun clearRevocationKeys(): SelfSignatureSubpackets + + fun setFeatures(vararg features: Feature): SelfSignatureSubpackets + + fun setFeatures(isCritical: Boolean, vararg features: Feature): SelfSignatureSubpackets + + fun setFeatures(features: Features?): SelfSignatureSubpackets +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.kt new file mode 100644 index 00000000..d147dc97 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.kt @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +interface SignatureSubpacketCallback { + + /** + * Callback method that can be used to modify the hashed subpackets of a signature. + * + * @param hashedSubpackets hashed subpackets + */ + fun modifyHashedSubpackets(hashedSubpackets: S) { + // Empty default implementation to allow for cleaner overriding + } + + /** + * Callback method that can be used to modify the unhashed subpackets of a signature. + * + * @param unhashedSubpackets unhashed subpackets + */ + fun modifyUnhashedSubpackets(unhashedSubpackets: S) { + // Empty default implementation to allow for cleaner overriding + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt new file mode 100644 index 00000000..d7d907f0 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt @@ -0,0 +1,491 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +import openpgp.secondsTill +import openpgp.toSecondsPrecision +import org.bouncycastle.bcpg.SignatureSubpacket +import org.bouncycastle.bcpg.SignatureSubpacketTags +import org.bouncycastle.bcpg.sig.* +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector +import org.pgpainless.algorithm.* +import org.pgpainless.key.util.RevocationAttributes +import java.lang.IllegalArgumentException +import java.net.URL +import java.util.* +import kotlin.experimental.or + +class SignatureSubpackets + : BaseSignatureSubpackets, SelfSignatureSubpackets, CertificationSubpackets, RevocationSignatureSubpackets { + + interface Callback : SignatureSubpacketCallback + + var signatureCreationTimeSubpacket: SignatureCreationTime? = null + var signatureExpirationTimeSubpacket: SignatureExpirationTime? = null + var issuerKeyIdSubpacket: IssuerKeyID? = null + var issuerFingerprintSubpacket: IssuerFingerprint? = null + val notationDataSubpackets: List = mutableListOf() + val intendedRecipientFingerprintSubpackets: List = mutableListOf() + val revocationKeySubpackets: List = mutableListOf() + var exportableSubpacket: Exportable? = null + var signatureTargetSubpacket: SignatureTarget? = null + var featuresSubpacket: Features? = null + var keyFlagsSubpacket: KeyFlags? = null + var trustSubpacket: TrustSignature? = null + var preferredCompressionAlgorithmsSubpacket: PreferredAlgorithms? = null + var preferredSymmetricKeyAlgorithmsSubpacket: PreferredAlgorithms? = null + var preferredHashAlgorithmsSubpacket: PreferredAlgorithms? = null + val embeddedSignatureSubpackets: List = mutableListOf() + var signerUserIdSubpacket: SignerUserID? = null + var keyExpirationTimeSubpacket: KeyExpirationTime? = null + var policyURISubpacket: PolicyURI? = null + var primaryUserIdSubpacket: PrimaryUserID? = null + var regularExpressionSubpacket: RegularExpression? = null + var revocableSubpacket: Revocable? = null + var revocationReasonSubpacket: RevocationReason? = null + val residualSubpackets: List = mutableListOf() + + companion object { + + @JvmStatic + fun refreshHashedSubpackets(issuer: PGPPublicKey, oldSignature: PGPSignature): SignatureSubpackets { + return createHashedSubpacketsFrom(issuer, oldSignature.hashedSubPackets) + } + + @JvmStatic + fun refreshUnhashedSubpackets(oldSignature: PGPSignature): SignatureSubpackets { + return createSubpacketsFrom(oldSignature.unhashedSubPackets) + } + + @JvmStatic + fun createHashedSubpacketsFrom(issuer: PGPPublicKey, base: PGPSignatureSubpacketVector): SignatureSubpackets { + return createSubpacketsFrom(base).apply { + setIssuerFingerprintAndKeyId(issuer) + } + } + + @JvmStatic + fun createSubpacketsFrom(base: PGPSignatureSubpacketVector): SignatureSubpackets { + return SignatureSubpackets().apply { + SignatureSubpacketsHelper.applyFrom(base, this) + } + } + + @JvmStatic + fun createHashedSubpackets(issuer: PGPPublicKey): SignatureSubpackets { + return SignatureSubpackets().apply { + setIssuerFingerprintAndKeyId(issuer) + } + } + + @JvmStatic + fun createEmptySubpackets(): SignatureSubpackets { + return SignatureSubpackets() + } + } + + override fun setRevocationReason(revocationAttributes: RevocationAttributes): SignatureSubpackets = apply { + setRevocationReason(false, revocationAttributes) + } + + override fun setRevocationReason(isCritical: Boolean, revocationAttributes: RevocationAttributes): SignatureSubpackets = apply { + setRevocationReason(isCritical, revocationAttributes.reason, revocationAttributes.description) + } + + override fun setRevocationReason(isCritical: Boolean, reason: RevocationAttributes.Reason, description: CharSequence): SignatureSubpackets = apply { + setRevocationReason(RevocationReason(isCritical, reason.code, description.toString())) + } + + override fun setRevocationReason(reason: RevocationReason?): SignatureSubpackets = apply { + this.revocationReasonSubpacket = reason + } + + override fun setKeyFlags(vararg keyflags: KeyFlag): SignatureSubpackets = apply { + setKeyFlags(true, *keyflags) + } + + override fun setKeyFlags(keyFlags: List): SignatureSubpackets = apply { + setKeyFlags(true, *keyFlags.toTypedArray()) + } + + override fun setKeyFlags(isCritical: Boolean, vararg keyFlags: KeyFlag): SignatureSubpackets = apply { + setKeyFlags(KeyFlags(isCritical, KeyFlag.toBitmask(*keyFlags))) + } + + override fun setKeyFlags(keyFlags: KeyFlags?): SignatureSubpackets = apply { + this.keyFlagsSubpacket = keyFlags + } + + override fun setPrimaryUserId(): SignatureSubpackets = apply { + setPrimaryUserId(true) + } + + override fun setPrimaryUserId(isCritical: Boolean): SignatureSubpackets = apply { + setPrimaryUserId(PrimaryUserID(isCritical, true)) + } + + override fun setPrimaryUserId(primaryUserID: PrimaryUserID?): SignatureSubpackets = apply { + this.primaryUserIdSubpacket = primaryUserID + } + + override fun setKeyExpirationTime(key: PGPPublicKey, keyExpirationTime: Date?): SignatureSubpackets = apply { + setKeyExpirationTime(key.creationTime, keyExpirationTime) + } + + override fun setKeyExpirationTime(keyCreationTime: Date, keyExpirationTime: Date?): SignatureSubpackets = apply { + setKeyExpirationTime(true, keyCreationTime, keyExpirationTime) + } + + override fun setKeyExpirationTime(isCritical: Boolean, keyCreationTime: Date, keyExpirationTime: Date?): SignatureSubpackets = apply { + if (keyExpirationTime == null) { + setKeyExpirationTime(isCritical, 0) + } else { + setKeyExpirationTime(isCritical, keyCreationTime.secondsTill(keyExpirationTime)) + } + } + + override fun setKeyExpirationTime(isCritical: Boolean, secondsFromCreationToExpiration: Long): SignatureSubpackets = apply { + enforceExpirationBounds(secondsFromCreationToExpiration) + setKeyExpirationTime(KeyExpirationTime(isCritical, secondsFromCreationToExpiration)) + } + + override fun setKeyExpirationTime(keyExpirationTime: KeyExpirationTime?): SignatureSubpackets = apply { + this.keyExpirationTimeSubpacket = keyExpirationTime + } + + override fun setPreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): SignatureSubpackets = apply { + setPreferredCompressionAlgorithms(setOf(*algorithms)) + } + + override fun setPreferredCompressionAlgorithms(algorithms: Collection): SignatureSubpackets = apply { + setPreferredCompressionAlgorithms(false, algorithms) + } + + override fun setPreferredCompressionAlgorithms(isCritical: Boolean, algorithms: Collection): SignatureSubpackets = apply { + setPreferredCompressionAlgorithms(PreferredAlgorithms( + SignatureSubpacketTags.PREFERRED_COMP_ALGS, + isCritical, + algorithms.map { it.algorithmId }.toIntArray())) + } + + override fun setPreferredCompressionAlgorithms(algorithms: PreferredAlgorithms?): SignatureSubpackets = apply { + require(algorithms == null || algorithms.type == SignatureSubpacketTags.PREFERRED_COMP_ALGS) { + "Invalid preferred compression algorithms type." + } + this.preferredCompressionAlgorithmsSubpacket = algorithms + } + + override fun setPreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): SignatureSubpackets = apply { + setPreferredSymmetricKeyAlgorithms(setOf(*algorithms)) + } + + override fun setPreferredSymmetricKeyAlgorithms(algorithms: Collection): SignatureSubpackets = apply { + setPreferredSymmetricKeyAlgorithms(false, algorithms) + } + + override fun setPreferredSymmetricKeyAlgorithms(isCritical: Boolean, algorithms: Collection): SignatureSubpackets = apply { + setPreferredSymmetricKeyAlgorithms(PreferredAlgorithms( + SignatureSubpacketTags.PREFERRED_SYM_ALGS, + isCritical, + algorithms.map { it.algorithmId }.toIntArray() + )) + } + + override fun setPreferredSymmetricKeyAlgorithms(algorithms: PreferredAlgorithms?): SignatureSubpackets = apply { + require(algorithms == null || algorithms.type == SignatureSubpacketTags.PREFERRED_SYM_ALGS) { + "Invalid preferred symmetric algorithms type." + } + this.preferredSymmetricKeyAlgorithmsSubpacket = algorithms + } + + override fun setPreferredHashAlgorithms(vararg algorithms: HashAlgorithm): SignatureSubpackets = apply { + setPreferredHashAlgorithms(setOf(*algorithms)) + } + + override fun setPreferredHashAlgorithms(algorithms: Collection): SignatureSubpackets = apply { + setPreferredHashAlgorithms(false, algorithms) + } + + override fun setPreferredHashAlgorithms(isCritical: Boolean, algorithms: Collection): SignatureSubpackets = apply { + setPreferredHashAlgorithms(PreferredAlgorithms( + SignatureSubpacketTags.PREFERRED_HASH_ALGS, + isCritical, + algorithms.map { it.algorithmId }.toIntArray() + )) + } + + override fun setPreferredHashAlgorithms(algorithms: PreferredAlgorithms?): SignatureSubpackets = apply { + require(algorithms == null || algorithms.type == SignatureSubpacketTags.PREFERRED_HASH_ALGS) { + "Invalid preferred hash algorithms type." + } + this.preferredHashAlgorithmsSubpacket = algorithms + } + + override fun addRevocationKey(revocationKey: PGPPublicKey): SignatureSubpackets = apply { + addRevocationKey(true, revocationKey) + } + + override fun addRevocationKey(isCritical: Boolean, revocationKey: PGPPublicKey): SignatureSubpackets = apply { + addRevocationKey(isCritical, false, revocationKey) + } + + override fun addRevocationKey(isCritical: Boolean, isSensitive: Boolean, revocationKey: PGPPublicKey): SignatureSubpackets = apply { + val clazz = 0x80.toByte() or if (isSensitive) 0x40.toByte() else 0x00.toByte() + addRevocationKey(RevocationKey(isCritical, clazz, revocationKey.algorithm, revocationKey.fingerprint)) + } + + override fun addRevocationKey(revocationKey: RevocationKey): SignatureSubpackets = apply { + (this.revocationKeySubpackets as MutableList).add(revocationKey) + } + + override fun clearRevocationKeys(): SignatureSubpackets = apply { + (this.revocationKeySubpackets as MutableList).clear() + } + + override fun setFeatures(vararg features: Feature): SignatureSubpackets = apply { + setFeatures(true, *features) + } + + override fun setFeatures(isCritical: Boolean, vararg features: Feature): SignatureSubpackets = apply { + setFeatures(Features(isCritical, Feature.toBitmask(*features))) + } + + override fun setFeatures(features: Features?): SignatureSubpackets = apply { + this.featuresSubpacket = features + } + + override fun setIssuerFingerprintAndKeyId(key: PGPPublicKey): SignatureSubpackets = apply { + setIssuerKeyId(key.keyID) + setIssuerFingerprint(key) + } + + override fun setIssuerKeyId(keyId: Long): SignatureSubpackets = apply { + setIssuerKeyId(false, keyId) + } + + override fun setIssuerKeyId(isCritical: Boolean, keyId: Long): SignatureSubpackets = apply { + setIssuerKeyId(IssuerKeyID(isCritical, keyId)) + } + + override fun setIssuerKeyId(issuerKeyID: IssuerKeyID?): SignatureSubpackets = apply { + this.issuerKeyIdSubpacket = issuerKeyID + } + + override fun setIssuerFingerprint(isCritical: Boolean, issuer: PGPPublicKey): SignatureSubpackets = apply { + setIssuerFingerprint(IssuerFingerprint(isCritical, issuer.version, issuer.fingerprint)) + } + + override fun setIssuerFingerprint(issuer: PGPPublicKey): SignatureSubpackets = apply { + setIssuerFingerprint(false, issuer) + } + + override fun setIssuerFingerprint(fingerprint: IssuerFingerprint?): SignatureSubpackets = apply { + this.issuerFingerprintSubpacket = fingerprint + } + + override fun setSignatureCreationTime(creationTime: Date): SignatureSubpackets = apply { + setSignatureCreationTime(true, creationTime) + } + + override fun setSignatureCreationTime(isCritical: Boolean, creationTime: Date): SignatureSubpackets = apply { + setSignatureCreationTime(SignatureCreationTime(isCritical, creationTime)) + } + + override fun setSignatureCreationTime(creationTime: SignatureCreationTime?): SignatureSubpackets = apply { + this.signatureCreationTimeSubpacket = creationTime + } + override fun setSignatureExpirationTime(creationTime: Date, expirationTime: Date?): SignatureSubpackets = apply { + setSignatureExpirationTime(true, creationTime, expirationTime) + } + + override fun setSignatureExpirationTime(isCritical: Boolean, creationTime: Date, expirationTime: Date?): SignatureSubpackets = apply { + if (expirationTime != null) { + require(creationTime.toSecondsPrecision() < expirationTime.toSecondsPrecision()) { + "Expiration time MUST NOT be less or equal the creation time." + } + setSignatureExpirationTime(SignatureExpirationTime(isCritical, creationTime.secondsTill(expirationTime))) + } else { + setSignatureExpirationTime(SignatureExpirationTime(isCritical, 0)) + } + } + + override fun setSignatureExpirationTime(isCritical: Boolean, seconds: Long): SignatureSubpackets = apply { + enforceExpirationBounds(seconds) + setSignatureExpirationTime(SignatureExpirationTime(isCritical, seconds)) + } + + /** + * Enforce that
seconds
is within bounds of an unsigned 32bit number. + * Values less than 0 are illegal, as well as values greater 0xffffffff. + * + * @param seconds number to check + * @throws IllegalArgumentException in case of an under- or overflow + */ + private fun enforceExpirationBounds(seconds: Long) { + require(seconds <= 0xffffffffL) { + "Integer overflow. Seconds from creation to expiration (${seconds}) cannot be larger than ${0xffffffffL}." + } + require(seconds >= 0) { + "Seconds from creation to expiration cannot be less than 0." + } + } + + override fun setSignatureExpirationTime(expirationTime: SignatureExpirationTime?): SignatureSubpackets = apply { + this.signatureExpirationTimeSubpacket = expirationTime + } + + override fun setSignerUserId(userId: CharSequence): SignatureSubpackets = apply { + setSignerUserId(false, userId) + } + + override fun setSignerUserId(isCritical: Boolean, userId: CharSequence): SignatureSubpackets = apply { + setSignerUserId(SignerUserID(isCritical, userId.toString())) + } + + override fun setSignerUserId(signerUserID: SignerUserID?): SignatureSubpackets = apply { + this.signerUserIdSubpacket = signerUserID + } + + override fun addNotationData(isCritical: Boolean, notationName: String, notationValue: String): SignatureSubpackets = apply { + addNotationData(isCritical, true, notationName, notationValue) + } + + override fun addNotationData(isCritical: Boolean, isHumanReadable: Boolean, notationName: String, notationValue: String): SignatureSubpackets = apply { + addNotationData(NotationData(isCritical, isHumanReadable, notationName, notationValue)) + } + + override fun addNotationData(notationData: NotationData): SignatureSubpackets = apply { + (this.notationDataSubpackets as MutableList).add(notationData) + } + + override fun clearNotationData(): SignatureSubpackets = apply { + (this.notationDataSubpackets as MutableList).clear() + } + + override fun addIntendedRecipientFingerprint(recipientKey: PGPPublicKey): SignatureSubpackets = apply { + addIntendedRecipientFingerprint(false, recipientKey) + } + + override fun addIntendedRecipientFingerprint(isCritical: Boolean, recipientKey: PGPPublicKey): SignatureSubpackets = apply { + addIntendedRecipientFingerprint(IntendedRecipientFingerprint(isCritical, recipientKey.version, recipientKey.fingerprint)) + } + + override fun addIntendedRecipientFingerprint(intendedRecipient: IntendedRecipientFingerprint): SignatureSubpackets = apply { + (this.intendedRecipientFingerprintSubpackets as MutableList).add(intendedRecipient) + } + + override fun clearIntendedRecipientFingerprints(): SignatureSubpackets = apply { + (this.intendedRecipientFingerprintSubpackets as MutableList).clear() + } + + override fun setExportable(): SignatureSubpackets = apply { + setExportable(true) + } + + override fun setExportable(isExportable: Boolean): SignatureSubpackets = apply { + setExportable(true, isExportable) + } + + override fun setExportable(isCritical: Boolean, isExportable: Boolean): SignatureSubpackets = apply { + setExportable(Exportable(isCritical, isExportable)) + } + + override fun setExportable(exportable: Exportable?): SignatureSubpackets = apply { + this.exportableSubpacket = exportable + } + + override fun setPolicyUrl(policyUrl: URL): SignatureSubpackets = apply { + setPolicyUrl(false, policyUrl) + } + + override fun setPolicyUrl(isCritical: Boolean, policyUrl: URL): SignatureSubpackets = apply { + setPolicyUrl(PolicyURI(isCritical, policyUrl.toString())) + } + + override fun setPolicyUrl(policyUrl: PolicyURI?): SignatureSubpackets = apply { + this.policyURISubpacket = policyURISubpacket + } + + override fun setRegularExpression(regex: CharSequence): SignatureSubpackets = apply { + setRegularExpression(false, regex) + } + + override fun setRegularExpression(isCritical: Boolean, regex: CharSequence): SignatureSubpackets = apply { + setRegularExpression(RegularExpression(isCritical, regex.toString())) + } + + override fun setRegularExpression(regex: RegularExpression?): SignatureSubpackets = apply { + this.regularExpressionSubpacket = regex + } + + override fun setRevocable(): SignatureSubpackets = apply { + setRevocable(true) + } + + override fun setRevocable(isRevocable: Boolean): SignatureSubpackets = apply { + setRevocable(true, isRevocable) + } + + override fun setRevocable(isCritical: Boolean, isRevocable: Boolean): SignatureSubpackets = apply { + setRevocable(Revocable(isCritical, isRevocable)) + } + + override fun setRevocable(revocable: Revocable?): SignatureSubpackets = apply { + this.revocableSubpacket = revocable + } + + override fun setSignatureTarget(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm, hashData: ByteArray): SignatureSubpackets = apply { + setSignatureTarget(true, keyAlgorithm, hashAlgorithm, hashData) + } + + override fun setSignatureTarget(isCritical: Boolean, keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm, hashData: ByteArray): SignatureSubpackets = apply { + setSignatureTarget(SignatureTarget(isCritical, keyAlgorithm.algorithmId, hashAlgorithm.algorithmId, hashData)) + } + + override fun setSignatureTarget(signatureTarget: SignatureTarget?): SignatureSubpackets = apply { + this.signatureTargetSubpacket = signatureTarget + } + + override fun setTrust(depth: Int, amount: Int): SignatureSubpackets = apply { + setTrust(true, depth, amount) + } + + override fun setTrust(isCritical: Boolean, depth: Int, amount: Int): SignatureSubpackets = apply { + setTrust(TrustSignature(isCritical, depth, amount)) + } + + override fun setTrust(trust: TrustSignature?): SignatureSubpackets = apply { + this.trustSubpacket = trust + } + + override fun addEmbeddedSignature(signature: PGPSignature): SignatureSubpackets = apply { + addEmbeddedSignature(true, signature) + } + + override fun addEmbeddedSignature(isCritical: Boolean, signature: PGPSignature): SignatureSubpackets = apply { + val sig = signature.encoded + val data = if (sig.size - 1 > 256) { + ByteArray(sig.size - 3) + } else { + ByteArray(sig.size - 2) + } + System.arraycopy(sig, sig.size - data.size, data, 0, data.size) + addEmbeddedSignature(EmbeddedSignature(isCritical, false, data)) + } + + override fun addEmbeddedSignature(embeddedSignature: EmbeddedSignature): SignatureSubpackets = apply { + (this.embeddedSignatureSubpackets as MutableList).add(embeddedSignature) + } + + override fun clearEmbeddedSignatures(): SignatureSubpackets = apply { + (this.embeddedSignatureSubpackets as MutableList).clear() + } + + fun addResidualSubpacket(subpacket: org.bouncycastle.bcpg.SignatureSubpacket): SignatureSubpackets = apply { + (residualSubpackets as MutableList).add(subpacket) + } +} \ No newline at end of file From b72f95b46cc00caff7eb93505a0045f76a4ee884 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 20 Oct 2023 14:55:51 +0200 Subject: [PATCH 146/155] Kotlin conversion: SignatureSubpacketsHelper --- .../subpackets/SignatureSubpacketsHelper.java | 196 ------------------ .../signature/subpackets/package-info.java | 8 - .../subpackets/SignatureSubpackets.kt | 8 +- .../subpackets/SignatureSubpacketsHelper.kt | 159 ++++++++++++++ 4 files changed, 161 insertions(+), 210 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/package-info.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.java deleted file mode 100644 index 2a7d4f83..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.java +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -import org.bouncycastle.bcpg.SignatureSubpacket; -import org.bouncycastle.bcpg.sig.EmbeddedSignature; -import org.bouncycastle.bcpg.sig.Exportable; -import org.bouncycastle.bcpg.sig.Features; -import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint; -import org.bouncycastle.bcpg.sig.KeyExpirationTime; -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.NotationData; -import org.bouncycastle.bcpg.sig.PolicyURI; -import org.bouncycastle.bcpg.sig.PreferredAlgorithms; -import org.bouncycastle.bcpg.sig.PrimaryUserID; -import org.bouncycastle.bcpg.sig.RegularExpression; -import org.bouncycastle.bcpg.sig.Revocable; -import org.bouncycastle.bcpg.sig.RevocationKey; -import org.bouncycastle.bcpg.sig.RevocationReason; -import org.bouncycastle.bcpg.sig.SignatureExpirationTime; -import org.bouncycastle.bcpg.sig.SignatureTarget; -import org.bouncycastle.bcpg.sig.SignerUserID; -import org.bouncycastle.bcpg.sig.TrustSignature; -import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.pgpainless.algorithm.Feature; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.util.RevocationAttributes; - -public class SignatureSubpacketsHelper { - - public static SignatureSubpackets applyFrom(PGPSignatureSubpacketVector vector, SignatureSubpackets subpackets) { - for (SignatureSubpacket subpacket : vector.toArray()) { - org.pgpainless.algorithm.SignatureSubpacket type = org.pgpainless.algorithm.SignatureSubpacket.requireFromCode(subpacket.getType()); - switch (type) { - case signatureCreationTime: - case issuerKeyId: - case issuerFingerprint: - // ignore, we override this anyways - break; - case signatureExpirationTime: - SignatureExpirationTime sigExpTime = (SignatureExpirationTime) subpacket; - subpackets.setSignatureExpirationTime(sigExpTime.isCritical(), sigExpTime.getTime()); - break; - case exportableCertification: - Exportable exp = (Exportable) subpacket; - subpackets.setExportable(exp.isCritical(), exp.isExportable()); - break; - case trustSignature: - TrustSignature trustSignature = (TrustSignature) subpacket; - subpackets.setTrust(trustSignature.isCritical(), trustSignature.getDepth(), trustSignature.getTrustAmount()); - break; - case revocable: - Revocable rev = (Revocable) subpacket; - subpackets.setRevocable(rev.isCritical(), rev.isRevocable()); - break; - case keyExpirationTime: - KeyExpirationTime keyExpTime = (KeyExpirationTime) subpacket; - subpackets.setKeyExpirationTime(keyExpTime.isCritical(), keyExpTime.getTime()); - break; - case preferredSymmetricAlgorithms: - subpackets.setPreferredSymmetricKeyAlgorithms((PreferredAlgorithms) subpacket); - break; - case revocationKey: - RevocationKey revocationKey = (RevocationKey) subpacket; - subpackets.addRevocationKey(revocationKey); - break; - case notationData: - NotationData notationData = (NotationData) subpacket; - subpackets.addNotationData(notationData.isCritical(), notationData.getNotationName(), notationData.getNotationValue()); - break; - case preferredHashAlgorithms: - subpackets.setPreferredHashAlgorithms((PreferredAlgorithms) subpacket); - break; - case preferredCompressionAlgorithms: - subpackets.setPreferredCompressionAlgorithms((PreferredAlgorithms) subpacket); - break; - case primaryUserId: - PrimaryUserID primaryUserID = (PrimaryUserID) subpacket; - subpackets.setPrimaryUserId(primaryUserID); - break; - case keyFlags: - KeyFlags flags = (KeyFlags) subpacket; - subpackets.setKeyFlags(flags.isCritical(), KeyFlag.fromBitmask(flags.getFlags()).toArray(new KeyFlag[0])); - break; - case signerUserId: - SignerUserID signerUserID = (SignerUserID) subpacket; - subpackets.setSignerUserId(signerUserID.isCritical(), signerUserID.getID()); - break; - case revocationReason: - RevocationReason reason = (RevocationReason) subpacket; - subpackets.setRevocationReason(reason.isCritical(), - RevocationAttributes.Reason.fromCode(reason.getRevocationReason()), - reason.getRevocationDescription()); - break; - case features: - Features f = (Features) subpacket; - subpackets.setFeatures(f.isCritical(), Feature.fromBitmask(f.getData()[0]).toArray(new Feature[0])); - break; - case signatureTarget: - SignatureTarget target = (SignatureTarget) subpacket; - subpackets.setSignatureTarget(target.isCritical(), - PublicKeyAlgorithm.requireFromId(target.getPublicKeyAlgorithm()), - HashAlgorithm.requireFromId(target.getHashAlgorithm()), - target.getHashData()); - break; - case embeddedSignature: - EmbeddedSignature embeddedSignature = (EmbeddedSignature) subpacket; - subpackets.addEmbeddedSignature(embeddedSignature); - break; - case intendedRecipientFingerprint: - IntendedRecipientFingerprint intendedRecipientFingerprint = (IntendedRecipientFingerprint) subpacket; - subpackets.addIntendedRecipientFingerprint(intendedRecipientFingerprint); - break; - case policyUrl: - PolicyURI policyURI = (PolicyURI) subpacket; - subpackets.setPolicyUrl(policyURI); - break; - case regularExpression: - RegularExpression regex = (RegularExpression) subpacket; - subpackets.setRegularExpression(regex); - break; - - case keyServerPreferences: - case preferredKeyServers: - case placeholder: - case preferredAEADAlgorithms: - case attestedCertification: - subpackets.addResidualSubpacket(subpacket); - break; - } - } - return subpackets; - } - - public static PGPSignatureSubpacketGenerator applyTo(SignatureSubpackets subpackets, PGPSignatureSubpacketGenerator generator) { - addSubpacket(generator, subpackets.getIssuerKeyIdSubpacket()); - addSubpacket(generator, subpackets.getIssuerFingerprintSubpacket()); - addSubpacket(generator, subpackets.getSignatureCreationTimeSubpacket()); - addSubpacket(generator, subpackets.getSignatureExpirationTimeSubpacket()); - addSubpacket(generator, subpackets.getExportableSubpacket()); - addSubpacket(generator, subpackets.getPolicyURISubpacket()); - addSubpacket(generator, subpackets.getRegularExpressionSubpacket()); - for (NotationData notationData : subpackets.getNotationDataSubpackets()) { - addSubpacket(generator, notationData); - } - for (IntendedRecipientFingerprint intendedRecipientFingerprint : subpackets.getIntendedRecipientFingerprintSubpackets()) { - addSubpacket(generator, intendedRecipientFingerprint); - } - for (RevocationKey revocationKey : subpackets.getRevocationKeySubpackets()) { - addSubpacket(generator, revocationKey); - } - addSubpacket(generator, subpackets.getSignatureTargetSubpacket()); - addSubpacket(generator, subpackets.getFeaturesSubpacket()); - addSubpacket(generator, subpackets.getKeyFlagsSubpacket()); - addSubpacket(generator, subpackets.getTrustSubpacket()); - addSubpacket(generator, subpackets.getPreferredCompressionAlgorithmsSubpacket()); - addSubpacket(generator, subpackets.getPreferredSymmetricKeyAlgorithmsSubpacket()); - addSubpacket(generator, subpackets.getPreferredHashAlgorithmsSubpacket()); - for (EmbeddedSignature embeddedSignature : subpackets.getEmbeddedSignatureSubpackets()) { - addSubpacket(generator, embeddedSignature); - } - addSubpacket(generator, subpackets.getSignerUserIdSubpacket()); - addSubpacket(generator, subpackets.getKeyExpirationTimeSubpacket()); - addSubpacket(generator, subpackets.getPrimaryUserIdSubpacket()); - addSubpacket(generator, subpackets.getRevocableSubpacket()); - addSubpacket(generator, subpackets.getRevocationReasonSubpacket()); - for (SignatureSubpacket subpacket : subpackets.getResidualSubpackets()) { - addSubpacket(generator, subpacket); - } - - return generator; - } - - private static void addSubpacket(PGPSignatureSubpacketGenerator generator, SignatureSubpacket subpacket) { - if (subpacket != null) { - generator.addCustomSubpacket(subpacket); - } - } - - public static PGPSignatureSubpacketVector toVector(SignatureSubpackets subpackets) { - PGPSignatureSubpacketGenerator generator = new PGPSignatureSubpacketGenerator(); - applyTo(subpackets, generator); - return generator.generate(); - } - - public static PGPSignatureSubpacketVector toVector(RevocationSignatureSubpackets subpackets) { - PGPSignatureSubpacketGenerator generator = new PGPSignatureSubpacketGenerator(); - applyTo((SignatureSubpackets) subpackets, generator); - return generator.generate(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/package-info.java deleted file mode 100644 index 09dfd6a2..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to OpenPGP signatures. - */ -package org.pgpainless.signature.subpackets; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt index d7d907f0..a88e4e8a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt @@ -70,16 +70,12 @@ class SignatureSubpackets @JvmStatic fun createSubpacketsFrom(base: PGPSignatureSubpacketVector): SignatureSubpackets { - return SignatureSubpackets().apply { - SignatureSubpacketsHelper.applyFrom(base, this) - } + return SignatureSubpacketsHelper.applyFrom(base, SignatureSubpackets()) } @JvmStatic fun createHashedSubpackets(issuer: PGPPublicKey): SignatureSubpackets { - return SignatureSubpackets().apply { - setIssuerFingerprintAndKeyId(issuer) - } + return createEmptySubpackets().setIssuerFingerprintAndKeyId(issuer) } @JvmStatic diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.kt new file mode 100644 index 00000000..6767b0af --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.kt @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +import org.bouncycastle.bcpg.sig.* +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector +import org.pgpainless.algorithm.* +import org.pgpainless.key.util.RevocationAttributes + +class SignatureSubpacketsHelper { + + companion object { + @JvmStatic + fun applyFrom(vector: PGPSignatureSubpacketVector, subpackets: SignatureSubpackets) = subpackets.apply { + for (subpacket in vector.toArray()) { + val type = SignatureSubpacket.requireFromCode(subpacket.type) + when (type) { + SignatureSubpacket.signatureCreationTime, + SignatureSubpacket.issuerKeyId, + SignatureSubpacket.issuerFingerprint -> { /* ignore, we override this anyway */ } + SignatureSubpacket.signatureExpirationTime -> (subpacket as SignatureExpirationTime).let { + subpackets.setSignatureExpirationTime(it.isCritical, it.time) + } + SignatureSubpacket.exportableCertification -> (subpacket as Exportable).let { + subpackets.setExportable(it.isCritical, it.isExportable) + } + SignatureSubpacket.trustSignature -> (subpacket as TrustSignature).let { + subpackets.setTrust(it.isCritical, it.depth, it.trustAmount) + } + SignatureSubpacket.revocable -> (subpacket as Revocable).let { + subpackets.setRevocable(it.isCritical, it.isRevocable) + } + SignatureSubpacket.keyExpirationTime -> (subpacket as KeyExpirationTime).let { + subpackets.setKeyExpirationTime(it.isCritical, it.time) + } + SignatureSubpacket.preferredSymmetricAlgorithms -> (subpacket as PreferredAlgorithms).let { + subpackets.setPreferredSymmetricKeyAlgorithms(PreferredAlgorithms(it.type, it.isCritical, it.isLongLength, it.data)) + } + SignatureSubpacket.preferredHashAlgorithms -> (subpacket as PreferredAlgorithms).let { + subpackets.setPreferredHashAlgorithms(PreferredAlgorithms(it.type, it.isCritical, it.isLongLength, it.data)) + } + SignatureSubpacket.preferredCompressionAlgorithms -> (subpacket as PreferredAlgorithms).let { + subpackets.setPreferredCompressionAlgorithms(PreferredAlgorithms(it.type, it.isCritical, it.isLongLength, it.data)) + } + SignatureSubpacket.revocationKey -> (subpacket as RevocationKey).let { + subpackets.addRevocationKey(RevocationKey(it.isCritical, it.signatureClass, it.algorithm, it.fingerprint)) + } + SignatureSubpacket.notationData -> (subpacket as NotationData).let { + subpackets.addNotationData(it.isCritical, it.isHumanReadable, it.notationName, it.notationValue) + } + SignatureSubpacket.primaryUserId -> (subpacket as PrimaryUserID).let { + subpackets.setPrimaryUserId(PrimaryUserID(it.isCritical, it.isPrimaryUserID)) + } + SignatureSubpacket.keyFlags -> (subpacket as KeyFlags).let { + subpackets.setKeyFlags(it.isCritical, *(KeyFlag.fromBitmask(it.flags).toTypedArray())) + } + SignatureSubpacket.signerUserId -> (subpacket as SignerUserID).let { + subpackets.setSignerUserId(it.isCritical, it.id) + } + SignatureSubpacket.revocationReason -> (subpacket as RevocationReason).let { + subpackets.setRevocationReason(it.isCritical, RevocationAttributes.Reason.fromCode(it.revocationReason), it.revocationDescription) + } + SignatureSubpacket.features -> (subpacket as Features).let { + subpackets.setFeatures(it.isCritical, *(Feature.fromBitmask(it.features.toInt()).toTypedArray())) + } + SignatureSubpacket.signatureTarget -> (subpacket as SignatureTarget).let { + subpackets.setSignatureTarget(it.isCritical, + PublicKeyAlgorithm.requireFromId(it.publicKeyAlgorithm), + HashAlgorithm.requireFromId(it.hashAlgorithm), + it.hashData) + } + SignatureSubpacket.embeddedSignature -> (subpacket as EmbeddedSignature).let { + subpackets.addEmbeddedSignature(it) + } + SignatureSubpacket.intendedRecipientFingerprint -> (subpacket as IntendedRecipientFingerprint).let { + subpackets.addIntendedRecipientFingerprint(it) + } + SignatureSubpacket.policyUrl -> (subpacket as PolicyURI).let { + subpackets.setPolicyUrl(it) + } + SignatureSubpacket.regularExpression -> (subpacket as RegularExpression).let { + subpackets.setRegularExpression(it) + } + SignatureSubpacket.keyServerPreferences, + SignatureSubpacket.preferredKeyServers, + SignatureSubpacket.placeholder, + SignatureSubpacket.preferredAEADAlgorithms, + SignatureSubpacket.attestedCertification -> subpackets.addResidualSubpacket(subpacket) + } + } + } + + @JvmStatic + fun applyTo(subpackets: SignatureSubpackets, generator: PGPSignatureSubpacketGenerator): PGPSignatureSubpacketGenerator { + return generator.apply { + addSubpacket(subpackets.issuerKeyIdSubpacket) + addSubpacket(subpackets.issuerFingerprintSubpacket) + addSubpacket(subpackets.signatureCreationTimeSubpacket) + addSubpacket(subpackets.signatureExpirationTimeSubpacket) + addSubpacket(subpackets.exportableSubpacket) + addSubpacket(subpackets.policyURISubpacket) + addSubpacket(subpackets.regularExpressionSubpacket) + for (notation in subpackets.notationDataSubpackets) { + addSubpacket(notation) + } + for (recipient in subpackets.intendedRecipientFingerprintSubpackets) { + addSubpacket(recipient) + } + for (revocationKey in subpackets.revocationKeySubpackets) { + addSubpacket(revocationKey) + } + addSubpacket(subpackets.signatureTargetSubpacket) + addSubpacket(subpackets.featuresSubpacket) + addSubpacket(subpackets.keyFlagsSubpacket) + addSubpacket(subpackets.trustSubpacket) + addSubpacket(subpackets.preferredCompressionAlgorithmsSubpacket) + addSubpacket(subpackets.preferredSymmetricKeyAlgorithmsSubpacket) + addSubpacket(subpackets.preferredHashAlgorithmsSubpacket) + for (embedded in subpackets.embeddedSignatureSubpackets) { + addSubpacket(embedded) + } + addSubpacket(subpackets.signerUserIdSubpacket) + addSubpacket(subpackets.keyExpirationTimeSubpacket) + addSubpacket(subpackets.primaryUserIdSubpacket) + addSubpacket(subpackets.revocableSubpacket) + addSubpacket(subpackets.revocationReasonSubpacket) + for (residual in subpackets.residualSubpackets) { + addSubpacket(residual) + } + } + } + + @JvmStatic + private fun PGPSignatureSubpacketGenerator.addSubpacket(subpacket: org.bouncycastle.bcpg.SignatureSubpacket?) { + if (subpacket != null) { + this.addCustomSubpacket(subpacket) + } + } + + @JvmStatic + fun toVector(subpackets: SignatureSubpackets): PGPSignatureSubpacketVector { + return PGPSignatureSubpacketGenerator().let { + applyTo(subpackets, it) + it.generate() + } + } + + @JvmStatic + fun toVector(subpackets: RevocationSignatureSubpackets): PGPSignatureSubpacketVector { + return PGPSignatureSubpacketGenerator().let { + applyTo(subpackets as SignatureSubpackets, it) + it.generate() + } + } + } +} \ No newline at end of file From 384347ed1ae7ddf32cf501f728de15bd2f3d249d Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Mon, 23 Oct 2023 12:30:18 +0530 Subject: [PATCH 147/155] build: add `ktfmt` via Spotless --- build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index 57cc04c4..48791e21 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,7 @@ buildscript { plugins { id 'org.jetbrains.kotlin.jvm' version "1.8.10" + id 'com.diffplug.spotless' version '6.22.0' apply false } apply from: 'version.gradle' @@ -30,6 +31,7 @@ allprojects { apply plugin: 'jacoco' apply plugin: 'checkstyle' apply plugin: 'kotlin' + apply plugin: 'com.diffplug.spotless' compileJava { options.release = 8 @@ -47,6 +49,12 @@ allprojects { toolVersion = '10.12.1' } + spotless { + kotlin { + ktfmt().dropboxStyle() + } + } + group 'org.pgpainless' description = "Simple to use OpenPGP API for Java based on Bouncycastle" version = shortVersion From 51e9bfc67f19e16a69790a8d92bd6b1c86a76a5f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 23 Oct 2023 14:24:31 +0200 Subject: [PATCH 148/155] Apply new formatting from 'gradle spotlessApply' --- .../src/main/kotlin/openpgp/DateExtensions.kt | 29 +- .../src/main/kotlin/openpgp/LongExtensions.kt | 12 +- .../CachingBcPublicKeyDataDecryptorFactory.kt | 40 +- .../extensions/PGPKeyRingExtensions.kt | 48 +- .../extensions/PGPPublicKeyExtensions.kt | 38 +- .../extensions/PGPSecretKeyExtensions.kt | 16 +- .../extensions/PGPSecretKeyRingExtensions.kt | 48 +- .../extensions/PGPSignatureExtensions.kt | 97 +-- .../main/kotlin/org/pgpainless/PGPainless.kt | 74 +- .../org/pgpainless/algorithm/AEADAlgorithm.kt | 14 +- .../pgpainless/algorithm/AlgorithmSuite.kt | 26 +- .../pgpainless/algorithm/CertificationType.kt | 15 +- .../algorithm/CompressionAlgorithm.kt | 18 +- .../algorithm/DocumentSignatureType.kt | 10 +- .../pgpainless/algorithm/EncryptionPurpose.kt | 16 +- .../org/pgpainless/algorithm/Feature.kt | 57 +- .../org/pgpainless/algorithm/HashAlgorithm.kt | 50 +- .../org/pgpainless/algorithm/KeyFlag.kt | 57 +- .../org/pgpainless/algorithm/OpenPgpPacket.kt | 11 +- .../algorithm/PublicKeyAlgorithm.kt | 82 +-- .../pgpainless/algorithm/RevocationState.kt | 38 +- .../algorithm/RevocationStateType.kt | 14 +- .../algorithm/SignatureSubpacket.kt | 328 ++++----- .../org/pgpainless/algorithm/SignatureType.kt | 156 ++-- .../pgpainless/algorithm/StreamEncoding.kt | 43 +- .../algorithm/SymmetricKeyAlgorithm.kt | 103 +-- .../pgpainless/algorithm/Trustworthiness.kt | 52 +- .../negotiation/HashAlgorithmNegotiator.kt | 26 +- .../SymmetricKeyAlgorithmNegotiator.kt | 38 +- .../authentication/CertificateAuthenticity.kt | 31 +- .../authentication/CertificateAuthority.kt | 68 +- .../ConsumerOptions.kt | 172 ++--- .../CustomPublicKeyDataDecryptorFactory.kt | 14 +- .../DecryptionBuilder.kt | 12 +- .../DecryptionBuilderInterface.kt | 7 +- .../DecryptionStream.kt | 8 +- .../HardwareSecurity.kt | 49 +- .../IntegrityProtectedInputStream.kt | 16 +- .../MessageInspector.kt | 41 +- .../MessageMetadata.kt | 354 ++++----- .../MissingKeyPassphraseStrategy.kt | 13 +- .../MissingPublicKeyCallback.kt | 17 +- .../OpenPgpMessageInputStream.kt | 445 +++++++----- .../SignatureVerification.kt | 38 +- .../TeeBCPGInputStream.kt | 55 +- .../ClearsignedMessageUtil.kt | 40 +- .../InMemoryMultiPassStrategy.kt | 12 +- .../cleartext_signatures/MultiPassStrategy.kt | 37 +- .../WriteToFileMultiPassStrategy.kt | 16 +- .../syntax_check/InputSymbol.kt | 31 +- .../syntax_check/OpenPgpMessageSyntax.kt | 15 +- .../syntax_check/PDA.kt | 67 +- .../syntax_check/StackSymbol.kt | 14 +- .../syntax_check/State.kt | 6 +- .../syntax_check/Syntax.kt | 19 +- .../syntax_check/Transition.kt | 22 +- .../encryption_signing/BcHashContextSigner.kt | 34 +- .../BcPGPHashContextContentSignerBuilder.kt | 55 +- .../encryption_signing/CRLFGeneratorStream.kt | 18 +- .../encryption_signing/EncryptionBuilder.kt | 35 +- .../EncryptionBuilderInterface.kt | 8 +- .../encryption_signing/EncryptionOptions.kt | 176 +++-- .../encryption_signing/EncryptionResult.kt | 50 +- .../encryption_signing/EncryptionStream.kt | 96 ++- .../PGPHashContextContentSignerBuilder.kt | 24 +- .../encryption_signing/ProducerOptions.kt | 118 ++- .../SignatureGenerationStream.kt | 21 +- .../encryption_signing/SigningOptions.kt | 342 +++++---- .../implementation/BcImplementationFactory.kt | 112 +-- .../implementation/ImplementationFactory.kt | 78 +- .../JceImplementationFactory.kt | 120 +-- .../org/pgpainless/key/OpenPgpFingerprint.kt | 96 +-- .../pgpainless/key/OpenPgpV4Fingerprint.kt | 31 +- .../pgpainless/key/OpenPgpV5Fingerprint.kt | 22 +- .../pgpainless/key/OpenPgpV6Fingerprint.kt | 22 +- .../org/pgpainless/key/SubkeyIdentifier.kt | 40 +- .../org/pgpainless/key/_64DigitFingerprint.kt | 54 +- .../key/certification/CertifyCertificate.kt | 109 +-- .../key/collection/PGPKeyRingCollection.kt | 54 +- .../key/generation/KeyRingBuilder.kt | 141 ++-- .../key/generation/KeyRingBuilderInterface.kt | 13 +- .../key/generation/KeyRingTemplates.kt | 229 +++--- .../org/pgpainless/key/generation/KeySpec.kt | 12 +- .../key/generation/KeySpecBuilder.kt | 54 +- .../key/generation/KeySpecBuilderInterface.kt | 12 +- .../key/generation/type/KeyLength.kt | 2 +- .../pgpainless/key/generation/type/KeyType.kt | 43 +- .../key/generation/type/ecc/EllipticCurve.kt | 23 +- .../key/generation/type/ecc/ecdh/ECDH.kt | 5 +- .../key/generation/type/ecc/ecdsa/ECDSA.kt | 5 +- .../key/generation/type/eddsa/EdDSA.kt | 5 +- .../key/generation/type/eddsa/EdDSACurve.kt | 6 +- .../key/generation/type/elgamal/ElGamal.kt | 5 +- .../generation/type/elgamal/ElGamalLength.kt | 79 +- .../pgpainless/key/generation/type/rsa/RSA.kt | 13 +- .../key/generation/type/rsa/RsaLength.kt | 2 +- .../pgpainless/key/generation/type/xdh/XDH.kt | 7 +- .../key/generation/type/xdh/XDHSpec.kt | 7 +- .../org/pgpainless/key/info/KeyAccessor.kt | 77 +- .../kotlin/org/pgpainless/key/info/KeyInfo.kt | 66 +- .../org/pgpainless/key/info/KeyRingInfo.kt | 686 +++++++++--------- .../secretkeyring/SecretKeyRingEditor.kt | 632 ++++++++++------ .../SecretKeyRingEditorInterface.kt | 442 +++++------ .../pgpainless/key/parsing/KeyRingReader.kt | 172 ++--- .../protection/BaseSecretKeyRingProtector.kt | 32 +- .../CachingSecretKeyRingProtector.kt | 89 ++- .../protection/KeyRingProtectionSettings.kt | 37 +- .../PasswordBasedSecretKeyRingProtector.kt | 58 +- .../key/protection/SecretKeyRingProtector.kt | 86 ++- .../key/protection/UnlockSecretKey.kt | 34 +- .../protection/UnprotectedKeysProtector.kt | 5 +- .../key/protection/fixes/S2KUsageFix.kt | 40 +- .../MapBasedPassphraseProvider.kt | 7 +- .../SecretKeyPassphraseProvider.kt | 19 +- .../SolitaryPassphraseProvider.kt | 6 +- .../org/pgpainless/key/util/KeyIdUtil.kt | 13 +- .../org/pgpainless/key/util/KeyRingUtils.kt | 256 ++++--- .../util/PublicKeyParameterValidationUtil.kt | 110 ++- .../key/util/RevocationAttributes.kt | 99 ++- .../kotlin/org/pgpainless/key/util/UserId.kt | 125 ++-- .../kotlin/org/pgpainless/policy/Policy.kt | 314 ++++---- .../provider/BouncyCastleProviderFactory.kt | 4 +- .../pgpainless/provider/ProviderFactory.kt | 17 +- .../pgpainless/signature/SignatureUtils.kt | 151 ++-- .../consumer/CertificateValidator.kt | 223 +++--- .../consumer/OnePassSignatureCheck.kt | 15 +- .../signature/consumer/SignatureCheck.kt | 18 +- .../SignatureCreationDateComparator.kt | 18 +- .../signature/consumer/SignaturePicker.kt | 273 ++++--- .../consumer/SignatureValidityComparator.kt | 15 +- .../subpackets/BaseSignatureSubpackets.kt | 63 +- .../subpackets/CertificationSubpackets.kt | 3 +- .../RevocationSignatureSubpackets.kt | 17 +- .../subpackets/SelfSignatureSubpackets.kt | 65 +- .../subpackets/SignatureSubpacketCallback.kt | 2 +- .../subpackets/SignatureSubpackets.kt | 405 +++++++---- .../subpackets/SignatureSubpacketsHelper.kt | 202 ++++-- .../subpackets/SignatureSubpacketsUtil.kt | 322 ++++---- .../kotlin/org/pgpainless/util/ArmorUtils.kt | 272 +++---- .../util/ArmoredInputStreamFactory.kt | 23 +- .../util/ArmoredOutputStreamFactory.kt | 69 +- .../kotlin/org/pgpainless/util/DateUtil.kt | 17 +- .../kotlin/org/pgpainless/util/MultiMap.kt | 59 +- .../org/pgpainless/util/NotationRegistry.kt | 16 +- .../kotlin/org/pgpainless/util/Passphrase.kt | 63 +- .../kotlin/org/pgpainless/util/SessionKey.kt | 13 +- .../util/selection/userid/SelectUserId.kt | 81 +-- 147 files changed, 6186 insertions(+), 5198 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt b/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt index a1c80710..2763cb55 100644 --- a/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt +++ b/pgpainless-core/src/main/kotlin/openpgp/DateExtensions.kt @@ -11,40 +11,38 @@ import java.util.* /** * Return a new date which represents this date plus the given amount of seconds added. * - * Since '0' is a special date value in the OpenPGP specification - * (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0. + * Since '0' is a special date value in the OpenPGP specification (e.g. '0' means no expiration for + * expiration dates), this method will return 'null' if seconds is 0. * * @param date date * @param seconds number of seconds to be added * @return date plus seconds or null if seconds is '0' */ fun Date.plusSeconds(seconds: Long): Date? { - require(Long.MAX_VALUE - time > seconds) { "Adding $seconds seconds to this date would cause time to overflow." } - return if (seconds == 0L) null - else Date(this.time + 1000 * seconds) + require(Long.MAX_VALUE - time > seconds) { + "Adding $seconds seconds to this date would cause time to overflow." + } + return if (seconds == 0L) null else Date(this.time + 1000 * seconds) } val Date.asSeconds: Long get() = time / 1000 fun Date.secondsTill(later: Date): Long { - require(this <= later) { - "Timestamp MUST be before the later timestamp." - } + require(this <= later) { "Timestamp MUST be before the later timestamp." } return later.asSeconds - this.asSeconds } -/** - * Return a new [Date] instance with this instance's time floored down to seconds precision. - */ +/** Return a new [Date] instance with this instance's time floored down to seconds precision. */ fun Date.toSecondsPrecision(): Date { return Date(asSeconds * 1000) } internal val parser: SimpleDateFormat - // Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every invocation. - get() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss z") - .apply { timeZone = TimeZone.getTimeZone("UTC") } + // Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every + // invocation. + get() = + SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").apply { timeZone = TimeZone.getTimeZone("UTC") } /** * Format a date as UTC timestamp. @@ -55,12 +53,13 @@ fun Date.formatUTC(): String = parser.format(this) /** * Parse a UTC timestamp into a date. + * * @return date */ fun String.parseUTC(): Date { return try { parser.parse(this) - } catch (e : ParseException) { + } catch (e: ParseException) { throw IllegalArgumentException("Malformed UTC timestamp: $this", e) } } diff --git a/pgpainless-core/src/main/kotlin/openpgp/LongExtensions.kt b/pgpainless-core/src/main/kotlin/openpgp/LongExtensions.kt index 13f94943..c6c318b3 100644 --- a/pgpainless-core/src/main/kotlin/openpgp/LongExtensions.kt +++ b/pgpainless-core/src/main/kotlin/openpgp/LongExtensions.kt @@ -4,22 +4,18 @@ package openpgp -/** - * Format this Long as an OpenPGP key-ID (16 digit uppercase hex number). - */ +/** Format this Long as an OpenPGP key-ID (16 digit uppercase hex number). */ fun Long.openPgpKeyId(): String { return String.format("%016X", this).uppercase() } -/** - * Parse a Long form a 16 digit hex encoded OpenPgp key-ID. - */ +/** Parse a Long form a 16 digit hex encoded OpenPgp key-ID. */ fun Long.Companion.fromOpenPgpKeyId(hexKeyId: String): Long { require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) { "Provided long key-id does not match expected format. " + - "A long key-id consists of 16 hexadecimal characters." + "A long key-id consists of 16 hexadecimal characters." } // Calling toLong() only fails with a NumberFormatException. // Therefore, we call toULong(16).toLong(), which seems to work. return hexKeyId.toULong(16).toLong() -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt index 60b860f2..3a6f5351 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/CachingBcPublicKeyDataDecryptorFactory.kt @@ -11,40 +11,46 @@ import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactor import org.pgpainless.key.SubkeyIdentifier /** - * Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys. - * That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted. + * Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys. That + * way, if a message needs to be decrypted multiple times, expensive private key operations can be + * omitted. * - * This implementation changes the behavior or [recoverSessionData] to first return any - * cache hits. - * If no hit is found, the method call is delegated to the underlying [PublicKeyDataDecryptorFactory]. - * The result of that is then placed in the cache and returned. + * This implementation changes the behavior or [recoverSessionData] to first return any cache hits. + * If no hit is found, the method call is delegated to the underlying + * [PublicKeyDataDecryptorFactory]. The result of that is then placed in the cache and returned. */ class CachingBcPublicKeyDataDecryptorFactory( - privateKey: PGPPrivateKey, - override val subkeyIdentifier: SubkeyIdentifier + privateKey: PGPPrivateKey, + override val subkeyIdentifier: SubkeyIdentifier ) : BcPublicKeyDataDecryptorFactory(privateKey), CustomPublicKeyDataDecryptorFactory { private val cachedSessions: MutableMap = mutableMapOf() - override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array): ByteArray = - lookupSessionKeyData(secKeyData) ?: - costlyRecoverSessionData(keyAlgorithm, secKeyData) - .also { cacheSessionKeyData(secKeyData, it) } + override fun recoverSessionData( + keyAlgorithm: Int, + secKeyData: Array + ): ByteArray = + lookupSessionKeyData(secKeyData) + ?: costlyRecoverSessionData(keyAlgorithm, secKeyData).also { + cacheSessionKeyData(secKeyData, it) + } private fun lookupSessionKeyData(secKeyData: Array): ByteArray? = - cachedSessions[toKey(secKeyData)]?.clone() + cachedSessions[toKey(secKeyData)]?.clone() - private fun costlyRecoverSessionData(keyAlgorithm: Int, secKeyData: Array): ByteArray = - super.recoverSessionData(keyAlgorithm, secKeyData) + private fun costlyRecoverSessionData( + keyAlgorithm: Int, + secKeyData: Array + ): ByteArray = super.recoverSessionData(keyAlgorithm, secKeyData) private fun cacheSessionKeyData(secKeyData: Array, sessionKey: ByteArray) { cachedSessions[toKey(secKeyData)] = sessionKey.clone() } private fun toKey(secKeyData: Array): String = - Base64.toBase64String(secKeyData[0]) + Base64.toBase64String(secKeyData[0]) fun clear() { cachedSessions.clear() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt index afa38aa6..611fa591 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt @@ -13,12 +13,10 @@ import org.pgpainless.PGPainless import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.SubkeyIdentifier -/** - * Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. - */ +/** Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. */ fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean = - this.publicKey.keyID == subkeyIdentifier.primaryKeyId && - this.getPublicKey(subkeyIdentifier.subkeyId) != null + this.publicKey.keyID == subkeyIdentifier.primaryKeyId && + this.getPublicKey(subkeyIdentifier.subkeyId) != null /** * Return true, if the [PGPKeyRing] contains a public key with the given key-ID. @@ -26,8 +24,7 @@ fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean = * @param keyId keyId * @return true if key with the given key-ID is present, false otherwise */ -fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = - this.getPublicKey(keyId) != null +fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = this.getPublicKey(keyId) != null /** * Return true, if the [PGPKeyRing] contains a public key with the given fingerprint. @@ -36,7 +33,7 @@ fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = * @return true if key with the given fingerprint is present, false otherwise */ fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean = - this.getPublicKey(fingerprint) != null + this.getPublicKey(fingerprint) != null /** * Return the [PGPPublicKey] with the given [OpenPgpFingerprint] or null, if no such key is present. @@ -45,36 +42,33 @@ fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean = * @return public key */ fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = - this.getPublicKey(fingerprint.bytes) + this.getPublicKey(fingerprint.bytes) fun PGPKeyRing.requirePublicKey(keyId: Long): PGPPublicKey = - getPublicKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.") + getPublicKey(keyId) + ?: throw NoSuchElementException( + "OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.") fun PGPKeyRing.requirePublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey = - getPublicKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.") + getPublicKey(fingerprint) + ?: throw NoSuchElementException( + "OpenPGP key does not contain key with fingerprint $fingerprint.") /** - * Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. - * If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to - * identify the [PGPPublicKey] via its key-ID. + * Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If + * the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID + * subpacket to identify the [PGPPublicKey] via its key-ID. */ fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? = - signature.fingerprint?.let { this.getPublicKey(it) } ?: - this.getPublicKey(signature.keyID) + signature.fingerprint?.let { this.getPublicKey(it) } ?: this.getPublicKey(signature.keyID) -/** - * Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet. - */ +/** Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */ fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? = - this.getPublicKey(onePassSignature.keyID) + this.getPublicKey(onePassSignature.keyID) -/** - * Return the [OpenPgpFingerprint] of this OpenPGP key. - */ +/** Return the [OpenPgpFingerprint] of this OpenPGP key. */ val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint get() = OpenPgpFingerprint.of(this) -/** - * Return this OpenPGP key as an ASCII armored String. - */ -fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this) \ No newline at end of file +/** Return this OpenPGP key as an ASCII armored String. */ +fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt index ad51c6f4..847f1cf1 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt @@ -15,8 +15,8 @@ import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.generation.type.eddsa.EdDSACurve /** - * For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and [PublicKeyAlgorithm.EDDSA], - * this method returns the name of the underlying elliptic curve. + * For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and + * [PublicKeyAlgorithm.EDDSA], this method returns the name of the underlying elliptic curve. * * For other key types or unknown curves, this method throws an [IllegalArgumentException]. * @@ -24,27 +24,29 @@ import org.pgpainless.key.generation.type.eddsa.EdDSACurve */ fun PGPPublicKey.getCurveName(): String { PublicKeyAlgorithm.requireFromId(algorithm) - .let { - when (it) { - PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey - PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey - PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey - else -> throw IllegalArgumentException("No an elliptic curve public key ($it).") - } + .let { + when (it) { + PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey + PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey + PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey + else -> throw IllegalArgumentException("No an elliptic curve public key ($it).") } - .let { if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName else it.curveOID} - .let { it to ECUtil.getCurveName(it) } - .let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") } + } + .let { + if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName + else it.curveOID + } + .let { it to ECUtil.getCurveName(it) } + .let { + if (it.second != null) return it.second + else throw IllegalArgumentException("Unknown curve: ${it.first}") + } } -/** - * Return the [PublicKeyAlgorithm] of this key. - */ +/** Return the [PublicKeyAlgorithm] of this key. */ val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm get() = PublicKeyAlgorithm.requireFromId(algorithm) -/** - * Return the [OpenPgpFingerprint] of this key. - */ +/** Return the [OpenPgpFingerprint] of this key. */ val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint get() = OpenPgpFingerprint.of(this) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt index 3d759d1a..6049742c 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt @@ -7,7 +7,6 @@ package org.bouncycastle.extensions import org.bouncycastle.bcpg.S2K import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPrivateKey -import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor import org.pgpainless.algorithm.PublicKeyAlgorithm @@ -28,7 +27,7 @@ import org.pgpainless.util.Passphrase */ @Throws(PGPException::class, KeyIntegrityException::class) fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey = - UnlockSecretKey.unlockSecretKey(this, passphrase) + UnlockSecretKey.unlockSecretKey(this, passphrase) /** * Unlock the secret key to get its [PGPPrivateKey]. @@ -39,8 +38,9 @@ fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey = */ @Throws(PGPException::class, KeyIntegrityException::class) @JvmOverloads -fun PGPSecretKey.unlock(protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()): PGPPrivateKey = - UnlockSecretKey.unlockSecretKey(this, protector) +fun PGPSecretKey.unlock( + protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys() +): PGPPrivateKey = UnlockSecretKey.unlockSecretKey(this, protector) /** * Unlock the secret key to get its [PGPPrivateKey]. @@ -74,14 +74,10 @@ fun PGPSecretKey?.isDecrypted(): Boolean = (this == null) || (s2KUsage == 0) */ fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K) -/** - * Return the [PublicKeyAlgorithm] of this key. - */ +/** Return the [PublicKeyAlgorithm] of this key. */ val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm get() = publicKey.publicKeyAlgorithm -/** - * Return the [OpenPgpFingerprint] of this key. - */ +/** Return the [OpenPgpFingerprint] of this key. */ val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint get() = OpenPgpFingerprint.of(this) diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt index d0529d51..2116c748 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt @@ -8,9 +8,7 @@ import openpgp.openPgpKeyId import org.bouncycastle.openpgp.* import org.pgpainless.key.OpenPgpFingerprint -/** - * OpenPGP certificate containing the public keys of this OpenPGP key. - */ +/** OpenPGP certificate containing the public keys of this OpenPGP key. */ val PGPSecretKeyRing.certificate: PGPPublicKeyRing get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList()) @@ -20,8 +18,7 @@ val PGPSecretKeyRing.certificate: PGPPublicKeyRing * @param keyId keyId of the secret key * @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise */ -fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = - this.getSecretKey(keyId) != null +fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = this.getSecretKey(keyId) != null /** * Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given fingerprint. @@ -30,7 +27,7 @@ fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = * @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise */ fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean = - this.getSecretKey(fingerprint) != null + this.getSecretKey(fingerprint) != null /** * Return the [PGPSecretKey] with the given [OpenPgpFingerprint]. @@ -39,41 +36,44 @@ fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean = * @return the secret key or null */ fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? = - this.getSecretKey(fingerprint.bytes) + this.getSecretKey(fingerprint.bytes) /** * Return the [PGPSecretKey] with the given key-ID. * - * @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given key-ID + * @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given + * key-ID */ fun PGPSecretKeyRing.requireSecretKey(keyId: Long): PGPSecretKey = - getSecretKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.") + getSecretKey(keyId) + ?: throw NoSuchElementException( + "OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.") /** * Return the [PGPSecretKey] with the given fingerprint. * - * @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key with the given fingerprint + * @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key with the given + * fingerprint */ fun PGPSecretKeyRing.requireSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey = - getSecretKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.") + getSecretKey(fingerprint) + ?: throw NoSuchElementException( + "OpenPGP key does not contain key with fingerprint $fingerprint.") /** - * Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. - * If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to - * identify the [PGPSecretKey] via its key-ID. + * Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If + * the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID + * subpacket to identify the [PGPSecretKey] via its key-ID. */ fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? = - signature.fingerprint?.let { this.getSecretKey(it) } ?: - this.getSecretKey(signature.keyID) + signature.fingerprint?.let { this.getSecretKey(it) } ?: this.getSecretKey(signature.keyID) -/** - * Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet. - */ +/** Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */ fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? = - this.getSecretKey(onePassSignature.keyID) + this.getSecretKey(onePassSignature.keyID) fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? = - when(pkesk.version) { - 3 -> this.getSecretKey(pkesk.keyID) - else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.") - } \ No newline at end of file + when (pkesk.version) { + 3 -> this.getSecretKey(pkesk.keyID) + else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.") + } diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt index 4fe97bc7..2be011bd 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -4,6 +4,7 @@ package org.bouncycastle.extensions +import java.util.* import openpgp.plusSeconds import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature @@ -12,84 +13,84 @@ import org.pgpainless.algorithm.SignatureType import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.util.RevocationAttributes.Reason import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil -import java.util.* /** * Return the value of the KeyExpirationDate subpacket, or null, if the signature does not carry * such a subpacket. */ fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? = - SignatureSubpacketsUtil.getKeyExpirationTime(this) - ?.let { keyCreationDate.plusSeconds(it.time) } + SignatureSubpacketsUtil.getKeyExpirationTime(this)?.let { keyCreationDate.plusSeconds(it.time) } /** - * Return the value of the signature ExpirationTime subpacket, or null, if the signature - * does not carry such a subpacket. + * Return the value of the signature ExpirationTime subpacket, or null, if the signature does not + * carry such a subpacket. */ val PGPSignature.signatureExpirationDate: Date? - get() = SignatureSubpacketsUtil.getSignatureExpirationTime(this) - ?.let { this.creationTime.plusSeconds(it.time) } + get() = + SignatureSubpacketsUtil.getSignatureExpirationTime(this)?.let { + this.creationTime.plusSeconds(it.time) + } -/** - * Return true, if the signature is expired at the given reference time. - */ +/** Return true, if the signature is expired at the given reference time. */ fun PGPSignature.isExpired(referenceTime: Date = Date()) = - signatureExpirationDate?.let { referenceTime >= it } ?: false + signatureExpirationDate?.let { referenceTime >= it } ?: false /** * Return the key-ID of the issuer, determined by examining the IssuerKeyId and IssuerFingerprint * subpackets of the signature. */ val PGPSignature.issuerKeyId: Long - get() = when (version) { - 2, 3 -> keyID - else -> { - SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this) - ?.let { if (it != 0L) it else null } - ?: fingerprint?.keyId - ?: 0L + get() = + when (version) { + 2, + 3 -> keyID + else -> { + SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this)?.let { + if (it != 0L) it else null + } + ?: fingerprint?.keyId ?: 0L + } } - } -/** - * Return true, if the signature was likely issued by a key with the given fingerprint. - */ +/** Return true, if the signature was likely issued by a key with the given fingerprint. */ fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint): Boolean = - this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId) + this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId) /** * Return true, if the signature was likely issued by a key with the given fingerprint. + * * @param fingerprint fingerprint bytes */ @Deprecated("Discouraged in favor of method taking an OpenPgpFingerprint.") fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean = - try { - wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint)) - } catch (e : IllegalArgumentException) { - // Unknown fingerprint length / format - false - } - -fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = - wasIssuedBy(OpenPgpFingerprint.of(key)) - -/** - * Return true, if this signature is a hard revocation. - */ -val PGPSignature.isHardRevocation - get() = when (SignatureType.requireFromCode(signatureType)) { - SignatureType.KEY_REVOCATION, SignatureType.SUBKEY_REVOCATION, SignatureType.CERTIFICATION_REVOCATION -> { - SignatureSubpacketsUtil.getRevocationReason(this) - ?.let { Reason.isHardRevocation(it.revocationReason) } - ?: true // no reason -> hard revocation - } - else -> false // Not a revocation + try { + wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint)) + } catch (e: IllegalArgumentException) { + // Unknown fingerprint length / format + false } +fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = wasIssuedBy(OpenPgpFingerprint.of(key)) + +/** Return true, if this signature is a hard revocation. */ +val PGPSignature.isHardRevocation + get() = + when (SignatureType.requireFromCode(signatureType)) { + SignatureType.KEY_REVOCATION, + SignatureType.SUBKEY_REVOCATION, + SignatureType.CERTIFICATION_REVOCATION -> { + SignatureSubpacketsUtil.getRevocationReason(this)?.let { + Reason.isHardRevocation(it.revocationReason) + } + ?: true // no reason -> hard revocation + } + else -> false // Not a revocation + } + fun PGPSignature?.toRevocationState() = - if (this == null) RevocationState.notRevoked() - else if (isHardRevocation) RevocationState.hardRevoked() - else RevocationState.softRevoked(creationTime) + if (this == null) RevocationState.notRevoked() + else if (isHardRevocation) RevocationState.hardRevoked() + else RevocationState.softRevoked(creationTime) val PGPSignature.fingerprint: OpenPgpFingerprint? - get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this) \ No newline at end of file + get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt index 6aa9799e..d866ac93 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt @@ -4,6 +4,8 @@ package org.pgpainless +import java.io.OutputStream +import java.util.* import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing @@ -19,8 +21,6 @@ import org.pgpainless.key.parsing.KeyRingReader import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.policy.Policy import org.pgpainless.util.ArmorUtils -import java.io.OutputStream -import java.util.* class PGPainless private constructor() { @@ -28,25 +28,24 @@ class PGPainless private constructor() { /** * Generate a fresh OpenPGP key ring from predefined templates. + * * @return templates */ - @JvmStatic - fun generateKeyRing() = KeyRingTemplates() + @JvmStatic fun generateKeyRing() = KeyRingTemplates() /** * Build a custom OpenPGP key ring. * * @return builder */ - @JvmStatic - fun buildKeyRing() = KeyRingBuilder() + @JvmStatic fun buildKeyRing() = KeyRingBuilder() /** * Read an existing OpenPGP key ring. + * * @return builder */ - @JvmStatic - fun readKeyRing() = KeyRingReader() + @JvmStatic fun readKeyRing() = KeyRingReader() /** * Extract a public key certificate from a secret key. @@ -56,10 +55,11 @@ class PGPainless private constructor() { */ @JvmStatic fun extractCertificate(secretKey: PGPSecretKeyRing) = - KeyRingUtils.publicKeyRingFrom(secretKey) + KeyRingUtils.publicKeyRingFrom(secretKey) /** - * Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key server) together. + * Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key + * server) together. * * @param originalCopy local, older copy of the cert * @param updatedCopy updated, newer copy of the cert @@ -67,31 +67,27 @@ class PGPainless private constructor() { * @throws PGPException in case of an error */ @JvmStatic - fun mergeCertificate(originalCopy: PGPPublicKeyRing, - updatedCopy: PGPPublicKeyRing) = - PGPPublicKeyRing.join(originalCopy, updatedCopy) + fun mergeCertificate(originalCopy: PGPPublicKeyRing, updatedCopy: PGPPublicKeyRing) = + PGPPublicKeyRing.join(originalCopy, updatedCopy) /** * Wrap a key or certificate in ASCII armor. * * @param key key or certificate * @return ascii armored string - * * @throws IOException in case of an error during the armoring process */ @JvmStatic fun asciiArmor(key: PGPKeyRing) = - if (key is PGPSecretKeyRing) - ArmorUtils.toAsciiArmoredString(key) - else - ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing) + if (key is PGPSecretKeyRing) ArmorUtils.toAsciiArmoredString(key) + else ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing) /** - * Wrap a key of certificate in ASCII armor and write the result into the given [OutputStream]. + * Wrap a key of certificate in ASCII armor and write the result into the given + * [OutputStream]. * * @param key key or certificate * @param outputStream output stream - * * @throws IOException in case of an error during the armoring process */ @JvmStatic @@ -106,33 +102,34 @@ class PGPainless private constructor() { * * @param signature detached signature * @return ascii armored string - * * @throws IOException in case of an error during the armoring process */ @JvmStatic fun asciiArmor(signature: PGPSignature) = ArmorUtils.toAsciiArmoredString(signature) /** - * Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using OpenPGP. + * Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using + * OpenPGP. * * @return builder */ - @JvmStatic - fun encryptAndOrSign() = EncryptionBuilder() + @JvmStatic fun encryptAndOrSign() = EncryptionBuilder() /** - * Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using OpenPGP. + * Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using + * OpenPGP. * * @return builder */ - @JvmStatic - fun decryptAndOrVerify() = DecryptionBuilder() + @JvmStatic fun decryptAndOrVerify() = DecryptionBuilder() /** - * Make changes to a secret key at the given reference time. - * This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys. + * Make changes to a secret key at the given reference time. This method can be used to + * change key expiration dates and passphrases, or add/revoke user-ids and subkeys. + * *
otherDepth
. + * Return true, if the certified cert can introduce certificates with trust depth of + *
otherDepth
. * * @param otherDepth other certifications trust depth * @return true if the cert can introduce the other @@ -54,7 +54,8 @@ class Trustworthiness(amount: Int, depth: Int) { fun canIntroduce(otherDepth: Int) = depth > otherDepth /** - * Return true, if the certified cert can introduce certificates with the given
other
trust depth. + * Return true, if the certified cert can introduce certificates with the given
other
+ * trust depth. * * @param other other certificates trust depth * @return true if the cert can introduce the other @@ -66,33 +67,29 @@ class Trustworthiness(amount: Int, depth: Int) { const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced const val NOT_TRUSTED = 0 // 0 is not trusted - @JvmStatic - private val validRange = 0..255 + @JvmStatic private val validRange = 0..255 /** * This means that we are fully convinced of the trustworthiness of the key. * * @return builder */ - @JvmStatic - fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED) + @JvmStatic fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED) /** - * This means that we are marginally (partially) convinced of the trustworthiness of the key. + * This means that we are marginally (partially) convinced of the trustworthiness of the + * key. * * @return builder */ - @JvmStatic - fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED) + @JvmStatic fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED) /** - * This means that we do not trust the key. - * Can be used to overwrite previous trust. + * This means that we do not trust the key. Can be used to overwrite previous trust. * * @return builder */ - @JvmStatic - fun untrusted() = Builder(NOT_TRUSTED) + @JvmStatic fun untrusted() = Builder(NOT_TRUSTED) @JvmStatic private fun capAmount(amount: Int): Int { @@ -114,29 +111,28 @@ class Trustworthiness(amount: Int, depth: Int) { class Builder(val amount: Int) { /** - * The key is a trusted introducer (depth 1). - * Certifications made by this key are considered trustworthy. + * The key is a trusted introducer (depth 1). Certifications made by this key are considered + * trustworthy. * * @return trust */ fun introducer() = Trustworthiness(amount, 1) /** - * The key is a meta introducer (depth 2). - * This key can introduce trusted introducers of depth 1. + * The key is a meta introducer (depth 2). This key can introduce trusted introducers of + * depth 1. * * @return trust */ fun metaIntroducer() = Trustworthiness(amount, 2) /** - * The key is a meta introducer of depth
n
. - * This key can introduce meta introducers of depth
n - 1
. + * The key is a meta introducer of depth
n
. This key can introduce meta + * introducers of depth
n - 1
. * * @param n depth * @return trust */ fun metaIntroducerOfDepth(d: Int) = Trustworthiness(amount, d) } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt index 98cbe522..31d6b118 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt @@ -26,8 +26,8 @@ interface HashAlgorithmNegotiator { companion object { /** - * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for non-revocation signatures - * based on the given [Policy]. + * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for + * non-revocation signatures based on the given [Policy]. * * @param policy algorithm policy * @return negotiator @@ -38,8 +38,8 @@ interface HashAlgorithmNegotiator { } /** - * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation signatures - * based on the given [Policy]. + * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation + * signatures based on the given [Policy]. * * @param policy algorithm policy * @return negotiator @@ -57,15 +57,17 @@ interface HashAlgorithmNegotiator { * @return negotiator */ @JvmStatic - fun negotiateByPolicy(hashAlgorithmPolicy: Policy.HashAlgorithmPolicy): HashAlgorithmNegotiator { - return object: HashAlgorithmNegotiator { - override fun negotiateHashAlgorithm(orderedPrefs: Set): HashAlgorithm { - return orderedPrefs.firstOrNull { - hashAlgorithmPolicy.isAcceptable(it) - } ?: hashAlgorithmPolicy.defaultHashAlgorithm() + fun negotiateByPolicy( + hashAlgorithmPolicy: Policy.HashAlgorithmPolicy + ): HashAlgorithmNegotiator { + return object : HashAlgorithmNegotiator { + override fun negotiateHashAlgorithm( + orderedPrefs: Set + ): HashAlgorithm { + return orderedPrefs.firstOrNull { hashAlgorithmPolicy.isAcceptable(it) } + ?: hashAlgorithmPolicy.defaultHashAlgorithm() } - } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt index 1a6dfe7f..d11f2c03 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/SymmetricKeyAlgorithmNegotiator.kt @@ -4,38 +4,41 @@ package org.pgpainless.algorithm.negotiation +import java.lang.IllegalArgumentException import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.policy.Policy -import java.lang.IllegalArgumentException interface SymmetricKeyAlgorithmNegotiator { /** - * Negotiate a symmetric encryption algorithm. - * If the override is non-null, it will be returned instead of performing an actual negotiation. - * Otherwise, the list of ordered sets containing the preferences of different recipient keys will be - * used to determine a suitable symmetric encryption algorithm. + * Negotiate a symmetric encryption algorithm. If the override is non-null, it will be returned + * instead of performing an actual negotiation. Otherwise, the list of ordered sets containing + * the preferences of different recipient keys will be used to determine a suitable symmetric + * encryption algorithm. * * @param policy algorithm policy * @param override algorithm override (if not null, return this) * @param keyPreferences list of preferences per key * @return negotiated algorithm */ - fun negotiate(policy: Policy.SymmetricKeyAlgorithmPolicy, - override: SymmetricKeyAlgorithm?, - keyPreferences: List>): SymmetricKeyAlgorithm + fun negotiate( + policy: Policy.SymmetricKeyAlgorithmPolicy, + override: SymmetricKeyAlgorithm?, + keyPreferences: List> + ): SymmetricKeyAlgorithm companion object { @JvmStatic fun byPopularity(): SymmetricKeyAlgorithmNegotiator { - return object: SymmetricKeyAlgorithmNegotiator { + return object : SymmetricKeyAlgorithmNegotiator { override fun negotiate( - policy: Policy.SymmetricKeyAlgorithmPolicy, - override: SymmetricKeyAlgorithm?, - keyPreferences: List>): - SymmetricKeyAlgorithm { + policy: Policy.SymmetricKeyAlgorithmPolicy, + override: SymmetricKeyAlgorithm?, + keyPreferences: List> + ): SymmetricKeyAlgorithm { if (override == SymmetricKeyAlgorithm.NULL) { - throw IllegalArgumentException("Algorithm override cannot be NULL (plaintext).") + throw IllegalArgumentException( + "Algorithm override cannot be NULL (plaintext).") } if (override != null) { @@ -53,7 +56,9 @@ interface SymmetricKeyAlgorithmNegotiator { // Pivot map and sort by popularity ascending // score to list(algo) - val byScore = supportWeight.toList() + val byScore = + supportWeight + .toList() .map { e -> e.second to e.first } .groupBy { e -> e.first } .map { e -> e.key to e.value.map { it.second }.toList() } @@ -70,8 +75,7 @@ interface SymmetricKeyAlgorithmNegotiator { return policy.defaultSymmetricKeyAlgorithm } - } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthenticity.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthenticity.kt index 2024c710..f3d60bf6 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthenticity.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthenticity.kt @@ -6,19 +6,19 @@ package org.pgpainless.authentication import org.bouncycastle.openpgp.PGPPublicKeyRing -class CertificateAuthenticity(val userId: String, - val certificate: PGPPublicKeyRing, - val certificationChains: Map, - val targetAmount: Int) { +class CertificateAuthenticity( + val userId: String, + val certificate: PGPPublicKeyRing, + val certificationChains: Map, + val targetAmount: Int +) { val totalTrustAmount: Int get() = certificationChains.values.sum() - /** - * Return the degree of authentication of the binding in percent. - * 100% means full authentication. - * Values smaller than 100% mean partial authentication. + * Return the degree of authentication of the binding in percent. 100% means full + * authentication. Values smaller than 100% mean partial authentication. * * @return authenticity in percent */ @@ -42,16 +42,7 @@ class CertificateAuthenticity(val userId: String, * @param trustAmount actual trust amount of the chain * @param chainLinks links of the chain, starting at the trust-root, ending at the target. */ -class CertificationChain( - val trustAmount: Int, - val chainLinks: List) { +class CertificationChain(val trustAmount: Int, val chainLinks: List) {} -} - -/** - * A chain link contains a node in the trust chain. - */ -class ChainLink( - val certificate: PGPPublicKeyRing) { - -} \ No newline at end of file +/** A chain link contains a node in the trust chain. */ +class ChainLink(val certificate: PGPPublicKeyRing) {} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthority.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthority.kt index e510f48a..093c2325 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthority.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/authentication/CertificateAuthority.kt @@ -2,14 +2,14 @@ // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.authentication; +package org.pgpainless.authentication -import org.pgpainless.key.OpenPgpFingerprint import java.util.* +import org.pgpainless.key.OpenPgpFingerprint /** - * Interface for a CA that can authenticate trust-worthy certificates. - * Such a CA might be a fixed list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust. + * Interface for a CA that can authenticate trust-worthy certificates. Such a CA might be a fixed + * list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust. * * @see PGPainless-WOT * @see OpenPGP Web of Trust @@ -17,52 +17,58 @@ import java.util.* interface CertificateAuthority { /** - * Determine the authenticity of the binding between the given fingerprint and the userId. - * In other words, determine, how much evidence can be gathered, that the certificate with the given - * fingerprint really belongs to the user with the given userId. + * Determine the authenticity of the binding between the given fingerprint and the userId. In + * other words, determine, how much evidence can be gathered, that the certificate with the + * given fingerprint really belongs to the user with the given userId. * * @param fingerprint fingerprint of the certificate * @param userId userId - * @param email if true, the userId will be treated as an email address and all user-IDs containing - * the email address will be matched. + * @param email if true, the userId will be treated as an email address and all user-IDs + * containing the email address will be matched. * @param referenceTime reference time at which the binding shall be evaluated - * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated, - * 60 = partially authenticated...) + * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly + * authenticated, 60 = partially authenticated...) * @return information about the authenticity of the binding */ - fun authenticateBinding(fingerprint: OpenPgpFingerprint, - userId: String, - email: Boolean, - referenceTime: Date, - targetAmount: Int): CertificateAuthenticity; + fun authenticateBinding( + fingerprint: OpenPgpFingerprint, + userId: String, + email: Boolean, + referenceTime: Date, + targetAmount: Int + ): CertificateAuthenticity /** * Lookup certificates, which carry a trustworthy binding to the given userId. * * @param userId userId - * @param email if true, the user-ID will be treated as an email address and all user-IDs containing - * the email address will be matched. + * @param email if true, the user-ID will be treated as an email address and all user-IDs + * containing the email address will be matched. * @param referenceTime reference time at which the binding shall be evaluated - * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated, - * 60 = partially authenticated...) + * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly + * authenticated, 60 = partially authenticated...) * @return list of identified bindings */ - fun lookupByUserId(userId: String, - email: Boolean, - referenceTime: Date, - targetAmount: Int): List + fun lookupByUserId( + userId: String, + email: Boolean, + referenceTime: Date, + targetAmount: Int + ): List /** - * Identify trustworthy bindings for a certificate. - * The result is a list of authenticatable userIds on the certificate. + * Identify trustworthy bindings for a certificate. The result is a list of authenticatable + * userIds on the certificate. * * @param fingerprint fingerprint of the certificate * @param referenceTime reference time for trust calculations - * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated, - * 60 = partially authenticated...) + * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly + * authenticated, 60 = partially authenticated...) * @return list of identified bindings */ - fun identifyByFingerprint(fingerprint: OpenPgpFingerprint, - referenceTime: Date, - targetAmount: Int): List + fun identifyByFingerprint( + fingerprint: OpenPgpFingerprint, + referenceTime: Date, + targetAmount: Int + ): List } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt index e0ec1fd5..dff33b2d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt @@ -4,6 +4,9 @@ package org.pgpainless.decryption_verification +import java.io.IOException +import java.io.InputStream +import java.util.* import org.bouncycastle.extensions.getPublicKeyFor import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory @@ -14,13 +17,8 @@ import org.pgpainless.key.protection.SecretKeyRingProtector import org.pgpainless.signature.SignatureUtils import org.pgpainless.util.Passphrase import org.pgpainless.util.SessionKey -import java.io.IOException -import java.io.InputStream -import java.util.* -/** - * Options for decryption and signature verification. - */ +/** Options for decryption and signature verification. */ class ConsumerOptions { private var ignoreMDCErrors = false @@ -34,15 +32,16 @@ class ConsumerOptions { private var missingCertificateCallback: MissingPublicKeyCallback? = null private var sessionKey: SessionKey? = null - private val customDecryptorFactories = mutableMapOf() + private val customDecryptorFactories = + mutableMapOf() private val decryptionKeys = mutableMapOf() private val decryptionPassphrases = mutableSetOf() private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy() /** - * Consider signatures on the message made before the given timestamp invalid. - * Null means no limitation. + * Consider signatures on the message made before the given timestamp invalid. Null means no + * limitation. * * @param timestamp timestamp * @return options @@ -54,8 +53,8 @@ class ConsumerOptions { fun getVerifyNotBefore() = verifyNotBefore /** - * Consider signatures on the message made after the given timestamp invalid. - * Null means no limitation. + * Consider signatures on the message made after the given timestamp invalid. Null means no + * limitation. * * @param timestamp timestamp * @return options @@ -82,26 +81,27 @@ class ConsumerOptions { * @param verificationCerts certificates for signature verification * @return options */ - fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions = apply { - for (cert in verificationCerts) { - addVerificationCert(cert) + fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions = + apply { + for (cert in verificationCerts) { + addVerificationCert(cert) + } } - } /** * Add some detached signatures from the given [InputStream] for verification. * * @param signatureInputStream input stream of detached signatures * @return options - * * @throws IOException in case of an IO error * @throws PGPException in case of an OpenPGP error */ @Throws(IOException::class, PGPException::class) - fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions = apply { - val signatures = SignatureUtils.readSignatures(signatureInputStream) - addVerificationOfDetachedSignatures(signatures) - } + fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions = + apply { + val signatures = SignatureUtils.readSignatures(signatureInputStream) + addVerificationOfDetachedSignatures(signatures) + } /** * Add some detached signatures for verification. @@ -109,7 +109,9 @@ class ConsumerOptions { * @param detachedSignatures detached signatures * @return options */ - fun addVerificationOfDetachedSignatures(detachedSignatures: List): ConsumerOptions = apply { + fun addVerificationOfDetachedSignatures( + detachedSignatures: List + ): ConsumerOptions = apply { for (signature in detachedSignatures) { addVerificationOfDetachedSignature(signature) } @@ -121,14 +123,16 @@ class ConsumerOptions { * @param detachedSignature detached signature * @return options */ - fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions = apply { - detachedSignatures.add(detachedSignature) - } + fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions = + apply { + detachedSignatures.add(detachedSignature) + } fun getDetachedSignatures() = detachedSignatures.toList() /** - * Set a callback that's used when a certificate (public key) is missing for signature verification. + * Set a callback that's used when a certificate (public key) is missing for signature + * verification. * * @param callback callback * @return options @@ -152,18 +156,18 @@ class ConsumerOptions { fun getSessionKey() = sessionKey /** - * Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] - * is used to decrypt it when needed. + * Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] is + * used to decrypt it when needed. * * @param key key * @param keyRingProtector protector for the secret key * @return options */ @JvmOverloads - fun addDecryptionKey(key: PGPSecretKeyRing, - protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply { - decryptionKeys[key] = protector - } + fun addDecryptionKey( + key: PGPSecretKeyRing, + protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys() + ) = apply { decryptionKeys[key] = protector } /** * Add the keys in the provided key collection for message decryption. @@ -173,18 +177,21 @@ class ConsumerOptions { * @return options */ @JvmOverloads - fun addDecryptionKeys(keys: PGPSecretKeyRingCollection, - protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply { + fun addDecryptionKeys( + keys: PGPSecretKeyRingCollection, + protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys() + ) = apply { for (key in keys) { addDecryptionKey(key, protector) } } /** - * Add a passphrase for message decryption. - * This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase. + * Add a passphrase for message decryption. This passphrase will be used to try to decrypt + * messages which were symmetrically encrypted for a passphrase. * - * See [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7) + * See + * [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7) * * @param passphrase passphrase * @return options @@ -195,8 +202,8 @@ class ConsumerOptions { /** * Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using - * hardware-backed secret keys. - * (See e.g. [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]). + * hardware-backed secret keys. (See e.g. + * [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]). * * @param factory decryptor factory * @return options @@ -206,9 +213,8 @@ class ConsumerOptions { } /** - * Return the custom [PublicKeyDataDecryptorFactory] that were - * set by the user. - * These factories can be used to decrypt session keys using a custom logic. + * Return the custom [PublicKeyDataDecryptorFactory] that were set by the user. These factories + * can be used to decrypt session keys using a custom logic. * * @return custom decryptor factories */ @@ -236,8 +242,8 @@ class ConsumerOptions { fun getCertificateSource() = certificates /** - * Return the callback that gets called when a certificate for signature verification is missing. - * This method might return `null` if the users hasn't set a callback. + * Return the callback that gets called when a certificate for signature verification is + * missing. This method might return `null` if the users hasn't set a callback. * * @return missing public key callback */ @@ -255,45 +261,46 @@ class ConsumerOptions { /** * By default, PGPainless will require encrypted messages to make use of SEIP data packets. - * Those are Symmetrically Encrypted Integrity Protected Data packets. - * Symmetrically Encrypted Data Packets without integrity protection are rejected by default. - * Furthermore, PGPainless will throw an exception if verification of the MDC error detection - * code of the SEIP packet fails. + * Those are Symmetrically Encrypted Integrity Protected Data packets. Symmetrically Encrypted + * Data Packets without integrity protection are rejected by default. Furthermore, PGPainless + * will throw an exception if verification of the MDC error detection code of the SEIP packet + * fails. * * Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an * attack or data corruption. * * This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data - * without integrity protection. - * If the flag
ignoreMDCErrors
is set to true, PGPainless will + * without integrity protection. If the flag
ignoreMDCErrors
is set to true, + * PGPainless will + * * not throw exceptions for SEIP packets with tampered ciphertext + * * not throw exceptions for SEIP packets with tampered MDC + * * not throw exceptions for MDCs with bad CTB + * * not throw exceptions for MDCs with bad length * - * * not throw exceptions for SEIP packets with tampered ciphertext - * * not throw exceptions for SEIP packets with tampered MDC - * * not throw exceptions for MDCs with bad CTB - * * not throw exceptions for MDCs with bad length + * It will however still throw an exception if it encounters a SEIP packet with missing or + * truncated MDC * - * - * It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC - * - * See [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13) + * See + * [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13) * * @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise. * @return options */ @Deprecated("Ignoring non-integrity-protected packets is discouraged.") - fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply { this.ignoreMDCErrors = ignoreMDCErrors } + fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply { + this.ignoreMDCErrors = ignoreMDCErrors + } fun isIgnoreMDCErrors() = ignoreMDCErrors /** - * Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. - * This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data. + * Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. This + * workaround might come in handy if PGPainless accidentally mistakes the data for binary + * OpenPGP data. * * @return options */ - fun forceNonOpenPgpData(): ConsumerOptions = apply { - this.forceNonOpenPgpData = true - } + fun forceNonOpenPgpData(): ConsumerOptions = apply { this.forceNonOpenPgpData = true } /** * Return true, if the ciphertext should be handled as binary non-OpenPGP data. @@ -303,15 +310,15 @@ class ConsumerOptions { fun isForceNonOpenPgpData() = forceNonOpenPgpData /** - * Specify the [MissingKeyPassphraseStrategy]. - * This strategy defines, how missing passphrases for unlocking secret keys are handled. - * In interactive mode ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing + * Specify the [MissingKeyPassphraseStrategy]. This strategy defines, how missing passphrases + * for unlocking secret keys are handled. In interactive mode + * ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing * passphrases for secret keys via the [SecretKeyRingProtector] * [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider] callback. * - * In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will instead - * throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of all keys for which - * there are missing passphrases. + * In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will + * instead throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of + * all keys for which there are missing passphrases. * * @param strategy strategy * @return options @@ -331,8 +338,8 @@ class ConsumerOptions { } /** - * Set a custom multi-pass strategy for processing cleartext-signed messages. - * Uses [InMemoryMultiPassStrategy] by default. + * Set a custom multi-pass strategy for processing cleartext-signed messages. Uses + * [InMemoryMultiPassStrategy] by default. * * @param multiPassStrategy multi-pass caching strategy * @return builder @@ -343,8 +350,7 @@ class ConsumerOptions { } /** - * Return the currently configured [MultiPassStrategy]. - * Defaults to [InMemoryMultiPassStrategy]. + * Return the currently configured [MultiPassStrategy]. Defaults to [InMemoryMultiPassStrategy]. * * @return multi-pass strategy */ @@ -353,8 +359,8 @@ class ConsumerOptions { } /** - * Source for OpenPGP certificates. - * When verifying signatures on a message, this object holds available signer certificates. + * Source for OpenPGP certificates. When verifying signatures on a message, this object holds + * available signer certificates. */ class CertificateSource { private val explicitCertificates: MutableSet = mutableSetOf() @@ -370,6 +376,7 @@ class ConsumerOptions { /** * Return the set of explicitly set verification certificates. + * * @return explicitly set verification certs */ fun getExplicitCertificates(): Set { @@ -377,9 +384,9 @@ class ConsumerOptions { } /** - * Return a certificate which contains a subkey with the given keyId. - * This method first checks all explicitly set verification certs and if no cert is found it consults - * the certificate stores. + * Return a certificate which contains a subkey with the given keyId. This method first + * checks all explicitly set verification certs and if no cert is found it consults the + * certificate stores. * * @param keyId key id * @return certificate @@ -389,13 +396,10 @@ class ConsumerOptions { } fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? = - explicitCertificates.firstOrNull { - it.getPublicKeyFor(signature) != null - } + explicitCertificates.firstOrNull { it.getPublicKeyFor(signature) != null } } companion object { - @JvmStatic - fun get() = ConsumerOptions() + @JvmStatic fun get() = ConsumerOptions() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt index b7f57da3..4a0dbeba 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.kt @@ -8,19 +8,19 @@ import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.pgpainless.key.SubkeyIdentifier /** - * Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message decryption - * using public keys. - * This class can for example be used to implement message encryption using hardware tokens like smartcards or - * TPMs. + * Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message + * decryption using public keys. This class can for example be used to implement message encryption + * using hardware tokens like smartcards or TPMs. + * * @see [ConsumerOptions.addCustomDecryptorFactory] */ interface CustomPublicKeyDataDecryptorFactory : PublicKeyDataDecryptorFactory { /** - * Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] - * is intended. + * Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] is + * intended. * * @return subkey identifier */ val subkeyIdentifier: SubkeyIdentifier -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilder.kt index 4934f5de..d1d4f8b2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilder.kt @@ -7,20 +7,20 @@ package org.pgpainless.decryption_verification import java.io.InputStream /** - * Builder class that takes an [InputStream] of ciphertext (or plaintext signed data) - * and combines it with a configured [ConsumerOptions] object to form a [DecryptionStream] which - * can be used to decrypt an OpenPGP message or verify signatures. + * Builder class that takes an [InputStream] of ciphertext (or plaintext signed data) and combines + * it with a configured [ConsumerOptions] object to form a [DecryptionStream] which can be used to + * decrypt an OpenPGP message or verify signatures. */ -class DecryptionBuilder: DecryptionBuilderInterface { +class DecryptionBuilder : DecryptionBuilderInterface { override fun onInputStream(inputStream: InputStream): DecryptionBuilderInterface.DecryptWith { return DecryptWithImpl(inputStream) } - class DecryptWithImpl(val inputStream: InputStream): DecryptionBuilderInterface.DecryptWith { + class DecryptWithImpl(val inputStream: InputStream) : DecryptionBuilderInterface.DecryptWith { override fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream { return OpenPgpMessageInputStream.create(inputStream, consumerOptions) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilderInterface.kt index c15f301e..18fd4179 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilderInterface.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionBuilderInterface.kt @@ -4,14 +4,15 @@ package org.pgpainless.decryption_verification -import org.bouncycastle.openpgp.PGPException import java.io.IOException import java.io.InputStream +import org.bouncycastle.openpgp.PGPException interface DecryptionBuilderInterface { /** - * Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed data. + * Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed + * data. * * @param inputStream encrypted and/or signed data. * @return api handle @@ -31,4 +32,4 @@ interface DecryptionBuilderInterface { @Throws(PGPException::class, IOException::class) fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt index b9499784..86bd490a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/DecryptionStream.kt @@ -9,13 +9,13 @@ import java.io.InputStream /** * Abstract definition of an [InputStream] which can be used to decrypt / verify OpenPGP messages. */ -abstract class DecryptionStream: InputStream() { +abstract class DecryptionStream : InputStream() { /** - * Return [MessageMetadata] about the decrypted / verified message. - * The [DecryptionStream] MUST be closed via [close] before the metadata object can be accessed. + * Return [MessageMetadata] about the decrypted / verified message. The [DecryptionStream] MUST + * be closed via [close] before the metadata object can be accessed. * * @return message metadata */ abstract val metadata: MessageMetadata -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt index 7d0d243a..1974e290 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/HardwareSecurity.kt @@ -4,6 +4,7 @@ package org.pgpainless.decryption_verification +import kotlin.jvm.Throws import org.bouncycastle.bcpg.AEADEncDataPacket import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket import org.bouncycastle.openpgp.PGPException @@ -12,25 +13,22 @@ import org.bouncycastle.openpgp.operator.PGPDataDecryptor import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory import org.pgpainless.key.SubkeyIdentifier -import kotlin.jvm.Throws -/** - * Enable integration of hardware-backed OpenPGP keys. - */ +/** Enable integration of hardware-backed OpenPGP keys. */ class HardwareSecurity { interface DecryptionCallback { /** - * Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with - * hardware security modules such as smartcards or TPMs. + * Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for + * dealing with hardware security modules such as smartcards or TPMs. * - * If decryption fails for some reason, a subclass of the [HardwareSecurityException] is thrown. + * If decryption fails for some reason, a subclass of the [HardwareSecurityException] is + * thrown. * * @param keyId id of the key * @param keyAlgorithm algorithm * @param sessionKeyData encrypted session key - * * @return decrypted session key * @throws HardwareSecurityException exception */ @@ -39,38 +37,51 @@ class HardwareSecurity { } /** - * Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted session keys - * to a [DecryptionCallback]. - * Users can provide such a callback to delegate decryption of messages to hardware security SDKs. + * Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted + * session keys to a [DecryptionCallback]. Users can provide such a callback to delegate + * decryption of messages to hardware security SDKs. */ class HardwareDataDecryptorFactory( - override val subkeyIdentifier: SubkeyIdentifier, - private val callback: DecryptionCallback, + override val subkeyIdentifier: SubkeyIdentifier, + private val callback: DecryptionCallback, ) : CustomPublicKeyDataDecryptorFactory { // luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument. private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null) - override fun createDataDecryptor(withIntegrityPacket: Boolean, encAlgorithm: Int, key: ByteArray?): PGPDataDecryptor { + override fun createDataDecryptor( + withIntegrityPacket: Boolean, + encAlgorithm: Int, + key: ByteArray? + ): PGPDataDecryptor { return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key) } - override fun createDataDecryptor(aeadEncDataPacket: AEADEncDataPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor { + override fun createDataDecryptor( + aeadEncDataPacket: AEADEncDataPacket?, + sessionKey: PGPSessionKey? + ): PGPDataDecryptor { return factory.createDataDecryptor(aeadEncDataPacket, sessionKey) } - override fun createDataDecryptor(seipd: SymmetricEncIntegrityPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor { + override fun createDataDecryptor( + seipd: SymmetricEncIntegrityPacket?, + sessionKey: PGPSessionKey? + ): PGPDataDecryptor { return factory.createDataDecryptor(seipd, sessionKey) } - override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array): ByteArray { + override fun recoverSessionData( + keyAlgorithm: Int, + secKeyData: Array + ): ByteArray { return try { callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0]) - } catch (e : HardwareSecurityException) { + } catch (e: HardwareSecurityException) { throw PGPException("Hardware-backed decryption failed.", e) } } } class HardwareSecurityException : Exception() -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.kt index a1e095f8..4618882c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.kt @@ -4,23 +4,25 @@ package org.pgpainless.decryption_verification +import java.io.IOException +import java.io.InputStream import org.bouncycastle.openpgp.PGPEncryptedData import org.bouncycastle.openpgp.PGPException import org.pgpainless.exception.ModificationDetectionException import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.IOException -import java.io.InputStream class IntegrityProtectedInputStream( - private val inputStream: InputStream, - private val encryptedData: PGPEncryptedData, - private val options: ConsumerOptions + private val inputStream: InputStream, + private val encryptedData: PGPEncryptedData, + private val options: ConsumerOptions ) : InputStream() { private var closed: Boolean = false override fun read() = inputStream.read() + override fun read(b: ByteArray, off: Int, len: Int) = inputStream.read(b, off, len) + override fun close() { if (closed) return @@ -29,7 +31,7 @@ class IntegrityProtectedInputStream( try { if (!encryptedData.verify()) throw ModificationDetectionException() LOGGER.debug("Integrity Protection check passed.") - } catch (e : PGPException) { + } catch (e: PGPException) { throw IOException("Data appears to not be integrity protected.", e) } } @@ -39,4 +41,4 @@ class IntegrityProtectedInputStream( @JvmStatic val LOGGER: Logger = LoggerFactory.getLogger(IntegrityProtectedInputStream::class.java) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt index 64b2a5f3..acfcba51 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt @@ -4,14 +4,15 @@ package org.pgpainless.decryption_verification +import java.io.IOException +import java.io.InputStream import org.bouncycastle.openpgp.* import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.util.ArmorUtils -import java.io.IOException -import java.io.InputStream /** - * Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase protected. + * Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase + * protected. */ class MessageInspector { @@ -23,9 +24,10 @@ class MessageInspector { * @param isSignedOnly true, if the message is not encrypted, but signed using OnePassSignatures */ data class EncryptionInfo( - val keyIds: List, - val isPassphraseEncrypted: Boolean, - val isSignedOnly: Boolean) { + val keyIds: List, + val isPassphraseEncrypted: Boolean, + val isSignedOnly: Boolean + ) { val isEncrypted: Boolean get() = isPassphraseEncrypted || keyIds.isNotEmpty() @@ -34,25 +36,26 @@ class MessageInspector { companion object { /** - * Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it. + * Parses parts of the provided OpenPGP message in order to determine which keys were used + * to encrypt it. * * @param message OpenPGP message * @return encryption info - * * @throws PGPException in case the message is broken * @throws IOException in case of an IO error */ @JvmStatic @Throws(PGPException::class, IOException::class) - fun determineEncryptionInfoForMessage(message: String): EncryptionInfo = determineEncryptionInfoForMessage(message.byteInputStream()) + fun determineEncryptionInfoForMessage(message: String): EncryptionInfo = + determineEncryptionInfoForMessage(message.byteInputStream()) /** - * Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it. - * Note: This method does not rewind the passed in Stream, so you might need to take care of that yourselves. + * Parses parts of the provided OpenPGP message in order to determine which keys were used + * to encrypt it. Note: This method does not rewind the passed in Stream, so you might need + * to take care of that yourselves. * * @param inputStream openpgp message * @return encryption information - * * @throws IOException in case of an IO error * @throws PGPException if the message is broken */ @@ -70,13 +73,12 @@ class MessageInspector { var n: Any? while (objectFactory.nextObject().also { n = it } != null) { when (val next = n!!) { - is PGPOnePassSignatureList -> { if (!next.isEmpty) { - return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = true) + return EncryptionInfo( + listOf(), isPassphraseEncrypted = false, isSignedOnly = true) } } - is PGPEncryptedDataList -> { var isPassphraseEncrypted = false val keyIds = mutableListOf() @@ -90,13 +92,12 @@ class MessageInspector { // Data is encrypted, we cannot go deeper return EncryptionInfo(keyIds, isPassphraseEncrypted, false) } - is PGPCompressedData -> { - objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - PGPUtil.getDecoderStream(next.dataStream)) + objectFactory = + ImplementationFactory.getInstance() + .getPGPObjectFactory(PGPUtil.getDecoderStream(next.dataStream)) continue } - is PGPLiteralData -> { break } @@ -105,4 +106,4 @@ class MessageInspector { return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = false) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt index 3eb49e5d..ecdeadf0 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt @@ -4,6 +4,8 @@ package org.pgpainless.decryption_verification +import java.util.* +import javax.annotation.Nonnull import org.bouncycastle.extensions.matches import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPLiteralData @@ -15,116 +17,118 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.util.SessionKey -import java.util.* -import javax.annotation.Nonnull -/** - * View for extracting metadata about a [Message]. - */ -class MessageMetadata( - val message: Message -) { +/** View for extracting metadata about a [Message]. */ +class MessageMetadata(val message: Message) { // ################################################################################################################ - // ### Encryption ### + // ### Encryption + // ### // ################################################################################################################ /** - * The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is unencrypted. + * The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is + * unencrypted. */ val encryptionAlgorithm: SymmetricKeyAlgorithm? - get() = encryptionAlgorithms.let { - if (it.hasNext()) it.next() else null - } + get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null } /** - * [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. - * The first item returned by the iterator is the algorithm of the outermost encrypted data packet, the next item - * that of the next nested encrypted data packet and so on. - * The iterator might also be empty, in case of an unencrypted message. + * [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. The first item + * returned by the iterator is the algorithm of the outermost encrypted data packet, the next + * item that of the next nested encrypted data packet and so on. The iterator might also be + * empty, in case of an unencrypted message. */ val encryptionAlgorithms: Iterator get() = encryptionLayers.asSequence().map { it.algorithm }.iterator() val isEncrypted: Boolean - get() = if (encryptionAlgorithm == null) false else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL + get() = + if (encryptionAlgorithm == null) false + else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL fun isEncryptedFor(keys: PGPKeyRing): Boolean { return encryptionLayers.asSequence().any { - it.recipients.any { keyId -> - keys.getPublicKey(keyId) != null - } + it.recipients.any { keyId -> keys.getPublicKey(keyId) != null } } } /** - * [SessionKey] of the outermost encrypted data packet. - * If the message was unencrypted, this method returns `null`. + * [SessionKey] of the outermost encrypted data packet. If the message was unencrypted, this + * method returns `null`. */ val sessionKey: SessionKey? get() = sessionKeys.asSequence().firstOrNull() /** - * [Iterator] of each [SessionKey] for all encrypted data packets in the message. - * The first item returned by the iterator is the session key of the outermost encrypted data packet, - * the next item that of the next nested encrypted data packet and so on. - * The iterator might also be empty, in case of an unencrypted message. + * [Iterator] of each [SessionKey] for all encrypted data packets in the message. The first item + * returned by the iterator is the session key of the outermost encrypted data packet, the next + * item that of the next nested encrypted data packet and so on. The iterator might also be + * empty, in case of an unencrypted message. */ val sessionKeys: Iterator get() = encryptionLayers.asSequence().mapNotNull { it.sessionKey }.iterator() /** * [SubkeyIdentifier] of the decryption key that was used to decrypt the outermost encryption - * layer. - * If the message was unencrypted or was decrypted using a passphrase, this field might be `null`. + * layer. If the message was unencrypted or was decrypted using a passphrase, this field might + * be `null`. */ val decryptionKey: SubkeyIdentifier? - get() = encryptionLayers.asSequence() - .mapNotNull { it.decryptionKey } - .firstOrNull() + get() = encryptionLayers.asSequence().mapNotNull { it.decryptionKey }.firstOrNull() - /** - * List containing all recipient keyIDs. - */ + /** List containing all recipient keyIDs. */ val recipientKeyIds: List - get() = encryptionLayers.asSequence() + get() = + encryptionLayers + .asSequence() .map { it.recipients.toMutableList() } - .reduce { all, keyIds -> all.addAll(keyIds); all } + .reduce { all, keyIds -> + all.addAll(keyIds) + all + } .toList() val encryptionLayers: Iterator - get() = object : LayerIterator(message) { - override fun matches(layer: Packet) = layer is EncryptedData - override fun getProperty(last: Layer) = last as EncryptedData - } + get() = + object : LayerIterator(message) { + override fun matches(layer: Packet) = layer is EncryptedData + + override fun getProperty(last: Layer) = last as EncryptedData + } // ################################################################################################################ - // ### Compression ### + // ### Compression + // ### // ################################################################################################################ /** - * [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message - * does not contain any compressed data packets. + * [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message does + * not contain any compressed data packets. */ - val compressionAlgorithm: CompressionAlgorithm? = compressionAlgorithms.asSequence().firstOrNull() + val compressionAlgorithm: CompressionAlgorithm? = + compressionAlgorithms.asSequence().firstOrNull() /** - * [Iterator] of each [CompressionAlgorithm] encountered in the message. - * The first item returned by the iterator is the algorithm of the outermost compressed data packet, the next - * item that of the next nested compressed data packet and so on. - * The iterator might also be empty, in case of a message without any compressed data packets. + * [Iterator] of each [CompressionAlgorithm] encountered in the message. The first item returned + * by the iterator is the algorithm of the outermost compressed data packet, the next item that + * of the next nested compressed data packet and so on. The iterator might also be empty, in + * case of a message without any compressed data packets. */ val compressionAlgorithms: Iterator get() = compressionLayers.asSequence().map { it.algorithm }.iterator() val compressionLayers: Iterator - get() = object : LayerIterator(message) { - override fun matches(layer: Packet) = layer is CompressedData - override fun getProperty(last: Layer) = last as CompressedData - } + get() = + object : LayerIterator(message) { + override fun matches(layer: Packet) = layer is CompressedData + + override fun getProperty(last: Layer) = last as CompressedData + } // ################################################################################################################ - // ### Signatures ### + // ### Signatures + // ### // ################################################################################################################ val isUsingCleartextSignatureFramework: Boolean @@ -133,81 +137,87 @@ class MessageMetadata( val verifiedSignatures: List get() = verifiedInlineSignatures.plus(verifiedDetachedSignatures) - /** - * List of all rejected signatures. - */ + /** List of all rejected signatures. */ val rejectedSignatures: List - get() = mutableListOf() + get() = + mutableListOf() .plus(rejectedInlineSignatures) .plus(rejectedDetachedSignatures) .toList() /** - * List of all verified inline-signatures. - * This list contains all acceptable, correct signatures that were part of the message itself. + * List of all verified inline-signatures. This list contains all acceptable, correct signatures + * that were part of the message itself. */ - val verifiedInlineSignatures: List = verifiedInlineSignaturesByLayer + val verifiedInlineSignatures: List = + verifiedInlineSignaturesByLayer .asSequence() .map { it.toMutableList() } - .reduce { acc, signatureVerifications -> acc.addAll(signatureVerifications); acc } + .reduce { acc, signatureVerifications -> + acc.addAll(signatureVerifications) + acc + } .toList() /** * [Iterator] of each [List] of verified inline-signatures of the message, separated by layer. - * Since signatures might occur in different layers within a message, this method can be used to gain more detailed - * insights into what signatures were encountered at what layers of the message structure. - * Each item of the [Iterator] represents a layer of the message and contains only signatures from - * this layer. - * An empty list means no (or no acceptable) signatures were encountered in that layer. + * Since signatures might occur in different layers within a message, this method can be used to + * gain more detailed insights into what signatures were encountered at what layers of the + * message structure. Each item of the [Iterator] represents a layer of the message and contains + * only signatures from this layer. An empty list means no (or no acceptable) signatures were + * encountered in that layer. */ val verifiedInlineSignaturesByLayer: Iterator> - get() = object : LayerIterator>(message) { - override fun matches(layer: Packet) = layer is Layer + get() = + object : LayerIterator>(message) { + override fun matches(layer: Packet) = layer is Layer - override fun getProperty(last: Layer): List { - return listOf() + override fun getProperty(last: Layer): List { + return listOf() .plus(last.verifiedOnePassSignatures) .plus(last.verifiedPrependedSignatures) + } } - } - - /** - * List of all rejected inline-signatures of the message. - */ - val rejectedInlineSignatures: List = rejectedInlineSignaturesByLayer + /** List of all rejected inline-signatures of the message. */ + val rejectedInlineSignatures: List = + rejectedInlineSignaturesByLayer .asSequence() .map { it.toMutableList() } - .reduce { acc, failures -> acc.addAll(failures); acc} + .reduce { acc, failures -> + acc.addAll(failures) + acc + } .toList() /** - * Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected inline-signatures - * of the message, but organized by layer. + * Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected + * inline-signatures of the message, but organized by layer. */ val rejectedInlineSignaturesByLayer: Iterator> - get() = object : LayerIterator>(message) { - override fun matches(layer: Packet) = layer is Layer + get() = + object : LayerIterator>(message) { + override fun matches(layer: Packet) = layer is Layer - override fun getProperty(last: Layer): List = + override fun getProperty(last: Layer): List = mutableListOf() - .plus(last.rejectedOnePassSignatures) - .plus(last.rejectedPrependedSignatures) - } + .plus(last.rejectedOnePassSignatures) + .plus(last.rejectedPrependedSignatures) + } /** - * List of all verified detached signatures. - * This list contains all acceptable, correct detached signatures. + * List of all verified detached signatures. This list contains all acceptable, correct detached + * signatures. */ val verifiedDetachedSignatures: List = message.verifiedDetachedSignatures - /** - * List of all rejected detached signatures. - */ - val rejectedDetachedSignatures: List = message.rejectedDetachedSignatures + /** List of all rejected detached signatures. */ + val rejectedDetachedSignatures: List = + message.rejectedDetachedSignatures /** - * True, if the message contains any (verified or rejected) signature, false if no signatures are present. + * True, if the message contains any (verified or rejected) signature, false if no signatures + * are present. */ val hasSignature: Boolean get() = isVerifiedSigned() || hasRejectedSignatures() @@ -217,110 +227,131 @@ class MessageMetadata( fun hasRejectedSignatures(): Boolean = rejectedSignatures.isNotEmpty() /** - * Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId. + * Return true, if the message was signed by a certificate for which we can authenticate a + * binding to the given userId. * * @param userId userId - * @param email if true, treat the user-id as an email address and match all userIDs containing this address + * @param email if true, treat the user-id as an email address and match all userIDs containing + * this address * @param certificateAuthority certificate authority - * @param targetAmount targeted trust amount that needs to be reached by the binding to qualify as authenticated. - * defaults to 120. + * @param targetAmount targeted trust amount that needs to be reached by the binding to qualify + * as authenticated. defaults to 120. * @return true, if we can authenticate a binding for a signing key with sufficient evidence */ @JvmOverloads - fun isAuthenticatablySignedBy(userId: String, email: Boolean, certificateAuthority: CertificateAuthority, targetAmount: Int = 120): Boolean { - return verifiedSignatures.any { certificateAuthority - .authenticateBinding(it.signingKey.fingerprint, userId, email, it.signature.creationTime, targetAmount) + fun isAuthenticatablySignedBy( + userId: String, + email: Boolean, + certificateAuthority: CertificateAuthority, + targetAmount: Int = 120 + ): Boolean { + return verifiedSignatures.any { + certificateAuthority + .authenticateBinding( + it.signingKey.fingerprint, + userId, + email, + it.signature.creationTime, + targetAmount) .authenticated } } /** - * Return rue, if the message was verifiable signed by a certificate that either has the given fingerprint - * as primary key, or as the signing subkey. + * Return rue, if the message was verifiable signed by a certificate that either has the given + * fingerprint as primary key, or as the signing subkey. * * @param fingerprint fingerprint * @return true if message was signed by a cert identified by the given fingerprint */ fun isVerifiedSignedBy(fingerprint: OpenPgpFingerprint) = - verifiedSignatures.any { it.signingKey.matches(fingerprint) } + verifiedSignatures.any { it.signingKey.matches(fingerprint) } fun isVerifiedSignedBy(keys: PGPKeyRing) = - verifiedSignatures.any { keys.matches(it.signingKey) } + verifiedSignatures.any { keys.matches(it.signingKey) } fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) = - verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) } + verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) } fun isVerifiedDetachedSignedBy(keys: PGPKeyRing) = - verifiedDetachedSignatures.any { keys.matches(it.signingKey) } + verifiedDetachedSignatures.any { keys.matches(it.signingKey) } fun isVerifiedInlineSignedBy(fingerprint: OpenPgpFingerprint) = - verifiedInlineSignatures.any { it.signingKey.matches(fingerprint) } + verifiedInlineSignatures.any { it.signingKey.matches(fingerprint) } fun isVerifiedInlineSignedBy(keys: PGPKeyRing) = - verifiedInlineSignatures.any { keys.matches(it.signingKey) } + verifiedInlineSignatures.any { keys.matches(it.signingKey) } // ################################################################################################################ - // ### Literal Data ### + // ### Literal Data + // ### // ################################################################################################################ /** - * Value of the literal data packet's filename field. - * This value can be used to store a decrypted file under its original filename, - * but since this field is not necessarily part of the signed data of a message, usage of this field is - * discouraged. + * Value of the literal data packet's filename field. This value can be used to store a + * decrypted file under its original filename, but since this field is not necessarily part of + * the signed data of a message, usage of this field is discouraged. * - * @see RFC4880 §5.9. Literal Data Packet + * @see RFC4880 §5.9. Literal Data + * Packet */ val filename: String? = findLiteralData()?.fileName /** - * True, if the sender signals an increased degree of confidentiality by setting the filename of the literal - * data packet to a special value that indicates that the data is intended for your eyes only. + * True, if the sender signals an increased degree of confidentiality by setting the filename of + * the literal data packet to a special value that indicates that the data is intended for your + * eyes only. */ @Deprecated("Reliance on this signaling mechanism is discouraged.") val isForYourEyesOnly: Boolean = PGPLiteralData.CONSOLE == filename /** - * Value of the literal data packets modification date field. - * This value can be used to restore the modification date of a decrypted file, - * but since this field is not necessarily part of the signed data, its use is discouraged. + * Value of the literal data packets modification date field. This value can be used to restore + * the modification date of a decrypted file, but since this field is not necessarily part of + * the signed data, its use is discouraged. * - * @see RFC4880 §5.9. Literal Data Packet + * @see RFC4880 §5.9. Literal Data + * Packet */ val modificationDate: Date? = findLiteralData()?.modificationDate /** - * Value of the format field of the literal data packet. - * This value indicates what format (text, binary data, ...) the data has. - * Since this field is not necessarily part of the signed data of a message, its usage is discouraged. + * Value of the format field of the literal data packet. This value indicates what format (text, + * binary data, ...) the data has. Since this field is not necessarily part of the signed data + * of a message, its usage is discouraged. * - * @see RFC4880 §5.9. Literal Data Packet + * @see RFC4880 §5.9. Literal Data + * Packet */ val literalDataEncoding: StreamEncoding? = findLiteralData()?.format /** - * Find the [LiteralData] layer of an OpenPGP message. - * This method might return null, for example for a cleartext signed message without OpenPGP packets. + * Find the [LiteralData] layer of an OpenPGP message. This method might return null, for + * example for a cleartext signed message without OpenPGP packets. * * @return literal data */ private fun findLiteralData(): LiteralData? { - // If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed message, + // If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed + // message, // we might not have a Literal Data packet. var nested = message.child ?: return null while (nested.hasNestedChild()) { val layer = nested as Layer - nested = checkNotNull(layer.child) { - // Otherwise, we MUST find a Literal Data packet, or else the message is malformed - "Malformed OpenPGP message. Cannot find Literal Data Packet" - } + nested = + checkNotNull(layer.child) { + // Otherwise, we MUST find a Literal Data packet, or else the message is + // malformed + "Malformed OpenPGP message. Cannot find Literal Data Packet" + } } return nested as LiteralData } // ################################################################################################################ - // ### Message Structure ### + // ### Message Structure + // ### // ################################################################################################################ interface Packet @@ -329,13 +360,12 @@ class MessageMetadata( fun hasNestedChild(): Boolean } - abstract class Layer( - val depth: Int - ) : Packet { + abstract class Layer(val depth: Int) : Packet { init { if (depth > MAX_LAYER_DEPTH) { - throw MalformedOpenPgpMessageException("Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.") + throw MalformedOpenPgpMessageException( + "Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.") } } @@ -347,9 +377,8 @@ class MessageMetadata( val rejectedPrependedSignatures: List = mutableListOf() /** - * Nested child element of this layer. - * Might be `null`, if this layer does not have a child element - * (e.g. if this is a [LiteralData] packet). + * Nested child element of this layer. Might be `null`, if this layer does not have a child + * element (e.g. if this is a [LiteralData] packet). */ var child: Nested? = null @@ -386,8 +415,8 @@ class MessageMetadata( * Outermost OpenPGP Message structure. * * @param cleartextSigned whether the message is using the Cleartext Signature Framework - * - * @see RFC4880 §7. Cleartext Signature Framework + * @see RFC4880 §7. Cleartext + * Signature Framework */ class Message(var cleartextSigned: Boolean = false) : Layer(0) { fun setCleartextSigned() = apply { cleartextSigned = true } @@ -397,14 +426,14 @@ class MessageMetadata( * Literal Data Packet. * * @param fileName value of the filename field. An empty String represents no filename. - * @param modificationDate value of the modification date field. The special value `Date(0)` indicates no - * modification date. + * @param modificationDate value of the modification date field. The special value `Date(0)` + * indicates no modification date. * @param format value of the format field. */ class LiteralData( - val fileName: String = "", - val modificationDate: Date = Date(0L), - val format: StreamEncoding = StreamEncoding.BINARY + val fileName: String = "", + val modificationDate: Date = Date(0L), + val format: StreamEncoding = StreamEncoding.BINARY ) : Nested { // A literal data packet MUST NOT have a child element, as its content is the plaintext @@ -417,9 +446,7 @@ class MessageMetadata( * @param algorithm [CompressionAlgorithm] used to compress the packet. * @param depth nesting depth at which this packet was encountered. */ - class CompressedData( - val algorithm: CompressionAlgorithm, - depth: Int) : Layer(depth), Nested { + class CompressedData(val algorithm: CompressionAlgorithm, depth: Int) : Layer(depth), Nested { // A compressed data packet MUST have a child element override fun hasNestedChild() = true @@ -431,38 +458,30 @@ class MessageMetadata( * @param algorithm symmetric key algorithm used to encrypt the packet. * @param depth nesting depth at which this packet was encountered. */ - class EncryptedData( - val algorithm: SymmetricKeyAlgorithm, - depth: Int - ) : Layer(depth), Nested { + class EncryptedData(val algorithm: SymmetricKeyAlgorithm, depth: Int) : Layer(depth), Nested { - /** - * [SessionKey] used to decrypt the packet. - */ + /** [SessionKey] used to decrypt the packet. */ var sessionKey: SessionKey? = null - /** - * List of all recipient key ids to which the packet was encrypted for. - */ + /** List of all recipient key ids to which the packet was encrypted for. */ val recipients: List = mutableListOf() - fun addRecipients(keyIds: List) = apply { - (recipients as MutableList).addAll(keyIds) - } + fun addRecipients(keyIds: List) = apply { (recipients as MutableList).addAll(keyIds) } /** - * Identifier of the subkey that was used to decrypt the packet (in case of a public key encrypted packet). + * Identifier of the subkey that was used to decrypt the packet (in case of a public key + * encrypted packet). */ var decryptionKey: SubkeyIdentifier? = null // An encrypted data packet MUST have a child element override fun hasNestedChild() = true - } /** - * Iterator that iterates the packet structure from outermost to innermost packet, emitting the results of - * a transformation ([getProperty]) on those packets that match ([matches]) a given criterion. + * Iterator that iterates the packet structure from outermost to innermost packet, emitting the + * results of a transformation ([getProperty]) on those packets that match ([matches]) a given + * criterion. * * @param message outermost structure object */ @@ -519,6 +538,7 @@ class MessageMetadata( } abstract fun matches(layer: Packet): Boolean + abstract fun getProperty(last: Layer): O } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.kt index f8bb448b..c5443ba8 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingKeyPassphraseStrategy.kt @@ -4,19 +4,18 @@ package org.pgpainless.decryption_verification -/** - * Strategy defining how missing secret key passphrases are handled. - */ +/** Strategy defining how missing secret key passphrases are handled. */ enum class MissingKeyPassphraseStrategy { /** - * Try to interactively obtain key passphrases one-by-one via callbacks, - * eg [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider]. + * Try to interactively obtain key passphrases one-by-one via callbacks, eg + * [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider]. */ INTERACTIVE, /** * Do not try to obtain passphrases interactively and instead throw a - * [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing passphrases. + * [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing + * passphrases. */ THROW_EXCEPTION -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingPublicKeyCallback.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingPublicKeyCallback.kt index 723530b7..eb81847f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingPublicKeyCallback.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MissingPublicKeyCallback.kt @@ -9,20 +9,19 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing fun interface MissingPublicKeyCallback { /** - * This method gets called if we encounter a signature made by a key which was not provided for signature verification. - * If you cannot provide the requested key, it is safe to return null here. - * PGPainless will then continue verification with the next signature. + * This method gets called if we encounter a signature made by a key which was not provided for + * signature verification. If you cannot provide the requested key, it is safe to return null + * here. PGPainless will then continue verification with the next signature. * - * Note: The key-id might belong to a subkey, so be aware that when looking up the [PGPPublicKeyRing], - * you may not only search for the key-id on the key rings primary key! + * Note: The key-id might belong to a subkey, so be aware that when looking up the + * [PGPPublicKeyRing], you may not only search for the key-id on the key rings primary key! * - * It would be super cool to provide the OpenPgp fingerprint here, but unfortunately one-pass-signatures - * only contain the key id. + * It would be super cool to provide the OpenPgp fingerprint here, but unfortunately + * one-pass-signatures only contain the key id. * * @param keyId ID of the missing signing (sub)key * @return keyring containing the key or null - * * @see RFC */ fun onMissingPublicKeyEncountered(keyId: Long): PGPPublicKeyRing? -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index b823fdaf..5e8b68f2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -4,6 +4,9 @@ package org.pgpainless.decryption_verification +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream import openpgp.openPgpKeyId import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.UnsupportedPacketVersionException @@ -34,16 +37,14 @@ import org.pgpainless.signature.consumer.SignatureValidator import org.pgpainless.util.ArmoredInputStreamFactory import org.pgpainless.util.SessionKey import org.slf4j.LoggerFactory -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream class OpenPgpMessageInputStream( - type: Type, - inputStream: InputStream, - private val options: ConsumerOptions, - private val layerMetadata: Layer, - private val policy: Policy) : DecryptionStream() { + type: Type, + inputStream: InputStream, + private val options: ConsumerOptions, + private val layerMetadata: Layer, + private val policy: Policy +) : DecryptionStream() { private val signatures: Signatures = Signatures(options) private var packetInputStream: TeeBCPGInputStream? = null @@ -58,19 +59,20 @@ class OpenPgpMessageInputStream( signatures.addDetachedSignatures(options.getDetachedSignatures()) } - when(type) { + when (type) { Type.standard -> { // tee out packet bytes for signature verification - packetInputStream = TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures) + packetInputStream = + TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures) // *omnomnom* consumePackets() } - Type.cleartext_signed -> { val multiPassStrategy = options.getMultiPassStrategy() - val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage( + val detachedSignatures = + ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage( inputStream, multiPassStrategy.messageOutputStream) for (signature in detachedSignatures) { @@ -78,9 +80,9 @@ class OpenPgpMessageInputStream( } options.isForceNonOpenPgpData() - nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures) + nestedInputStream = + TeeInputStream(multiPassStrategy.messageInputStream, this.signatures) } - Type.non_openpgp -> { packetInputStream = null nestedInputStream = TeeInputStream(inputStream, this.signatures) @@ -89,11 +91,17 @@ class OpenPgpMessageInputStream( } enum class Type { - standard, cleartext_signed, non_openpgp + standard, + cleartext_signed, + non_openpgp } - constructor(inputStream: InputStream, options: ConsumerOptions, metadata: Layer, policy: Policy): - this(Type.standard, inputStream, options, metadata, policy) + constructor( + inputStream: InputStream, + options: ConsumerOptions, + metadata: Layer, + policy: Policy + ) : this(Type.standard, inputStream, options, metadata, policy) private fun consumePackets() { val pIn = packetInputStream ?: return @@ -102,50 +110,53 @@ class OpenPgpMessageInputStream( // Comsume packets, potentially stepping into nested layers layer@ while (run { - packet = pIn.nextPacketTag() - packet - } != null) { + packet = pIn.nextPacketTag() + packet + } != null) { signatures.nextPacket(packet!!) // Consume packets in a layer - when(packet) { - + when (packet) { OpenPgpPacket.LIT -> { processLiteralData() break@layer // nest down } - OpenPgpPacket.COMP -> { processCompressedData() break@layer // nest down } - OpenPgpPacket.OPS -> { processOnePassSignature() // OPS is on the same layer, no nest down } - OpenPgpPacket.SIG -> { processSignature() // SIG is on the same layer, no nest down } - - OpenPgpPacket.PKESK, OpenPgpPacket.SKESK, OpenPgpPacket.SED, OpenPgpPacket.SEIPD -> { + OpenPgpPacket.PKESK, + OpenPgpPacket.SKESK, + OpenPgpPacket.SED, + OpenPgpPacket.SEIPD -> { if (processEncryptedData()) { break@layer } throw MissingDecryptionMethodException("No working decryption method found.") } - OpenPgpPacket.MARKER -> { LOGGER.debug("Skipping Marker Packet") pIn.readMarker() } - - OpenPgpPacket.SK, OpenPgpPacket.PK, OpenPgpPacket.SSK, OpenPgpPacket.PSK, OpenPgpPacket.TRUST, OpenPgpPacket.UID, OpenPgpPacket.UATTR -> + OpenPgpPacket.SK, + OpenPgpPacket.PK, + OpenPgpPacket.SSK, + OpenPgpPacket.PSK, + OpenPgpPacket.TRUST, + OpenPgpPacket.UID, + OpenPgpPacket.UATTR -> throw MalformedOpenPgpMessageException("Illegal Packet in Stream: $packet") - - OpenPgpPacket.EXP_1, OpenPgpPacket.EXP_2, OpenPgpPacket.EXP_3, OpenPgpPacket.EXP_4 -> + OpenPgpPacket.EXP_1, + OpenPgpPacket.EXP_2, + OpenPgpPacket.EXP_3, + OpenPgpPacket.EXP_4 -> throw MalformedOpenPgpMessageException("Unsupported Packet in Stream: $packet") - else -> throw MalformedOpenPgpMessageException("Unexpected Packet in Stream: $packet") } @@ -158,8 +169,10 @@ class OpenPgpMessageInputStream( val literalData = packetInputStream!!.readLiteralData() // Extract Metadata - layerMetadata.child = LiteralData( - literalData.fileName, literalData.modificationTime, + layerMetadata.child = + LiteralData( + literalData.fileName, + literalData.modificationTime, StreamEncoding.requireFromCode(literalData.format)) nestedInputStream = literalData.inputStream @@ -171,18 +184,22 @@ class OpenPgpMessageInputStream( val compressedData = packetInputStream!!.readCompressedData() // Extract Metadata - val compressionLayer = CompressedData( + val compressionLayer = + CompressedData( CompressionAlgorithm.requireFromId(compressedData.algorithm), layerMetadata.depth + 1) - LOGGER.debug("Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.") - nestedInputStream = OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy) + LOGGER.debug( + "Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.") + nestedInputStream = + OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy) } private fun processOnePassSignature() { syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE) val ops = packetInputStream!!.readOnePassSignature() - LOGGER.debug("One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") + LOGGER.debug( + "One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.addOnePassSignature(ops) } @@ -190,26 +207,33 @@ class OpenPgpMessageInputStream( // true if signature corresponds to OPS val isSigForOps = syntaxVerifier.peekStack() == StackSymbol.OPS syntaxVerifier.next(InputSymbol.SIGNATURE) - val signature = try { - packetInputStream!!.readSignature() - } catch (e : UnsupportedPacketVersionException) { - LOGGER.debug("Unsupported Signature at depth ${layerMetadata.depth} encountered.", e) - return - } + val signature = + try { + packetInputStream!!.readSignature() + } catch (e: UnsupportedPacketVersionException) { + LOGGER.debug( + "Unsupported Signature at depth ${layerMetadata.depth} encountered.", e) + return + } val keyId = signature.issuerKeyId if (isSigForOps) { - LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") - signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with + LOGGER.debug( + "Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") + signatures + .leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are + // dealt with signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy) } else { - LOGGER.debug("Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") + LOGGER.debug( + "Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") signatures.addPrependedSignature(signature) } } private fun processEncryptedData(): Boolean { - LOGGER.debug("Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.") + LOGGER.debug( + "Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.") syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA) val encDataList = packetInputStream!!.readEncryptedDataList() if (!encDataList.isIntegrityProtected) { @@ -220,22 +244,25 @@ class OpenPgpMessageInputStream( } val esks = SortedESKs(encDataList) - LOGGER.debug("Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" + + LOGGER.debug( + "Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" + " ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" + " have an anonymous recipient.") // try custom decryptor factories for ((key, decryptorFactory) in options.getCustomDecryptorFactories()) { LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.") - esks.pkesks.filter { - // find matching PKESK - it.keyID == key.subkeyId - }.forEach { - // attempt decryption - if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) { - return true + esks.pkesks + .filter { + // find matching PKESK + it.keyID == key.subkeyId + } + .forEach { + // attempt decryption + if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) { + return true + } } - } } // try provided session key @@ -244,20 +271,24 @@ class OpenPgpMessageInputStream( LOGGER.debug("Attempt decryption with provided session key.") throwIfUnacceptable(sk.algorithm) - val decryptorFactory = ImplementationFactory.getInstance() - .getSessionKeyDataDecryptorFactory(sk) + val decryptorFactory = + ImplementationFactory.getInstance().getSessionKeyDataDecryptorFactory(sk) val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1) val skEncData = encDataList.extractSessionKeyEncryptedData() try { val decrypted = skEncData.getDataStream(decryptorFactory) layer.sessionKey = sk - val integrityProtected = IntegrityProtectedInputStream(decrypted, skEncData, options) - nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, layer, policy) + val integrityProtected = + IntegrityProtectedInputStream(decrypted, skEncData, options) + nestedInputStream = + OpenPgpMessageInputStream(integrityProtected, options, layer, policy) LOGGER.debug("Successfully decrypted data using provided session key") return true - } catch (e : PGPException) { + } catch (e: PGPException) { // Session key mismatch? - LOGGER.debug("Decryption using provided session key failed. Mismatched session key and message?", e) + LOGGER.debug( + "Decryption using provided session key failed. Mismatched session key and message?", + e) } } @@ -267,19 +298,21 @@ class OpenPgpMessageInputStream( LOGGER.debug("Attempt decryption with provided passphrase") val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm) if (!isAcceptable(algorithm)) { - LOGGER.debug("Skipping SKESK with unacceptable encapsulation algorithm $algorithm") + LOGGER.debug( + "Skipping SKESK with unacceptable encapsulation algorithm $algorithm") continue } - val decryptorFactory = ImplementationFactory.getInstance() - .getPBEDataDecryptorFactory(passphrase) + val decryptorFactory = + ImplementationFactory.getInstance().getPBEDataDecryptorFactory(passphrase) if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) { return true } } } - val postponedDueToMissingPassphrase = mutableListOf>() + val postponedDueToMissingPassphrase = + mutableListOf>() // try (known) secret keys esks.pkesks.forEach { pkesk -> @@ -295,7 +328,8 @@ class OpenPgpMessageInputStream( LOGGER.debug("Attempt decryption using secret key $decryptionKeyId") val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue if (!protector.hasPassphraseFor(secretKey.keyID)) { - LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") + LOGGER.debug( + "Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") postponedDueToMissingPassphrase.add(secretKey to pkesk) continue } @@ -319,7 +353,8 @@ class OpenPgpMessageInputStream( val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue if (!protector.hasPassphraseFor(secretKey.keyID)) { - LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") + LOGGER.debug( + "Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") postponedDueToMissingPassphrase.add(secretKey to pkesk) continue } @@ -331,15 +366,14 @@ class OpenPgpMessageInputStream( } } - if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) { + if (options.getMissingKeyPassphraseStrategy() == + MissingKeyPassphraseStrategy.THROW_EXCEPTION) { // Non-interactive mode: Throw an exception with all locked decryption keys - postponedDueToMissingPassphrase.map { - SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) - }.also { - if (it.isNotEmpty()) - throw MissingPassphraseException(it.toSet()) - } - } else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) { + postponedDueToMissingPassphrase + .map { SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) } + .also { if (it.isNotEmpty()) throw MissingPassphraseException(it.toSet()) } + } else if (options.getMissingKeyPassphraseStrategy() == + MissingKeyPassphraseStrategy.INTERACTIVE) { for ((secretKey, pkesk) in postponedDueToMissingPassphrase) { val keyId = secretKey.keyID val decryptionKeys = getDecryptionKey(pkesk)!! @@ -348,7 +382,8 @@ class OpenPgpMessageInputStream( continue } - LOGGER.debug("Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.") + LOGGER.debug( + "Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.") val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue val privateKey = secretKey.unlock(protector) if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { @@ -364,29 +399,37 @@ class OpenPgpMessageInputStream( return false } - private fun decryptWithPrivateKey(esks: SortedESKs, - privateKey: PGPPrivateKey, - decryptionKeyId: SubkeyIdentifier, - pkesk: PGPPublicKeyEncryptedData): Boolean { - val decryptorFactory = ImplementationFactory.getInstance() - .getPublicKeyDataDecryptorFactory(privateKey) + private fun decryptWithPrivateKey( + esks: SortedESKs, + privateKey: PGPPrivateKey, + decryptionKeyId: SubkeyIdentifier, + pkesk: PGPPublicKeyEncryptedData + ): Boolean { + val decryptorFactory = + ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey) return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk) } - private fun hasUnsupportedS2KSpecifier(secretKey: PGPSecretKey, decryptionKeyId: SubkeyIdentifier): Boolean { + private fun hasUnsupportedS2KSpecifier( + secretKey: PGPSecretKey, + decryptionKeyId: SubkeyIdentifier + ): Boolean { val s2k = secretKey.s2K if (s2k != null) { if (s2k.type in 100..110) { - LOGGER.debug("Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}") + LOGGER.debug( + "Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}") return true } } return false } - private fun decryptSKESKAndStream(esks: SortedESKs, - skesk: PGPPBEEncryptedData, - decryptorFactory: PBEDataDecryptorFactory): Boolean { + private fun decryptSKESKAndStream( + esks: SortedESKs, + skesk: PGPPBEEncryptedData, + decryptorFactory: PBEDataDecryptorFactory + ): Boolean { try { val decrypted = skesk.getDataStream(decryptorFactory) val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory)) @@ -396,38 +439,45 @@ class OpenPgpMessageInputStream( encryptedData.addRecipients(esks.pkesks.map { it.keyID }) LOGGER.debug("Successfully decrypted data with passphrase") val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options) - nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) + nestedInputStream = + OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) return true - } catch (e : UnacceptableAlgorithmException) { + } catch (e: UnacceptableAlgorithmException) { throw e - } catch (e : PGPException) { - LOGGER.debug("Decryption of encrypted data packet using password failed. Password mismatch?", e) + } catch (e: PGPException) { + LOGGER.debug( + "Decryption of encrypted data packet using password failed. Password mismatch?", e) } return false } - private fun decryptPKESKAndStream(esks: SortedESKs, - decryptionKeyId: SubkeyIdentifier, - decryptorFactory: PublicKeyDataDecryptorFactory, - pkesk: PGPPublicKeyEncryptedData): Boolean { + private fun decryptPKESKAndStream( + esks: SortedESKs, + decryptionKeyId: SubkeyIdentifier, + decryptorFactory: PublicKeyDataDecryptorFactory, + pkesk: PGPPublicKeyEncryptedData + ): Boolean { try { val decrypted = pkesk.getDataStream(decryptorFactory) val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory)) throwIfUnacceptable(sessionKey.algorithm) - val encryptedData = EncryptedData( - SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)), + val encryptedData = + EncryptedData( + SymmetricKeyAlgorithm.requireFromId( + pkesk.getSymmetricAlgorithm(decryptorFactory)), layerMetadata.depth + 1) encryptedData.decryptionKey = decryptionKeyId encryptedData.sessionKey = sessionKey encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyID }) LOGGER.debug("Successfully decrypted data with key $decryptionKeyId") val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options) - nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) + nestedInputStream = + OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) return true - } catch (e : UnacceptableAlgorithmException) { + } catch (e: UnacceptableAlgorithmException) { throw e - } catch (e : PGPException) { + } catch (e: PGPException) { LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e) } return false @@ -441,11 +491,12 @@ class OpenPgpMessageInputStream( return -1 } - val r: Int = try { - nestedInputStream!!.read() - } catch (e: IOException) { - -1 - } + val r: Int = + try { + nestedInputStream!!.read() + } catch (e: IOException) { + -1 + } if (r != -1) { signatures.updateLiteral(r.toByte()) } else { @@ -532,34 +583,37 @@ class OpenPgpMessageInputStream( return MessageMetadata((layerMetadata as Message)) } - private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull { - it.any { - k -> k.keyID == keyId - }.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { - k -> k.keyID == keyId - }) - } - - private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull { - it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> - when (pkesk.version) { - 3 -> pkesk.keyID == subkey.keyID - else -> throw NotImplementedError("Version 6 PKESK not yet supported.") - } + private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = + options.getDecryptionKeys().firstOrNull { + it.any { k -> k.keyID == keyId } + .and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { k -> k.keyID == keyId }) } - } - private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List = - options.getDecryptionKeys().filter { - it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> + private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? = + options.getDecryptionKeys().firstOrNull { + it.getSecretKeyFor(pkesk) != null && + PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> when (pkesk.version) { 3 -> pkesk.keyID == subkey.keyID else -> throw NotImplementedError("Version 6 PKESK not yet supported.") } } - } + } - private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List> { + private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List = + options.getDecryptionKeys().filter { + it.getSecretKeyFor(pkesk) != null && + PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> + when (pkesk.version) { + 3 -> pkesk.keyID == subkey.keyID + else -> throw NotImplementedError("Version 6 PKESK not yet supported.") + } + } + } + + private fun findPotentialDecryptionKeys( + pkesk: PGPPublicKeyEncryptedData + ): List> { val algorithm = pkesk.algorithm val candidates = mutableListOf>() options.getDecryptionKeys().forEach { @@ -574,11 +628,12 @@ class OpenPgpMessageInputStream( } private fun isAcceptable(algorithm: SymmetricKeyAlgorithm): Boolean = - policy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm) + policy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm) private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) { if (!isAcceptable(algorithm)) { - throw UnacceptableAlgorithmException("Symmetric-Key algorithm $algorithm is not acceptable for message decryption.") + throw UnacceptableAlgorithmException( + "Symmetric-Key algorithm $algorithm is not acceptable for message decryption.") } } @@ -610,9 +665,7 @@ class OpenPgpMessageInputStream( get() = skesks.plus(pkesks).plus(anonPkesks) } - private class Signatures( - val options: ConsumerOptions - ) : OutputStream() { + private class Signatures(val options: ConsumerOptions) : OutputStream() { val detachedSignatures = mutableListOf() val prependedSignatures = mutableListOf() val onePassSignatures = mutableListOf() @@ -636,8 +689,10 @@ class OpenPgpMessageInputStream( if (check != null) { detachedSignatures.add(check) } else { - LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") - detachedSignaturesWithMissingCert.add(SignatureVerification.Failure( + LOGGER.debug( + "No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") + detachedSignaturesWithMissingCert.add( + SignatureVerification.Failure( signature, null, SignatureValidationException("Missing verification key."))) } } @@ -648,10 +703,11 @@ class OpenPgpMessageInputStream( if (check != null) { prependedSignatures.add(check) } else { - LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") - prependedSignaturesWithMissingCert.add(SignatureVerification.Failure( - signature, null, SignatureValidationException("Missing verification key") - )) + LOGGER.debug( + "No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") + prependedSignaturesWithMissingCert.add( + SignatureVerification.Failure( + signature, null, SignatureValidationException("Missing verification key"))) } } @@ -680,7 +736,11 @@ class OpenPgpMessageInputStream( } } - fun addCorrespondingOnePassSignature(signature: PGPSignature, layer: Layer, policy: Policy) { + fun addCorrespondingOnePassSignature( + signature: PGPSignature, + layer: Layer, + policy: Policy + ) { var found = false val keyId = signature.issuerKeyId for ((i, check) in onePassSignatures.withIndex().reversed()) { @@ -694,27 +754,32 @@ class OpenPgpMessageInputStream( } check.signature = signature - val verification = SignatureVerification(signature, + val verification = + SignatureVerification( + signature, SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID)) try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(signature) + SignatureValidator.signatureWasCreatedInBounds( + options.getVerifyNotBefore(), options.getVerifyNotAfter()) + .verify(signature) CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy) LOGGER.debug("Acceptable signature by key ${verification.signingKey}") layer.addVerifiedOnePassSignature(verification) } catch (e: SignatureValidationException) { LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) - layer.addRejectedOnePassSignature(SignatureVerification.Failure(verification, e)) + layer.addRejectedOnePassSignature( + SignatureVerification.Failure(verification, e)) } break } if (!found) { - LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") - inbandSignaturesWithMissingCert.add(SignatureVerification.Failure( - signature, null, SignatureValidationException("Missing verification key.") - )) + LOGGER.debug( + "No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") + inbandSignaturesWithMissingCert.add( + SignatureVerification.Failure( + signature, null, SignatureValidationException("Missing verification key."))) } } @@ -737,7 +802,9 @@ class OpenPgpMessageInputStream( } if (options.getMissingCertificateCallback() != null) { - return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID) + return options + .getMissingCertificateCallback()!! + .onMissingPublicKeyEncountered(signature.keyID) } return null // TODO: Missing cert for sig } @@ -749,7 +816,9 @@ class OpenPgpMessageInputStream( } if (options.getMissingCertificateCallback() != null) { - return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID) + return options + .getMissingCertificateCallback()!! + .onMissingPublicKeyEncountered(signature.keyID) } return null // TODO: Missing cert for sig } @@ -800,32 +869,42 @@ class OpenPgpMessageInputStream( fun finish(layer: Layer, policy: Policy) { for (detached in detachedSignatures) { - val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier) + val verification = + SignatureVerification(detached.signature, detached.signingKeyIdentifier) try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(detached.signature) + SignatureValidator.signatureWasCreatedInBounds( + options.getVerifyNotBefore(), options.getVerifyNotAfter()) + .verify(detached.signature) CertificateValidator.validateCertificateAndVerifyInitializedSignature( - detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy) + detached.signature, + KeyRingUtils.publicKeys(detached.signingKeyRing), + policy) LOGGER.debug("Acceptable signature by key ${verification.signingKey}") layer.addVerifiedDetachedSignature(verification) - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) - layer.addRejectedDetachedSignature(SignatureVerification.Failure(verification, e)) + layer.addRejectedDetachedSignature( + SignatureVerification.Failure(verification, e)) } } for (prepended in prependedSignatures) { - val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier) + val verification = + SignatureVerification(prepended.signature, prepended.signingKeyIdentifier) try { - SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) - .verify(prepended.signature) + SignatureValidator.signatureWasCreatedInBounds( + options.getVerifyNotBefore(), options.getVerifyNotAfter()) + .verify(prepended.signature) CertificateValidator.validateCertificateAndVerifyInitializedSignature( - prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy) + prepended.signature, + KeyRingUtils.publicKeys(prepended.signingKeyRing), + policy) LOGGER.debug("Acceptable signature by key ${verification.signingKey}") layer.addVerifiedPrependedSignature(verification) - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) - layer.addRejectedPrependedSignature(SignatureVerification.Failure(verification, e)) + layer.addRejectedPrependedSignature( + SignatureVerification.Failure(verification, e)) } } @@ -864,22 +943,22 @@ class OpenPgpMessageInputStream( companion object { @JvmStatic private fun initialize(signature: PGPSignature, publicKey: PGPPublicKey) { - val verifierProvider = ImplementationFactory.getInstance() - .pgpContentVerifierBuilderProvider + val verifierProvider = + ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider try { signature.init(verifierProvider, publicKey) - } catch (e : PGPException) { + } catch (e: PGPException) { throw RuntimeException(e) } } @JvmStatic private fun initialize(ops: PGPOnePassSignature, publicKey: PGPPublicKey) { - val verifierProvider = ImplementationFactory.getInstance() - .pgpContentVerifierBuilderProvider + val verifierProvider = + ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider try { ops.init(verifierProvider, publicKey) - } catch (e : PGPException) { + } catch (e: PGPException) { throw RuntimeException(e) } } @@ -891,36 +970,40 @@ class OpenPgpMessageInputStream( private val LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream::class.java) @JvmStatic - fun create(inputStream: InputStream, - options: ConsumerOptions) = create(inputStream, options, PGPainless.getPolicy()) + fun create(inputStream: InputStream, options: ConsumerOptions) = + create(inputStream, options, PGPainless.getPolicy()) @JvmStatic - fun create(inputStream: InputStream, - options: ConsumerOptions, - policy: Policy) = create(inputStream, options, Message(), policy) + fun create(inputStream: InputStream, options: ConsumerOptions, policy: Policy) = + create(inputStream, options, Message(), policy) @JvmStatic - internal fun create(inputStream: InputStream, - options: ConsumerOptions, - metadata: Layer, - policy: Policy): OpenPgpMessageInputStream { + internal fun create( + inputStream: InputStream, + options: ConsumerOptions, + metadata: Layer, + policy: Policy + ): OpenPgpMessageInputStream { val openPgpIn = OpenPgpInputStream(inputStream) openPgpIn.reset() if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) { - return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy) + return OpenPgpMessageInputStream( + Type.non_openpgp, openPgpIn, options, metadata, policy) } if (openPgpIn.isBinaryOpenPgp) { // Simply consume OpenPGP message - return OpenPgpMessageInputStream(Type.standard, openPgpIn, options, metadata, policy) + return OpenPgpMessageInputStream( + Type.standard, openPgpIn, options, metadata, policy) } return if (openPgpIn.isAsciiArmored) { val armorIn = ArmoredInputStreamFactory.get(openPgpIn) if (armorIn.isClearText) { (metadata as Message).setCleartextSigned() - OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy) + OpenPgpMessageInputStream( + Type.cleartext_signed, armorIn, options, metadata, policy) } else { // Simply consume dearmored OpenPGP message OpenPgpMessageInputStream(Type.standard, armorIn, options, metadata, policy) @@ -930,4 +1013,4 @@ class OpenPgpMessageInputStream( } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt index 8d229fb2..3e00fbb2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/SignatureVerification.kt @@ -11,43 +11,43 @@ import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.signature.SignatureUtils /** - * Tuple of a signature and an identifier of its corresponding verification key. - * Semantic meaning of the signature verification (success, failure) is merely given by context. - * E.g. [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications, - * while the class [Failure] contains failed verifications. + * Tuple of a signature and an identifier of its corresponding verification key. Semantic meaning of + * the signature verification (success, failure) is merely given by context. E.g. + * [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications, while the class + * [Failure] contains failed verifications. * * @param signature PGPSignature object * @param signingKey [SubkeyIdentifier] of the (sub-) key that is used for signature verification. - * Note, that this might be null, e.g. in case of a [Failure] due to missing verification key. + * Note, that this might be null, e.g. in case of a [Failure] due to missing verification key. */ -data class SignatureVerification( - val signature: PGPSignature, - val signingKey: SubkeyIdentifier -) { +data class SignatureVerification(val signature: PGPSignature, val signingKey: SubkeyIdentifier) { override fun toString(): String { return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" + - " Key: $signingKey;" + " Key: $signingKey;" } /** - * Tuple object of a [SignatureVerification] and the corresponding [SignatureValidationException] - * that caused the verification to fail. + * Tuple object of a [SignatureVerification] and the corresponding + * [SignatureValidationException] that caused the verification to fail. * - * @param signatureVerification verification (tuple of [PGPSignature] and corresponding [SubkeyIdentifier]) + * @param signatureVerification verification (tuple of [PGPSignature] and corresponding + * [SubkeyIdentifier]) * @param validationException exception that caused the verification to fail */ data class Failure( - val signature: PGPSignature, - val signingKey: SubkeyIdentifier?, - val validationException: SignatureValidationException + val signature: PGPSignature, + val signingKey: SubkeyIdentifier?, + val validationException: SignatureValidationException ) { - constructor(verification: SignatureVerification, validationException: SignatureValidationException): - this(verification.signature, verification.signingKey, validationException) + constructor( + verification: SignatureVerification, + validationException: SignatureValidationException + ) : this(verification.signature, verification.signingKey, validationException) override fun toString(): String { return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}" } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt index f6c5a454..73c10e8a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt @@ -4,6 +4,9 @@ package org.pgpainless.decryption_verification +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.MarkerPacket import org.bouncycastle.bcpg.Packet @@ -13,25 +16,21 @@ import org.bouncycastle.openpgp.PGPLiteralData import org.bouncycastle.openpgp.PGPOnePassSignature import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.algorithm.OpenPgpPacket -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream /** - * Since we need to update signatures with data from the underlying stream, this class is used to tee out the data. - * Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data out though, since - * [BCPGInputStream.readPacket] inconsistently calls a mix of [BCPGInputStream.read] and - * [InputStream.read] of the underlying stream. This would cause the second length byte to get swallowed up. + * Since we need to update signatures with data from the underlying stream, this class is used to + * tee out the data. Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data + * out though, since [BCPGInputStream.readPacket] inconsistently calls a mix of + * [BCPGInputStream.read] and [InputStream.read] of the underlying stream. This would cause the + * second length byte to get swallowed up. * - * Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the underlying - * stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the next packets tag, - * we need to delay teeing out that byte to signature verifiers. - * Hence, the reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using + * Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the + * underlying stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the + * next packets tag, we need to delay teeing out that byte to signature verifiers. Hence, the + * reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using * [DelayedTeeInputStream.squeeze]. */ -class TeeBCPGInputStream( - inputStream: BCPGInputStream, - outputStream: OutputStream) { +class TeeBCPGInputStream(inputStream: BCPGInputStream, outputStream: OutputStream) { private val delayedTee: DelayedTeeInputStream private val packetInputStream: BCPGInputStream @@ -43,8 +42,7 @@ class TeeBCPGInputStream( fun nextPacketTag(): OpenPgpPacket? { return packetInputStream.nextPacketTag().let { - if (it == -1) null - else OpenPgpPacket.requireFromTag(it) + if (it == -1) null else OpenPgpPacket.requireFromTag(it) } } @@ -82,8 +80,8 @@ class TeeBCPGInputStream( } class DelayedTeeInputStream( - private val inputStream: InputStream, - private val outputStream: OutputStream + private val inputStream: InputStream, + private val outputStream: OutputStream ) : InputStream() { private var last: Int = -1 @@ -94,7 +92,7 @@ class TeeBCPGInputStream( return try { last = inputStream.read() last - } catch (e : IOException) { + } catch (e: IOException) { if (e.message?.contains("crc check failed in armored message") == true) { throw e } @@ -108,19 +106,18 @@ class TeeBCPGInputStream( } inputStream.read(b, off, len).let { r -> - last = if (r > 0) { - outputStream.write(b, off, r - 1) - b[off + r - 1].toInt() - } else { - -1 - } + last = + if (r > 0) { + outputStream.write(b, off, r - 1) + b[off + r - 1].toInt() + } else { + -1 + } return r } } - /** - * Squeeze the last byte out and update the output stream. - */ + /** Squeeze the last byte out and update the output stream. */ fun squeeze() { if (last != -1) { outputStream.write(last) @@ -133,4 +130,4 @@ class TeeBCPGInputStream( outputStream.close() } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt index 7a3b93ee..78614a96 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt @@ -4,47 +4,49 @@ package org.pgpainless.decryption_verification.cleartext_signatures +import java.io.* +import kotlin.jvm.Throws import org.bouncycastle.bcpg.ArmoredInputStream import org.bouncycastle.openpgp.PGPSignatureList import org.bouncycastle.util.Strings import org.pgpainless.exception.WrongConsumingMethodException import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.util.ArmoredInputStreamFactory -import java.io.* -import kotlin.jvm.Throws /** - * Utility class to deal with cleartext-signed messages. - * Based on Bouncycastle's [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor]. + * Utility class to deal with cleartext-signed messages. Based on Bouncycastle's + * [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor]. */ class ClearsignedMessageUtil { companion object { /** - * Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided - * messageOutputStream. + * Dearmor a clearsigned message, detach the inband signatures and write the plaintext + * message to the provided messageOutputStream. * * @param clearsignedInputStream input stream containing a clearsigned message * @param messageOutputStream output stream to which the dearmored message shall be written * @return signatures - * * @throws IOException if the message is not clearsigned or some other IO error happens * @throws WrongConsumingMethodException in case the armored message is not cleartext signed */ @JvmStatic @Throws(WrongConsumingMethodException::class, IOException::class) fun detachSignaturesFromInbandClearsignedMessage( - clearsignedInputStream: InputStream, - messageOutputStream: OutputStream): PGPSignatureList { - val input: ArmoredInputStream = if (clearsignedInputStream is ArmoredInputStream) { - clearsignedInputStream - } else { - ArmoredInputStreamFactory.get(clearsignedInputStream) - } + clearsignedInputStream: InputStream, + messageOutputStream: OutputStream + ): PGPSignatureList { + val input: ArmoredInputStream = + if (clearsignedInputStream is ArmoredInputStream) { + clearsignedInputStream + } else { + ArmoredInputStreamFactory.get(clearsignedInputStream) + } if (!input.isClearText) { - throw WrongConsumingMethodException("Message isn't using the Cleartext Signature Framework.") + throw WrongConsumingMethodException( + "Message isn't using the Cleartext Signature Framework.") } BufferedOutputStream(messageOutputStream).use { output -> @@ -94,7 +96,11 @@ class ClearsignedMessageUtil { } @JvmStatic - private fun readInputLine(bOut: ByteArrayOutputStream, lookAhead: Int, fIn: InputStream): Int { + private fun readInputLine( + bOut: ByteArrayOutputStream, + lookAhead: Int, + fIn: InputStream + ): Int { var mLookAhead = lookAhead bOut.reset() var ch = mLookAhead @@ -150,4 +156,4 @@ class ClearsignedMessageUtil { return isLineEnding(b) || b == '\t'.code.toByte() || b == ' '.code.toByte() } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt index eed6438f..da7c7cec 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt @@ -8,12 +8,12 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream /** - * Implementation of the [MultiPassStrategy]. - * This class keeps the read data in memory by caching the data inside a [ByteArrayOutputStream]. + * Implementation of the [MultiPassStrategy]. This class keeps the read data in memory by caching + * the data inside a [ByteArrayOutputStream]. * - * Note, that this class is suitable and efficient for processing small amounts of data. - * For larger data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to - * prevent [OutOfMemoryError] and other issues. + * Note, that this class is suitable and efficient for processing small amounts of data. For larger + * data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to prevent + * [OutOfMemoryError] and other issues. */ class InMemoryMultiPassStrategy : MultiPassStrategy { @@ -26,4 +26,4 @@ class InMemoryMultiPassStrategy : MultiPassStrategy { get() = ByteArrayInputStream(getBytes()) fun getBytes(): ByteArray = messageOutputStream.toByteArray() -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt index ae69b9c5..4ef7ef3a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt @@ -7,16 +7,16 @@ package org.pgpainless.decryption_verification.cleartext_signatures import java.io.* /** - * Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures, - * a strategy for how to cache the read data is required. - * Otherwise, large data kept in memory could cause an [OutOfMemoryError] or other issues. + * Since for verification of cleartext signed messages, we need to read the whole data twice in + * order to verify signatures, a strategy for how to cache the read data is required. Otherwise, + * large data kept in memory could cause an [OutOfMemoryError] or other issues. * - * This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes - * to do verification. + * This is an Interface that describes a strategy to deal with the fact that detached signatures + * require multiple passes to do verification. * - * This interface can be used to write the signed data stream out via [messageOutputStream] and later - * get access to the data again via [messageInputStream]. - * Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away. + * This interface can be used to write the signed data stream out via [messageOutputStream] and + * later get access to the data again via [messageInputStream]. Thereby the detail where the data is + * being stored (memory, file, etc.) can be abstracted away. */ interface MultiPassStrategy { @@ -32,8 +32,8 @@ interface MultiPassStrategy { * Provide an [InputStream] which contains the data that was previously written away in * [messageOutputStream]. * - * As there may be multiple signatures that need to be processed, each call of this method MUST return - * a new [InputStream]. + * As there may be multiple signatures that need to be processed, each call of this method MUST + * return a new [InputStream]. * * @return input stream * @throws IOException io error @@ -43,9 +43,10 @@ interface MultiPassStrategy { companion object { /** - * Write the message content out to a file and re-read it to verify signatures. - * This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory. - * After the message has been processed completely, the messages content are available at the provided file. + * Write the message content out to a file and re-read it to verify signatures. This + * strategy is best suited for larger messages (e.g. plaintext signed files) which might not + * fit into memory. After the message has been processed completely, the messages content + * are available at the provided file. * * @param file target file * @return strategy @@ -56,10 +57,10 @@ interface MultiPassStrategy { } /** - * Read the message content into memory. - * This strategy is best suited for small messages which fit into memory. - * After the message has been processed completely, the message content can be accessed by calling - * [ByteArrayOutputStream.toByteArray] on [messageOutputStream]. + * Read the message content into memory. This strategy is best suited for small messages + * which fit into memory. After the message has been processed completely, the message + * content can be accessed by calling [ByteArrayOutputStream.toByteArray] on + * [messageOutputStream]. * * @return strategy */ @@ -68,4 +69,4 @@ interface MultiPassStrategy { return InMemoryMultiPassStrategy() } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt index 5d6567a4..88fcf6f1 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt @@ -7,18 +7,15 @@ package org.pgpainless.decryption_verification.cleartext_signatures import java.io.* /** - * Implementation of the [MultiPassStrategy]. - * When processing signed data the first time, the data is being written out into a file. - * For the second pass, that file is being read again. + * Implementation of the [MultiPassStrategy]. When processing signed data the first time, the data + * is being written out into a file. For the second pass, that file is being read again. * - * This strategy is recommended when larger amounts of data need to be processed. - * For smaller files, [InMemoryMultiPassStrategy] yields higher efficiency. + * This strategy is recommended when larger amounts of data need to be processed. For smaller files, + * [InMemoryMultiPassStrategy] yields higher efficiency. * * @param file file to write the data to and read from */ -class WriteToFileMultiPassStrategy( - private val file: File -) : MultiPassStrategy { +class WriteToFileMultiPassStrategy(private val file: File) : MultiPassStrategy { override val messageOutputStream: OutputStream @Throws(IOException::class) @@ -39,5 +36,4 @@ class WriteToFileMultiPassStrategy( } return FileInputStream(file) } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt index 133bfcb3..2bea3356 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/InputSymbol.kt @@ -5,35 +5,26 @@ package org.pgpainless.decryption_verification.syntax_check enum class InputSymbol { - /** - * A [PGPLiteralData] packet. - */ + /** A [PGPLiteralData] packet. */ LITERAL_DATA, - /** - * A [PGPSignatureList] object. - */ + /** A [PGPSignatureList] object. */ SIGNATURE, - /** - * A [PGPOnePassSignatureList] object. - */ + /** A [PGPOnePassSignatureList] object. */ ONE_PASS_SIGNATURE, /** - * A [PGPCompressedData] packet. - * The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify - * its nested packet sequence. + * A [PGPCompressedData] packet. The contents of this packet MUST form a valid OpenPGP message, + * so a nested PDA is opened to verify its nested packet sequence. */ COMPRESSED_DATA, /** - * A [PGPEncryptedDataList] object. - * This object combines multiple ESKs and the corresponding Symmetrically Encrypted - * (possibly Integrity Protected) Data packet. + * A [PGPEncryptedDataList] object. This object combines multiple ESKs and the corresponding + * Symmetrically Encrypted (possibly Integrity Protected) Data packet. */ ENCRYPTED_DATA, /** - * Marks the end of a (sub-) sequence. - * This input is given if the end of an OpenPGP message is reached. - * This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents - * (e.g. the end of a Compressed Data packet). + * Marks the end of a (sub-) sequence. This input is given if the end of an OpenPGP message is + * reached. This might be the case for the end of the whole ciphertext, or the end of a packet + * with nested contents (e.g. the end of a Compressed Data packet). */ END_OF_SEQUENCE -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt index 56bd9a77..127d10f8 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.kt @@ -9,9 +9,10 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException /** * This class describes the syntax for OpenPGP messages as specified by rfc4880. * - * See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) - * See [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/) - * See [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/) + * See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) See + * [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/) + * See + * [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/) */ class OpenPgpMessageSyntax : Syntax { @@ -33,10 +34,12 @@ class OpenPgpMessageSyntax : Syntax { return when (input) { InputSymbol.LITERAL_DATA -> Transition(State.LITERAL_MESSAGE) InputSymbol.SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.MSG) - InputSymbol.ONE_PASS_SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG) + InputSymbol.ONE_PASS_SIGNATURE -> + Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG) InputSymbol.COMPRESSED_DATA -> Transition(State.COMPRESSED_MESSAGE) InputSymbol.ENCRYPTED_DATA -> Transition(State.ENCRYPTED_MESSAGE) - InputSymbol.END_OF_SEQUENCE -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem) + InputSymbol.END_OF_SEQUENCE -> + throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem) else -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem) } } @@ -85,4 +88,4 @@ class OpenPgpMessageSyntax : Syntax { } throw MalformedOpenPgpMessageException(State.VALID, input, stackItem) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt index b1949917..7dff6ba2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/PDA.kt @@ -8,37 +8,41 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException import org.slf4j.LoggerFactory /** - * Pushdown Automaton for validating context-free languages. - * In PGPainless, this class is used to validate OpenPGP message packet sequences against the allowed syntax. + * Pushdown Automaton for validating context-free languages. In PGPainless, this class is used to + * validate OpenPGP message packet sequences against the allowed syntax. * * See [OpenPGP Message Syntax](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) */ -class PDA constructor( - private val syntax: Syntax, - private val stack: ArrayDeque, - private val inputs: MutableList, - private var state: State +class PDA +constructor( + private val syntax: Syntax, + private val stack: ArrayDeque, + private val inputs: MutableList, + private var state: State ) { /** - * Construct a PDA with a custom [Syntax], initial [State] and initial [StackSymbols][StackSymbol]. + * Construct a PDA with a custom [Syntax], initial [State] and initial + * [StackSymbols][StackSymbol]. * * @param syntax syntax * @param initialState initial state - * @param initialStack zero or more initial stack items (get pushed onto the stack in order of appearance) + * @param initialStack zero or more initial stack items (get pushed onto the stack in order of + * appearance) */ - constructor(syntax: Syntax, initialState: State, vararg initialStack: StackSymbol): this ( - syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState) + constructor( + syntax: Syntax, + initialState: State, + vararg initialStack: StackSymbol + ) : this(syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState) + + /** Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax]. */ + constructor() : + this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG) /** - * Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax]. - */ - constructor(): this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG) - - /** - * Process the next [InputSymbol]. - * This will either leave the PDA in the next state, or throw a [MalformedOpenPgpMessageException] if the - * input symbol is rejected. + * Process the next [InputSymbol]. This will either leave the PDA in the next state, or throw a + * [MalformedOpenPgpMessageException] if the input symbol is rejected. * * @param input input symbol * @throws MalformedOpenPgpMessageException if the input symbol is rejected @@ -53,14 +57,17 @@ class PDA constructor( } inputs.add(input) } catch (e: MalformedOpenPgpMessageException) { - val stackFormat = if (stackSymbol != null) { - "${stack.joinToString()}||$stackSymbol" - } else { - stack.joinToString() - } - val wrapped = MalformedOpenPgpMessageException( + val stackFormat = + if (stackSymbol != null) { + "${stack.joinToString()}||$stackSymbol" + } else { + stack.joinToString() + } + val wrapped = + MalformedOpenPgpMessageException( "Malformed message: After reading packet sequence ${inputs.joinToString()}, token '$input' is not allowed.\n" + - "No transition from state '$state' with stack $stackFormat", e) + "No transition from state '$state' with stack $stackFormat", + e) LOGGER.debug("Invalid input '$input'", wrapped) throw wrapped } @@ -87,7 +94,8 @@ class PDA constructor( */ fun assertValid() { if (!isValid()) { - throw MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: ${toString()}") + throw MalformedOpenPgpMessageException( + "Pushdown Automaton is not in an acceptable state: ${toString()}") } } @@ -114,7 +122,6 @@ class PDA constructor( } companion object { - @JvmStatic - private val LOGGER = LoggerFactory.getLogger(PDA::class.java) + @JvmStatic private val LOGGER = LoggerFactory.getLogger(PDA::class.java) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt index 960e5eba..8f927864 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/StackSymbol.kt @@ -5,16 +5,10 @@ package org.pgpainless.decryption_verification.syntax_check enum class StackSymbol { - /** - * OpenPGP Message. - */ + /** OpenPGP Message. */ MSG, - /** - * OnePassSignature (in case of BC this represents a OnePassSignatureList). - */ + /** OnePassSignature (in case of BC this represents a OnePassSignatureList). */ OPS, - /** - * Special symbol representing the end of the message. - */ + /** Special symbol representing the end of the message. */ TERMINUS -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt index 8e1f682c..8fad2fb7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/State.kt @@ -4,13 +4,11 @@ package org.pgpainless.decryption_verification.syntax_check -/** - * Set of states of the automaton. - */ +/** Set of states of the automaton. */ enum class State { OPENPGP_MESSAGE, LITERAL_MESSAGE, COMPRESSED_MESSAGE, ENCRYPTED_MESSAGE, VALID -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Syntax.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Syntax.kt index 16f0445b..a90e981e 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Syntax.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Syntax.kt @@ -6,25 +6,24 @@ package org.pgpainless.decryption_verification.syntax_check import org.pgpainless.exception.MalformedOpenPgpMessageException -/** - * This interface can be used to define a custom syntax for the [PDA]. - */ +/** This interface can be used to define a custom syntax for the [PDA]. */ interface Syntax { /** * Describe a transition rule from [State]
from
for [InputSymbol]
input
- * with [StackSymbol]
stackItem
from the top of the [PDAs][PDA] stack. - * The resulting [Transition] contains the new [State], as well as a list of - * [StackSymbols][StackSymbol] that get pushed onto the stack by the transition rule. - * If there is no applicable rule, a [MalformedOpenPgpMessageException] is thrown, since in this case - * the [InputSymbol] must be considered illegal. + * with [StackSymbol]
stackItem
from the top of the [PDAs][PDA] stack. The resulting + * [Transition] contains the new [State], as well as a list of [StackSymbols][StackSymbol] that + * get pushed onto the stack by the transition rule. If there is no applicable rule, a + * [MalformedOpenPgpMessageException] is thrown, since in this case the [InputSymbol] must be + * considered illegal. * * @param from current state of the PDA * @param input input symbol * @param stackItem item that got popped from the top of the stack * @return applicable transition rule containing the new state and pushed stack symbols - * @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input symbol is illegal) + * @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input + * symbol is illegal) */ @Throws(MalformedOpenPgpMessageException::class) fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Transition.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Transition.kt index 5bc6dfc6..d0a09992 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Transition.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/syntax_check/Transition.kt @@ -5,18 +5,18 @@ package org.pgpainless.decryption_verification.syntax_check /** - * Result of applying a transition rule. - * Transition rules can be described by implementing the [Syntax] interface. + * Result of applying a transition rule. Transition rules can be described by implementing the + * [Syntax] interface. * * @param newState new [State] that is reached by applying the transition. - * @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the transition. - * The list contains items in the order in which they are pushed onto the stack. - * The list may be empty. + * @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the + * transition. The list contains items in the order in which they are pushed onto the stack. The + * list may be empty. */ -class Transition private constructor( - val pushedItems: List, - val newState: State -) { +class Transition private constructor(val pushedItems: List, val newState: State) { - constructor(newState: State, vararg pushedItems: StackSymbol): this(pushedItems.toList(), newState) -} \ No newline at end of file + constructor( + newState: State, + vararg pushedItems: StackSymbol + ) : this(pushedItems.toList(), newState) +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcHashContextSigner.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcHashContextSigner.kt index 90f275a4..68e41091 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcHashContextSigner.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcHashContextSigner.kt @@ -4,6 +4,7 @@ package org.pgpainless.encryption_signing +import java.security.MessageDigest import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPrivateKey @@ -13,20 +14,23 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator import org.pgpainless.PGPainless import org.pgpainless.algorithm.SignatureType import org.pgpainless.key.protection.SecretKeyRingProtector -import java.security.MessageDigest class BcHashContextSigner { companion object { @JvmStatic - fun signHashContext(hashContext: MessageDigest, - signatureType: SignatureType, - secretKey: PGPSecretKeyRing, - protector: SecretKeyRingProtector): PGPSignature { + fun signHashContext( + hashContext: MessageDigest, + signatureType: SignatureType, + secretKey: PGPSecretKeyRing, + protector: SecretKeyRingProtector + ): PGPSignature { val info = PGPainless.inspectKeyRing(secretKey) - return info.signingSubkeys.mapNotNull { info.getSecretKey(it.keyID) }.firstOrNull() - ?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) } - ?: throw PGPException("Key does not contain suitable signing subkey.") + return info.signingSubkeys + .mapNotNull { info.getSecretKey(it.keyID) } + .firstOrNull() + ?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) } + ?: throw PGPException("Key does not contain suitable signing subkey.") } /** @@ -38,12 +42,14 @@ class BcHashContextSigner { * @throws PGPException in case of an OpenPGP error */ @JvmStatic - internal fun signHashContext(hashContext: MessageDigest, - signatureType: SignatureType, - privateKey: PGPPrivateKey): PGPSignature { + internal fun signHashContext( + hashContext: MessageDigest, + signatureType: SignatureType, + privateKey: PGPPrivateKey + ): PGPSignature { return PGPSignatureGenerator(BcPGPHashContextContentSignerBuilder(hashContext)) - .apply { init(signatureType.code, privateKey) } - .generate() + .apply { init(signatureType.code, privateKey) } + .generate() } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt index 381c4716..bf66b6dc 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.kt @@ -4,6 +4,8 @@ package org.pgpainless.encryption_signing +import java.io.OutputStream +import java.security.MessageDigest import org.bouncycastle.bcpg.PublicKeyAlgorithmTags import org.bouncycastle.crypto.CipherParameters import org.bouncycastle.crypto.CryptoException @@ -23,17 +25,14 @@ import org.bouncycastle.openpgp.operator.PGPContentSigner import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm -import java.io.OutputStream -import java.security.MessageDigest /** - * Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash contexts. - * This can come in handy to sign data, which was already processed to calculate the hash context, without the - * need to process it again to calculate the OpenPGP signature. + * Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash + * contexts. This can come in handy to sign data, which was already processed to calculate the hash + * context, without the need to process it again to calculate the OpenPGP signature. */ -class BcPGPHashContextContentSignerBuilder( - private val messageDigest: MessageDigest -) : PGPHashContextContentSignerBuilder() { +class BcPGPHashContextContentSignerBuilder(private val messageDigest: MessageDigest) : + PGPHashContextContentSignerBuilder() { private val keyConverter = BcPGPKeyConverter() private val _hashAlgorithm: HashAlgorithm @@ -50,15 +49,22 @@ class BcPGPHashContextContentSignerBuilder( return object : PGPContentSigner { override fun getOutputStream(): OutputStream = SignerOutputStream(signer) - override fun getSignature(): ByteArray = try { - signer.generateSignature() - } catch (e : CryptoException) { - throw IllegalStateException("unable to create signature.", e) - } + + override fun getSignature(): ByteArray = + try { + signer.generateSignature() + } catch (e: CryptoException) { + throw IllegalStateException("unable to create signature.", e) + } + override fun getDigest(): ByteArray = messageDigest.digest() + override fun getType(): Int = signatureType + override fun getHashAlgorithm(): Int = _hashAlgorithm.algorithmId + override fun getKeyAlgorithm(): Int = keyAlgorithm.algorithmId + override fun getKeyID(): Long = privateKey.keyID } } @@ -67,21 +73,25 @@ class BcPGPHashContextContentSignerBuilder( @JvmStatic private fun requireFromName(digestName: String): HashAlgorithm { val algorithm = HashAlgorithm.fromName(digestName) - require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName"} + require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName" } return algorithm } @JvmStatic - private fun createSigner(keyAlgorithm: PublicKeyAlgorithm, - messageDigest: MessageDigest, - keyParam: CipherParameters): Signer { + private fun createSigner( + keyAlgorithm: PublicKeyAlgorithm, + messageDigest: MessageDigest, + keyParam: CipherParameters + ): Signer { val staticDigest = ExistingMessageDigest(messageDigest) return when (keyAlgorithm.algorithmId) { - PublicKeyAlgorithmTags.RSA_GENERAL, PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest) + PublicKeyAlgorithmTags.RSA_GENERAL, + PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest) PublicKeyAlgorithmTags.DSA -> DSADigestSigner(DSASigner(), staticDigest) PublicKeyAlgorithmTags.ECDSA -> DSADigestSigner(ECDSASigner(), staticDigest) PublicKeyAlgorithmTags.EDDSA_LEGACY -> { - if (keyParam is Ed25519PrivateKeyParameters || keyParam is Ed25519PublicKeyParameters) + if (keyParam is Ed25519PrivateKeyParameters || + keyParam is Ed25519PublicKeyParameters) EdDsaSigner(Ed25519Signer(), staticDigest) else EdDsaSigner(Ed448Signer(byteArrayOf()), staticDigest) } @@ -91,10 +101,7 @@ class BcPGPHashContextContentSignerBuilder( } // Copied from BCs BcImplProvider - required since BCs class is package visible only :/ - internal class EdDsaSigner( - private val signer: Signer, - private val digest: Digest - ) : Signer { + internal class EdDsaSigner(private val signer: Signer, private val digest: Digest) : Signer { private val digBuf: ByteArray = ByteArray(digest.digestSize) override fun init(forSigning: Boolean, param: CipherParameters) { @@ -128,4 +135,4 @@ class BcPGPHashContextContentSignerBuilder( digest.reset() } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/CRLFGeneratorStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/CRLFGeneratorStream.kt index 8735d7b1..badc8b48 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/CRLFGeneratorStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/CRLFGeneratorStream.kt @@ -5,17 +5,15 @@ package org.pgpainless.encryption_signing -import org.pgpainless.algorithm.StreamEncoding import java.io.OutputStream +import org.pgpainless.algorithm.StreamEncoding /** - * [OutputStream] which applies CR-LF encoding of its input data, based on the desired [StreamEncoding]. - * This implementation originates from the Bouncy Castle library. + * [OutputStream] which applies CR-LF encoding of its input data, based on the desired + * [StreamEncoding]. This implementation originates from the Bouncy Castle library. */ -class CRLFGeneratorStream( - private val crlfOut: OutputStream, - encoding: StreamEncoding -) : OutputStream() { +class CRLFGeneratorStream(private val crlfOut: OutputStream, encoding: StreamEncoding) : + OutputStream() { private val isBinary: Boolean private var lastB = 0 @@ -26,9 +24,9 @@ class CRLFGeneratorStream( override fun write(b: Int) { if (!isBinary) { - if (b == '\n'.code && lastB != '\r'.code) { // Unix + if (b == '\n'.code && lastB != '\r'.code) { // Unix crlfOut.write('\r'.code) - } else if (lastB == '\r'.code) { // MAC + } else if (lastB == '\r'.code) { // MAC if (b != '\n'.code) { crlfOut.write('\n'.code) } @@ -49,4 +47,4 @@ class CRLFGeneratorStream( super.flush() crlfOut.flush() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt index 324fcf3b..13785df4 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilder.kt @@ -4,16 +4,18 @@ package org.pgpainless.encryption_signing +import java.io.OutputStream import org.pgpainless.PGPainless.Companion.getPolicy import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.OutputStream class EncryptionBuilder : EncryptionBuilderInterface { - override fun onOutputStream(outputStream: OutputStream): EncryptionBuilderInterface.WithOptions { + override fun onOutputStream( + outputStream: OutputStream + ): EncryptionBuilderInterface.WithOptions { return WithOptionsImpl(outputStream) } @@ -26,8 +28,7 @@ class EncryptionBuilder : EncryptionBuilderInterface { companion object { - @JvmStatic - val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java) + @JvmStatic val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java) /** * Negotiate the [SymmetricKeyAlgorithm] used for message encryption. @@ -36,24 +37,32 @@ class EncryptionBuilder : EncryptionBuilderInterface { * @return negotiated symmetric key algorithm */ @JvmStatic - fun negotiateSymmetricEncryptionAlgorithm(encryptionOptions: EncryptionOptions): SymmetricKeyAlgorithm { - val preferences = encryptionOptions.keyViews.values + fun negotiateSymmetricEncryptionAlgorithm( + encryptionOptions: EncryptionOptions + ): SymmetricKeyAlgorithm { + val preferences = + encryptionOptions.keyViews.values .map { it.preferredSymmetricKeyAlgorithms } .toList() - val algorithm = byPopularity().negotiate( - getPolicy().symmetricKeyEncryptionAlgorithmPolicy, - encryptionOptions.encryptionAlgorithmOverride, - preferences) - LOGGER.debug("Negotiation resulted in {} being the symmetric encryption algorithm of choice.", algorithm) + val algorithm = + byPopularity() + .negotiate( + getPolicy().symmetricKeyEncryptionAlgorithmPolicy, + encryptionOptions.encryptionAlgorithmOverride, + preferences) + LOGGER.debug( + "Negotiation resulted in {} being the symmetric encryption algorithm of choice.", + algorithm) return algorithm } @JvmStatic fun negotiateCompressionAlgorithm(producerOptions: ProducerOptions): CompressionAlgorithm { val compressionAlgorithmOverride = producerOptions.compressionAlgorithmOverride - return compressionAlgorithmOverride ?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm() + return compressionAlgorithmOverride + ?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm() // TODO: Negotiation } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilderInterface.kt index 2d42c68a..586fbc6e 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilderInterface.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionBuilderInterface.kt @@ -4,9 +4,9 @@ package org.pgpainless.encryption_signing -import org.bouncycastle.openpgp.PGPException import java.io.IOException import java.io.OutputStream +import org.bouncycastle.openpgp.PGPException fun interface EncryptionBuilderInterface { @@ -26,11 +26,11 @@ fun interface EncryptionBuilderInterface { * * @param options options * @return encryption stream - * * @throws PGPException if something goes wrong during encryption stream preparation - * @throws IOException if something goes wrong during encryption stream preparation (writing headers) + * @throws IOException if something goes wrong during encryption stream preparation (writing + * headers) */ @Throws(PGPException::class, IOException::class) fun withOptions(options: ProducerOptions): EncryptionStream } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt index 00b1359a..3e232654 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt @@ -4,6 +4,7 @@ package org.pgpainless.encryption_signing +import java.util.* import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator @@ -19,12 +20,8 @@ import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.info.KeyAccessor import org.pgpainless.key.info.KeyRingInfo import org.pgpainless.util.Passphrase -import java.util.* - -class EncryptionOptions( - private val purpose: EncryptionPurpose -) { +class EncryptionOptions(private val purpose: EncryptionPurpose) { private val _encryptionMethods: MutableSet = mutableSetOf() private val _encryptionKeyIdentifiers: MutableSet = mutableSetOf() private val _keyRingInfo: MutableMap = mutableMapOf() @@ -37,16 +34,20 @@ class EncryptionOptions( val encryptionMethods get() = _encryptionMethods.toSet() + val encryptionKeyIdentifiers get() = _encryptionKeyIdentifiers.toSet() + val keyRingInfo get() = _keyRingInfo.toMap() + val keyViews get() = _keyViews.toMap() + val encryptionAlgorithmOverride get() = _encryptionAlgorithmOverride - constructor(): this(EncryptionPurpose.ANY) + constructor() : this(EncryptionPurpose.ANY) /** * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys @@ -54,54 +55,56 @@ class EncryptionOptions( * * @return encryption options */ - fun setEvaluationDate(evaluationDate: Date) = apply { - this.evaluationDate = evaluationDate - } + fun setEvaluationDate(evaluationDate: Date) = apply { this.evaluationDate = evaluationDate } /** - * Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for - * identifiable bindings. - * Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients. + * Identify authenticatable certificates for the given user-ID by querying the {@link + * CertificateAuthority} for identifiable bindings. Add all acceptable bindings, whose trust + * amount is larger or equal to the target amount to the list of recipients. + * * @param userId userId - * @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address + * @param email if true, treat the user-ID as an email address and match all user-IDs containing + * the mail address * @param authority certificate authority - * @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated, - * 60 = partially authenticated...) + * @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated, 60 + * = partially authenticated...) * @return encryption options */ @JvmOverloads - fun addAuthenticatableRecipients(userId: String, email: Boolean, authority: CertificateAuthority, targetAmount: Int = 120) = apply { + fun addAuthenticatableRecipients( + userId: String, + email: Boolean, + authority: CertificateAuthority, + targetAmount: Int = 120 + ) = apply { var foundAcceptable = false - authority.lookupByUserId(userId, email, evaluationDate, targetAmount) - .filter { it.isAuthenticated() } - .forEach { addRecipient(it.certificate) - .also { - foundAcceptable = true - } - } + authority + .lookupByUserId(userId, email, evaluationDate, targetAmount) + .filter { it.isAuthenticated() } + .forEach { addRecipient(it.certificate).also { foundAcceptable = true } } require(foundAcceptable) { "Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount." } } /** - * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients. + * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) + * as recipients. * * @param keys keys * @return this */ fun addRecipients(keys: Iterable) = apply { keys.toList().let { - require(it.isNotEmpty()) { - "Set of recipient keys cannot be empty." - } + require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." } it.forEach { key -> addRecipient(key) } } } /** - * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients. - * Per key ring, the selector is applied to select one or more encryption subkeys. + * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) + * as recipients. Per key ring, the selector is applied to select one or more encryption + * subkeys. * * @param keys keys * @param selector encryption key selector @@ -109,9 +112,7 @@ class EncryptionOptions( */ fun addRecipients(keys: Iterable, selector: EncryptionKeySelector) = apply { keys.toList().let { - require(it.isNotEmpty()) { - "Set of recipient keys cannot be empty." - } + require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." } it.forEach { key -> addRecipient(key, selector) } } } @@ -125,19 +126,25 @@ class EncryptionOptions( fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector) /** - * Add a recipient by providing a key and recipient user-id. - * The user-id is used to determine the recipients preferences (algorithms etc.). + * Add a recipient by providing a key and recipient user-id. The user-id is used to determine + * the recipients preferences (algorithms etc.). * * @param key key ring * @param userId user id * @return this */ fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) = - addRecipient(key, userId, encryptionKeySelector) + addRecipient(key, userId, encryptionKeySelector) - fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence, encryptionKeySelector: EncryptionKeySelector) = apply { + fun addRecipient( + key: PGPPublicKeyRing, + userId: CharSequence, + encryptionKeySelector: EncryptionKeySelector + ) = apply { val info = KeyRingInfo(key, evaluationDate) - val subkeys = encryptionKeySelector.selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose)) + val subkeys = + encryptionKeySelector.selectEncryptionSubkeys( + info.getEncryptionSubkeys(userId, purpose)) if (subkeys.isEmpty()) { throw KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key)) } @@ -155,17 +162,23 @@ class EncryptionOptions( } @JvmOverloads - fun addHiddenRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector = encryptionKeySelector) = apply { - addAsRecipient(key, selector, true) - } + fun addHiddenRecipient( + key: PGPPublicKeyRing, + selector: EncryptionKeySelector = encryptionKeySelector + ) = apply { addAsRecipient(key, selector, true) } - private fun addAsRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector, wildcardKeyId: Boolean) = apply { + private fun addAsRecipient( + key: PGPPublicKeyRing, + selector: EncryptionKeySelector, + wildcardKeyId: Boolean + ) = apply { val info = KeyRingInfo(key, evaluationDate) - val primaryKeyExpiration = try { - info.primaryKeyExpirationDate - } catch (e: NoSuchElementException) { - throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key)) - } + val primaryKeyExpiration = + try { + info.primaryKeyExpirationDate + } catch (e: NoSuchElementException) { + throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key)) + } if (primaryKeyExpiration != null && primaryKeyExpiration < evaluationDate) { throw ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration) @@ -174,10 +187,12 @@ class EncryptionOptions( var encryptionSubkeys = selector.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose)) // There are some legacy keys around without key flags. - // If we allow encryption for those keys, we add valid keys without any key flags, if they are + // If we allow encryption for those keys, we add valid keys without any key flags, if they + // are // capable of encryption by means of their algorithm if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) { - encryptionSubkeys = info.validSubkeys + encryptionSubkeys = + info.validSubkeys .filter { it.isEncryptionKey } .filter { info.getKeyFlagsOf(it.keyID).isEmpty() } } @@ -194,13 +209,16 @@ class EncryptionOptions( } } - private fun addRecipientKey(certificate: PGPPublicKeyRing, - key: PGPPublicKey, - wildcardKeyId: Boolean) { + private fun addRecipientKey( + certificate: PGPPublicKeyRing, + key: PGPPublicKey, + wildcardKeyId: Boolean + ) { _encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID)) - addEncryptionMethod(ImplementationFactory.getInstance() - .getPublicKeyKeyEncryptionMethodGenerator(key) - .also { it.setUseWildcardKeyID(wildcardKeyId) }) + addEncryptionMethod( + ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key).also { + it.setUseWildcardKeyID(wildcardKeyId) + }) } /** @@ -210,19 +228,19 @@ class EncryptionOptions( * @return this */ fun addPassphrase(passphrase: Passphrase) = apply { - require(!passphrase.isEmpty) { - "Passphrase MUST NOT be empty." - } - addEncryptionMethod(ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase)) + require(!passphrase.isEmpty) { "Passphrase MUST NOT be empty." } + addEncryptionMethod( + ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase)) } /** * Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message. - * Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase) - * or {@link PGPKeyEncryptionMethodGenerator} (public key). + * Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase) or {@link + * PGPKeyEncryptionMethodGenerator} (public key). * - * This method is intended for advanced users to allow encryption for specific subkeys. - * This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless. + * This method is intended for advanced users to allow encryption for specific subkeys. This can + * come in handy for example if data needs to be encrypted to a subkey that's ignored by + * PGPainless. * * @param encryptionMethod encryption method * @return this @@ -232,10 +250,9 @@ class EncryptionOptions( } /** - * Override the used symmetric encryption algorithm. - * The symmetric encryption algorithm is used to encrypt the message itself, - * while the used symmetric key will be encrypted to all recipients using public key - * cryptography. + * Override the used symmetric encryption algorithm. The symmetric encryption algorithm is used + * to encrypt the message itself, while the used symmetric key will be encrypted to all + * recipients using public key cryptography. * * If the algorithm is not overridden, a suitable algorithm will be negotiated. * @@ -250,10 +267,10 @@ class EncryptionOptions( } /** - * If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will allow encryption - * for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag} subpacket. - * This is a workaround for dealing with legacy keys that have no key flags subpacket but rely on the key algorithm - * type to convey the subkeys use. + * If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will + * allow encryption for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag} + * subpacket. This is a workaround for dealing with legacy keys that have no key flags subpacket + * but rely on the key algorithm type to convey the subkeys use. * * @return this */ @@ -263,20 +280,16 @@ class EncryptionOptions( fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty() - fun interface EncryptionKeySelector { fun selectEncryptionSubkeys(encryptionCapableKeys: List): List } companion object { - @JvmStatic - fun get() = EncryptionOptions() + @JvmStatic fun get() = EncryptionOptions() - @JvmStatic - fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS) + @JvmStatic fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS) - @JvmStatic - fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE) + @JvmStatic fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE) /** * Only encrypt to the first valid encryption capable subkey we stumble upon. @@ -285,7 +298,8 @@ class EncryptionOptions( */ @JvmStatic fun encryptToFirstSubkey() = EncryptionKeySelector { encryptionCapableKeys -> - encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf() } + encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf() + } /** * Encrypt to any valid, encryption capable subkey on the key ring. @@ -293,6 +307,8 @@ class EncryptionOptions( * @return encryption key selector */ @JvmStatic - fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys -> encryptionCapableKeys } + fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys -> + encryptionCapableKeys + } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt index 0e9a40c9..24d89191 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionResult.kt @@ -4,6 +4,7 @@ package org.pgpainless.encryption_signing +import java.util.* import org.bouncycastle.extensions.matches import org.bouncycastle.openpgp.PGPLiteralData import org.bouncycastle.openpgp.PGPPublicKeyRing @@ -13,21 +14,20 @@ import org.pgpainless.algorithm.StreamEncoding import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.util.MultiMap -import java.util.* data class EncryptionResult( - val encryptionAlgorithm: SymmetricKeyAlgorithm, - val compressionAlgorithm: CompressionAlgorithm, - val detachedSignatures: MultiMap, - val recipients: Set, - val fileName: String, - val modificationDate: Date, - val fileEncoding: StreamEncoding + val encryptionAlgorithm: SymmetricKeyAlgorithm, + val compressionAlgorithm: CompressionAlgorithm, + val detachedSignatures: MultiMap, + val recipients: Set, + val fileName: String, + val modificationDate: Date, + val fileEncoding: StreamEncoding ) { /** - * Return true, if the message is marked as for-your-eyes-only. - * This is typically done by setting the filename "_CONSOLE". + * Return true, if the message is marked as for-your-eyes-only. This is typically done by + * setting the filename "_CONSOLE". * * @return is message for your eyes only? */ @@ -48,8 +48,7 @@ data class EncryptionResult( * * @return builder */ - @JvmStatic - fun builder() = Builder() + @JvmStatic fun builder() = Builder() } class Builder { @@ -70,32 +69,35 @@ data class EncryptionResult( _compressionAlgorithm = compressionAlgorithm } - fun setFileName(fileName: String) = apply { - _fileName = fileName - } + fun setFileName(fileName: String) = apply { _fileName = fileName } fun setModificationDate(modificationDate: Date) = apply { _modificationDate = modificationDate } - fun setFileEncoding(encoding: StreamEncoding) = apply { - _encoding = encoding - } + fun setFileEncoding(encoding: StreamEncoding) = apply { _encoding = encoding } fun addRecipient(recipient: SubkeyIdentifier) = apply { (recipients as MutableSet).add(recipient) } - fun addDetachedSignature(signingSubkeyIdentifier: SubkeyIdentifier, detachedSignature: PGPSignature) = apply { - detachedSignatures.put(signingSubkeyIdentifier, detachedSignature) - } + fun addDetachedSignature( + signingSubkeyIdentifier: SubkeyIdentifier, + detachedSignature: PGPSignature + ) = apply { detachedSignatures.put(signingSubkeyIdentifier, detachedSignature) } fun build(): EncryptionResult { checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." } checkNotNull(_compressionAlgorithm) { "Compression algorithm not set." } - return EncryptionResult(_encryptionAlgorithm!!, _compressionAlgorithm!!, detachedSignatures, recipients, - _fileName, _modificationDate, _encoding) + return EncryptionResult( + _encryptionAlgorithm!!, + _compressionAlgorithm!!, + detachedSignatures, + recipients, + _fileName, + _modificationDate, + _encoding) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt index 50397905..f2617c34 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt @@ -4,6 +4,9 @@ package org.pgpainless.encryption_signing +import java.io.BufferedOutputStream +import java.io.IOException +import java.io.OutputStream import org.bouncycastle.bcpg.ArmoredOutputStream import org.bouncycastle.bcpg.BCPGOutputStream import org.bouncycastle.openpgp.PGPCompressedDataGenerator @@ -16,9 +19,6 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.util.ArmoredOutputStreamFactory import org.slf4j.LoggerFactory -import java.io.BufferedOutputStream -import java.io.IOException -import java.io.OutputStream // 1 << 8 causes wrong partial body length encoding // 1 << 9 fixes this. @@ -30,11 +30,13 @@ const val BUFFER_SIZE = 1 shl 9 * depending on its configuration. * * This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream. - * @see Source + * + * @see Source */ class EncryptionStream( - private var outermostStream: OutputStream, - private val options: ProducerOptions, + private var outermostStream: OutputStream, + private val options: ProducerOptions, ) : OutputStream() { private val resultBuilder: EncryptionResult.Builder = EncryptionResult.builder() @@ -66,8 +68,8 @@ class EncryptionStream( outermostStream = BufferedOutputStream(outermostStream) LOGGER.debug("Wrap encryption output in ASCII armor.") - armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options) - .also { outermostStream = it } + armorOutputStream = + ArmoredOutputStreamFactory.get(outermostStream, options).also { outermostStream = it } } @Throws(IOException::class, PGPException::class) @@ -84,9 +86,11 @@ class EncryptionStream( EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let { resultBuilder.setEncryptionAlgorithm(it) LOGGER.debug("Encrypt message using symmetric algorithm $it.") - val encryptedDataGenerator = PGPEncryptedDataGenerator( - ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it) - .apply { setWithIntegrityPacket(true) }) + val encryptedDataGenerator = + PGPEncryptedDataGenerator( + ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it).apply { + setWithIntegrityPacket(true) + }) options.encryptionOptions.encryptionMethods.forEach { m -> encryptedDataGenerator.addMethod(m) } @@ -94,8 +98,11 @@ class EncryptionStream( resultBuilder.addRecipient(r) } - publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)) - .also { stream -> outermostStream = stream } + publicKeyEncryptedStream = + encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)).also { stream + -> + outermostStream = stream + } } } @@ -107,8 +114,10 @@ class EncryptionStream( if (it == CompressionAlgorithm.UNCOMPRESSED) return LOGGER.debug("Compress using $it.") - basicCompressionStream = BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)) - .also { stream -> outermostStream = stream } + basicCompressionStream = + BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)).also { stream -> + outermostStream = stream + } } } @@ -138,12 +147,17 @@ class EncryptionStream( return } - literalDataGenerator = PGPLiteralDataGenerator().also { gen -> - literalDataStream = gen.open(outermostStream, options.encoding.code, options.fileName, - options.modificationDate, ByteArray(BUFFER_SIZE)).also { stream -> - outermostStream = stream + literalDataGenerator = + PGPLiteralDataGenerator().also { gen -> + literalDataStream = + gen.open( + outermostStream, + options.encoding.code, + options.fileName, + options.modificationDate, + ByteArray(BUFFER_SIZE)) + .also { stream -> outermostStream = stream } } - } resultBuilder.apply { setFileName(options.fileName) setModificationDate(options.modificationDate) @@ -156,39 +170,47 @@ class EncryptionStream( } private fun prepareInputEncoding() { - outermostStream = CRLFGeneratorStream( + outermostStream = + CRLFGeneratorStream( // By buffering here, we drastically improve performance - // Reason is that CRLFGeneratorStream only implements write(int), so we need BufferedOutputStream to + // Reason is that CRLFGeneratorStream only implements write(int), so we need + // BufferedOutputStream to // "convert" to write(buf) calls again BufferedOutputStream(outermostStream), if (options.isApplyCRLFEncoding) StreamEncoding.UTF8 else StreamEncoding.BINARY) } private fun collectHashAlgorithmsForCleartextSigning(): Array { - return options.signingOptions?.signingMethods?.values - ?.map { it.hashAlgorithm }?.toSet() - ?.map { it.algorithmId }?.toTypedArray() - ?: arrayOf() + return options.signingOptions + ?.signingMethods + ?.values + ?.map { it.hashAlgorithm } + ?.toSet() + ?.map { it.algorithmId } + ?.toTypedArray() + ?: arrayOf() } - @Throws(IOException::class) - override fun write(data: Int) = outermostStream.write(data) + @Throws(IOException::class) override fun write(data: Int) = outermostStream.write(data) @Throws(IOException::class) override fun write(buffer: ByteArray) = write(buffer, 0, buffer.size) @Throws(IOException::class) - override fun write(buffer: ByteArray, off: Int, len: Int) = outermostStream.write(buffer, off, len) + override fun write(buffer: ByteArray, off: Int, len: Int) = + outermostStream.write(buffer, off, len) - @Throws(IOException::class) - override fun flush() = outermostStream.flush() + @Throws(IOException::class) override fun flush() = outermostStream.flush() @Throws(IOException::class) override fun close() { if (closed) return outermostStream.close() - literalDataStream?.apply { flush(); close() } + literalDataStream?.apply { + flush() + close() + } literalDataGenerator?.close() if (options.isCleartextSigned) { @@ -201,7 +223,7 @@ class EncryptionStream( try { writeSignatures() - } catch (e : PGPException) { + } catch (e: PGPException) { throw IOException("Exception while writing signatures.", e) } @@ -238,14 +260,14 @@ class EncryptionStream( } val result: EncryptionResult - get() = check(closed) { "EncryptionStream must be closed before accessing the result." } + get() = + check(closed) { "EncryptionStream must be closed before accessing the result." } .let { resultBuilder.build() } val isClosed get() = closed companion object { - @JvmStatic - private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java) + @JvmStatic private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.kt index aa3dd58c..ef8641a7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/PGPHashContextContentSignerBuilder.kt @@ -4,39 +4,43 @@ package org.pgpainless.encryption_signing +import java.io.OutputStream +import java.security.MessageDigest import org.bouncycastle.crypto.Digest import org.bouncycastle.crypto.Signer import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder -import java.io.OutputStream -import java.security.MessageDigest abstract class PGPHashContextContentSignerBuilder : PGPContentSignerBuilder { // Copied from BC, required since BCs class is package visible only - internal class SignerOutputStream( - private val signer: Signer - ) : OutputStream() { + internal class SignerOutputStream(private val signer: Signer) : OutputStream() { override fun write(p0: Int) = signer.update(p0.toByte()) + override fun write(b: ByteArray) = signer.update(b, 0, b.size) + override fun write(b: ByteArray, off: Int, len: Int) = signer.update(b, off, len) } - internal class ExistingMessageDigest( - private val digest: MessageDigest - ) : Digest { + internal class ExistingMessageDigest(private val digest: MessageDigest) : Digest { override fun getAlgorithmName(): String = digest.algorithm + override fun getDigestSize(): Int = digest.digestLength + override fun update(b: Byte) = digest.update(b) + override fun update(buf: ByteArray, inOff: Int, len: Int) = digest.update(buf) + override fun doFinal(out: ByteArray, outOff: Int): Int { digest.digest().copyInto(out, outOff) return digestSize } + override fun reset() { // Nope! - // We cannot reset, since BCs signer classes are resetting in their init() methods, which would also reset + // We cannot reset, since BCs signer classes are resetting in their init() methods, + // which would also reset // the messageDigest, losing its state. This would shatter our intention. } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt index 88345fd9..871f8950 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/ProducerOptions.kt @@ -4,15 +4,17 @@ package org.pgpainless.encryption_signing +import java.util.* import org.bouncycastle.openpgp.PGPLiteralData import org.pgpainless.PGPainless import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.StreamEncoding -import java.util.* -class ProducerOptions private constructor( - val encryptionOptions: EncryptionOptions?, - val signingOptions: SigningOptions?) { +class ProducerOptions +private constructor( + val encryptionOptions: EncryptionOptions?, + val signingOptions: SigningOptions? +) { private var _fileName: String = "" private var _modificationDate: Date = PGPLiteralData.NOW @@ -22,14 +24,15 @@ class ProducerOptions private constructor( private var _hideArmorHeaders = false var isDisableAsciiArmorCRC = false - private var _compressionAlgorithmOverride: CompressionAlgorithm = PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm + private var _compressionAlgorithmOverride: CompressionAlgorithm = + PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm private var asciiArmor = true private var _comment: String? = null private var _version: String? = null /** - * Specify, whether the result of the encryption/signing operation shall be ascii armored. - * The default value is true. + * Specify, whether the result of the encryption/signing operation shall be ascii armored. The + * default value is true. * * @param asciiArmor ascii armor * @return builder @@ -50,19 +53,15 @@ class ProducerOptions private constructor( get() = asciiArmor /** - * Set the comment header in ASCII armored output. - * The default value is null, which means no comment header is added. - * Multiline comments are possible using '\\n'. - *
- * Note: If a default header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment], - * then both comments will be written to the produced ASCII armor. + * Set the comment header in ASCII armored output. The default value is null, which means no + * comment header is added. Multiline comments are possible using '\\n'.
Note: If a default + * header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment], then + * both comments will be written to the produced ASCII armor. * * @param comment comment header text * @return builder */ - fun setComment(comment: String?) = apply { - _comment = comment - } + fun setComment(comment: String?) = apply { _comment = comment } /** * Return comment set for header in ascii armored output. @@ -80,18 +79,15 @@ class ProducerOptions private constructor( fun hasComment() = _comment != null /** - * Set the version header in ASCII armored output. - * The default value is null, which means no version header is added. - *
- * Note: If the value is non-null, then this method overrides the default version header set using + * Set the version header in ASCII armored output. The default value is null, which means no + * version header is added.
Note: If the value is non-null, then this method overrides the + * default version header set using * [org.pgpainless.util.ArmoredOutputStreamFactory.setVersionInfo]. * * @param version version header, or null for no version info. * @return builder */ - fun setVersion(version: String?) = apply { - _version = version - } + fun setVersion(version: String?) = apply { _version = version } /** * Return the version info header in ascii armored output. @@ -128,15 +124,13 @@ class ProducerOptions private constructor( get() = cleartextSigned /** - * Set the name of the encrypted file. - * Note: This option cannot be used simultaneously with [setForYourEyesOnly]. + * Set the name of the encrypted file. Note: This option cannot be used simultaneously with + * [setForYourEyesOnly]. * * @param fileName name of the encrypted file * @return this */ - fun setFileName(fileName: String) = apply { - _fileName = fileName - } + fun setFileName(fileName: String) = apply { _fileName = fileName } /** * Return the encrypted files name. @@ -147,17 +141,15 @@ class ProducerOptions private constructor( get() = _fileName /** - * Mark the encrypted message as for-your-eyes-only by setting a special file name. - * Note: Therefore this method cannot be used simultaneously with [setFileName]. + * Mark the encrypted message as for-your-eyes-only by setting a special file name. Note: + * Therefore this method cannot be used simultaneously with [setFileName]. * * @return this - * @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this special filename in - * newly generated literal data packets + * @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this + * special filename in newly generated literal data packets */ @Deprecated("Signaling using special file name is discouraged.") - fun setForYourEyesOnly() = apply { - _fileName = PGPLiteralData.CONSOLE - } + fun setForYourEyesOnly() = apply { _fileName = PGPLiteralData.CONSOLE } /** * Set the modification date of the encrypted file. @@ -165,9 +157,7 @@ class ProducerOptions private constructor( * @param modificationDate Modification date of the encrypted file. * @return this */ - fun setModificationDate(modificationDate: Date) = apply { - _modificationDate = modificationDate - } + fun setModificationDate(modificationDate: Date) = apply { _modificationDate = modificationDate } /** * Return the modification date of the encrypted file. @@ -178,40 +168,32 @@ class ProducerOptions private constructor( get() = _modificationDate /** - * Set format metadata field of the literal data packet. - * Defaults to [StreamEncoding.BINARY]. - *
- * This does not change the encoding of the wrapped data itself. - * To apply CR/LF encoding to your input data before processing, use [applyCRLFEncoding] instead. - * - * @see RFC4880 §5.9. Literal Data Packet + * Set format metadata field of the literal data packet. Defaults to [StreamEncoding.BINARY]. + *
This does not change the encoding of the wrapped data itself. To apply CR/LF encoding to + * your input data before processing, use [applyCRLFEncoding] instead. * * @param encoding encoding * @return this - * - * @deprecated options other than the default value of {@link StreamEncoding#BINARY} are discouraged. + * @see RFC4880 §5.9. + * Literal Data Packet + * @deprecated options other than the default value of {@link StreamEncoding#BINARY} are + * discouraged. */ @Deprecated("Options other than BINARY are discouraged.") - fun setEncoding(encoding: StreamEncoding) = apply { - encodingField = encoding - } + fun setEncoding(encoding: StreamEncoding) = apply { encodingField = encoding } val encoding: StreamEncoding get() = encodingField /** - * Apply special encoding of line endings to the input data. - * By default, this is disabled, which means that the data is not altered. - *
- * Enabling it will change the line endings to CR/LF. - * Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will result in - * the identity "decrypt(encrypt(data)) == data == verify(sign(data))". + * Apply special encoding of line endings to the input data. By default, this is disabled, which + * means that the data is not altered.
Enabling it will change the line endings to CR/LF. + * Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will + * result in the identity "decrypt(encrypt(data)) == data == verify(sign(data))". * * @return this */ - fun applyCRLFEncoding() = apply { - applyCRLFEncoding = true - } + fun applyCRLFEncoding() = apply { applyCRLFEncoding = true } /** * Return the input encoding that will be applied before signing / encryption. @@ -239,9 +221,8 @@ class ProducerOptions private constructor( /** * If set to `true`, armor headers like version or comments will be omitted from armored output. - * By default, armor headers are not hidden. - * Note: If comments are added via [setComment], those are not omitted, even if - * [hideArmorHeaders] is set to `true`. + * By default, armor headers are not hidden. Note: If comments are added via [setComment], those + * are not omitted, even if [hideArmorHeaders] is set to `true`. * * @param hideArmorHeaders true or false * @return this @@ -260,7 +241,7 @@ class ProducerOptions private constructor( */ @JvmStatic fun signAndEncrypt(encryptionOptions: EncryptionOptions, signingOptions: SigningOptions) = - ProducerOptions(encryptionOptions, signingOptions) + ProducerOptions(encryptionOptions, signingOptions) /** * Sign some data without encryption. @@ -268,8 +249,7 @@ class ProducerOptions private constructor( * @param signingOptions signing options * @return builder */ - @JvmStatic - fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions) + @JvmStatic fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions) /** * Encrypt some data without signing. @@ -281,12 +261,10 @@ class ProducerOptions private constructor( fun encrypt(encryptionOptions: EncryptionOptions) = ProducerOptions(encryptionOptions, null) /** - * Only wrap the data in an OpenPGP packet. - * No encryption or signing will be applied. + * Only wrap the data in an OpenPGP packet. No encryption or signing will be applied. * * @return builder */ - @JvmStatic - fun noEncryptionNoSigning() = ProducerOptions(null, null) + @JvmStatic fun noEncryptionNoSigning() = ProducerOptions(null, null) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SignatureGenerationStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SignatureGenerationStream.kt index d0f04851..b2d53dcd 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SignatureGenerationStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SignatureGenerationStream.kt @@ -6,23 +6,20 @@ package org.pgpainless.encryption_signing import java.io.OutputStream -/** - * OutputStream which has the task of updating signature generators for written data. - */ +/** OutputStream which has the task of updating signature generators for written data. */ class SignatureGenerationStream( - private val wrapped: OutputStream, - private val options: SigningOptions? + private val wrapped: OutputStream, + private val options: SigningOptions? ) : OutputStream() { override fun close() = wrapped.close() + override fun flush() = wrapped.flush() override fun write(b: Int) { wrapped.write(b) options?.run { - signingMethods.values.forEach { - it.signatureGenerator.update((b and 0xff).toByte()) - } + signingMethods.values.forEach { it.signatureGenerator.update((b and 0xff).toByte()) } } } @@ -30,10 +27,6 @@ class SignatureGenerationStream( override fun write(b: ByteArray, off: Int, len: Int) { wrapped.write(b, off, len) - options?.run { - signingMethods.values.forEach { - it.signatureGenerator.update(b, off, len) - } - } + options?.run { signingMethods.values.forEach { it.signatureGenerator.update(b, off, len) } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt index 512e074b..089b2d28 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt @@ -4,6 +4,7 @@ package org.pgpainless.encryption_signing +import java.util.* import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.pgpainless.PGPainless.Companion.getPolicy @@ -22,7 +23,6 @@ import org.pgpainless.policy.Policy import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper -import java.util.* class SigningOptions { @@ -34,10 +34,10 @@ class SigningOptions { get() = _hashAlgorithmOverride /** - * Override hash algorithm negotiation by dictating which hash algorithm needs to be used. - * If no override has been set, an acceptable algorithm will be negotiated instead. - * Note: To override the hash algorithm for signing, call this method *before* calling - * [addInlineSignature] or [addDetachedSignature]. + * Override hash algorithm negotiation by dictating which hash algorithm needs to be used. If no + * override has been set, an acceptable algorithm will be negotiated instead. Note: To override + * the hash algorithm for signing, call this method *before* calling [addInlineSignature] or + * [addDetachedSignature]. * * @param hashAlgorithmOverride override hash algorithm * @return this @@ -55,9 +55,7 @@ class SigningOptions { * @param evaluationDate new evaluation date * @return this */ - fun setEvaluationDate(evaluationDate: Date) = apply { - _evaluationDate = evaluationDate - } + fun setEvaluationDate(evaluationDate: Date) = apply { _evaluationDate = evaluationDate } /** * Sign the message using an inline signature made by the provided signing key. @@ -65,14 +63,15 @@ class SigningOptions { * @param signingKeyProtector protector to unlock the signing key * @param signingKey key ring containing the signing key * @return this - * * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be unlocked or a signing method cannot be created */ @Throws(KeyException::class, PGPException::class) - fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = apply { - addInlineSignature(signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT) - } + fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = + apply { + addInlineSignature( + signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT) + } /** * Add inline signatures with all secret key rings in the provided secret key ring collection. @@ -81,44 +80,41 @@ class SigningOptions { * @param signingKeys collection of signing keys * @param signatureType type of signature (binary, canonical text) * @return this - * * @throws KeyException if something is wrong with any of the keys - * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created + * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be + * created */ @Throws(KeyException::class, PGPException::class) - fun addInlineSignatures(signingKeyProtector: SecretKeyRingProtector, - signingKeys: Iterable, - signatureType: DocumentSignatureType) = apply { - signingKeys.forEach { - addInlineSignature(signingKeyProtector, it, null, signatureType) - } + fun addInlineSignatures( + signingKeyProtector: SecretKeyRingProtector, + signingKeys: Iterable, + signatureType: DocumentSignatureType + ) = apply { + signingKeys.forEach { addInlineSignature(signingKeyProtector, it, null, signatureType) } } - /** - * Add an inline-signature. - * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use - * of one-pass-signature packets. + * Add an inline-signature. Inline signatures are being embedded into the message itself and can + * be processed in one pass, thanks to the use of one-pass-signature packets. * * @param signingKeyProtector decryptor to unlock the signing secret key * @param signingKey signing key * @param signatureType type of signature (binary, canonical text) * @return this - * * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be unlocked or the signing method cannot be created */ @Throws(KeyException::class, PGPException::class) - fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, - signingKey: PGPSecretKeyRing, - signatureType: DocumentSignatureType) = apply { - addInlineSignature(signingKeyProtector, signingKey, null, signatureType) - } + fun addInlineSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + signatureType: DocumentSignatureType + ) = apply { addInlineSignature(signingKeyProtector, signingKey, null, signatureType) } /** - * Add an inline-signature. - * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use - * of one-pass-signature packets. + * Add an inline-signature. Inline signatures are being embedded into the message itself and can + * be processed in one pass, thanks to the use of one-pass-signature packets. + * *

* This method uses the passed in user-id to select user-specific hash algorithms. * @@ -126,27 +122,28 @@ class SigningOptions { * @param signingKey signing key * @param userId user-id of the signer * @param signatureType signature type (binary, canonical text) - * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature + * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the + * signature * @return this - * * @throws KeyException if the key is invalid * @throws PGPException if the key cannot be unlocked or the signing method cannot be created */ @Throws(KeyException::class, PGPException::class) @JvmOverloads - fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, - signingKey: PGPSecretKeyRing, - userId: CharSequence? = null, - signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, - subpacketsCallback: Callback? = null) = apply { + fun addInlineSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + userId: CharSequence? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null + ) = apply { val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) if (userId != null && !keyRingInfo.isUserIdValid(userId)) { throw UnboundUserIdException( - of(signingKey), - userId.toString(), - keyRingInfo.getLatestUserIdCertification(userId), - keyRingInfo.getUserIdRevocation(userId) - ) + of(signingKey), + userId.toString(), + keyRingInfo.getLatestUserIdCertification(userId), + keyRingInfo.getUserIdRevocation(userId)) } val signingPubKeys = keyRingInfo.signingSubkeys @@ -155,14 +152,16 @@ class SigningOptions { } for (signingPubKey in signingPubKeys) { - val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + val signingSecKey: PGPSecretKey = + signingKey.getSecretKey(signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val hashAlgorithms = - if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) - else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) + else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) - addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) + addSigningMethod( + signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) } } @@ -175,17 +174,22 @@ class SigningOptions { * @param signatureType signature type * @param subpacketsCallback callback to modify the signatures subpackets * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key + * @throws PGPException if the secret key cannot be unlocked or if no signing method can be + * created. + * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any + * signing-capable subkeys + * @throws KeyException.MissingSecretKeyException if the key ring does not contain the + * identified secret key */ @Throws(KeyException::class, PGPException::class) @JvmOverloads - fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, - signingKey: PGPSecretKeyRing, - keyId: Long, - signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, - subpacketsCallback: Callback? = null) = apply { + fun addInlineSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + keyId: Long, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null + ) = apply { val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) val signingPubKeys = keyRingInfo.signingSubkeys if (signingPubKeys.isEmpty()) { @@ -197,12 +201,14 @@ class SigningOptions { continue } - val signingSecKey = signingKey.getSecretKey(signingPubKey.keyID) + val signingSecKey = + signingKey.getSecretKey(signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) val signingSubkey = signingSecKey.unlock(signingKeyProtector) val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) - addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) + addSigningMethod( + signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) return this } throw MissingSecretKeyException(of(signingKey), keyId) @@ -215,45 +221,44 @@ class SigningOptions { * @param signingKeys collection of signing key rings * @param signatureType type of the signature (binary, canonical text) * @return this - * * @throws KeyException if something is wrong with any of the keys - * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created + * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing + * method cannot be created */ @Throws(KeyException::class, PGPException::class) - fun addDetachedSignatures(signingKeyProtector: SecretKeyRingProtector, - signingKeys: Iterable, - signatureType: DocumentSignatureType) = apply { - signingKeys.forEach { - addDetachedSignature(signingKeyProtector, it, null, signatureType) - } + fun addDetachedSignatures( + signingKeyProtector: SecretKeyRingProtector, + signingKeys: Iterable, + signatureType: DocumentSignatureType + ) = apply { + signingKeys.forEach { addDetachedSignature(signingKeyProtector, it, null, signatureType) } } /** - * Create a detached signature. - * Detached signatures are not being added into the PGP message itself. - * Instead, they can be distributed separately to the message. - * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). + * Create a detached signature. Detached signatures are not being added into the PGP message + * itself. Instead, they can be distributed separately to the message. Detached signatures are + * useful if the data that is being signed shall not be modified (e.g. when signing a file). * * @param signingKeyProtector decryptor to unlock the secret signing key * @param signingKey signing key * @param signatureType type of data that is signed (binary, canonical text) * @return this - * * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created + * @throws PGPException if the key cannot be validated or unlocked, or if no signature method + * can be created */ @Throws(KeyException::class, PGPException::class) - fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, - signingKey: PGPSecretKeyRing, - signatureType: DocumentSignatureType) = apply { - addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) - } + fun addDetachedSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + signatureType: DocumentSignatureType + ) = apply { addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) } /** - * Create a detached signature. - * Detached signatures are not being added into the PGP message itself. - * Instead, they can be distributed separately to the message. - * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). + * Create a detached signature. Detached signatures are not being added into the PGP message + * itself. Instead, they can be distributed separately to the message. Detached signatures are + * useful if the data that is being signed shall not be modified (e.g. when signing a file). + * *

* This method uses the passed in user-id to select user-specific hash algorithms. * @@ -263,25 +268,26 @@ class SigningOptions { * @param signatureType type of data that is signed (binary, canonical text) * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature * @return this - * * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created + * @throws PGPException if the key cannot be validated or unlocked, or if no signature method + * can be created */ @JvmOverloads @Throws(KeyException::class, PGPException::class) - fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, - signingKey: PGPSecretKeyRing, - userId: String? = null, - signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, - subpacketCallback: Callback? = null) = apply { + fun addDetachedSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + userId: String? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketCallback: Callback? = null + ) = apply { val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) if (userId != null && !keyRingInfo.isUserIdValid(userId)) { throw UnboundUserIdException( - of(signingKey), - userId.toString(), - keyRingInfo.getLatestUserIdCertification(userId), - keyRingInfo.getUserIdRevocation(userId) - ) + of(signingKey), + userId.toString(), + keyRingInfo.getLatestUserIdCertification(userId), + keyRingInfo.getUserIdRevocation(userId)) } val signingPubKeys = keyRingInfo.signingSubkeys @@ -290,14 +296,16 @@ class SigningOptions { } for (signingPubKey in signingPubKeys) { - val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + val signingSecKey: PGPSecretKey = + signingKey.getSecretKey(signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val hashAlgorithms = - if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) - else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) + else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) - addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback) + addSigningMethod( + signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback) } } @@ -310,17 +318,22 @@ class SigningOptions { * @param signatureType signature type * @param subpacketsCallback callback to modify the signatures subpackets * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key + * @throws PGPException if the secret key cannot be unlocked or if no signing method can be + * created. + * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any + * signing-capable subkeys + * @throws KeyException.MissingSecretKeyException if the key ring does not contain the + * identified secret key */ @Throws(KeyException::class, PGPException::class) @JvmOverloads - fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, - signingKey: PGPSecretKeyRing, - keyId: Long, - signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, - subpacketsCallback: Callback? = null) = apply { + fun addDetachedSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + keyId: Long, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null + ) = apply { val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) val signingPubKeys = keyRingInfo.signingSubkeys @@ -330,12 +343,20 @@ class SigningOptions { for (signingPubKey in signingPubKeys) { if (signingPubKey.keyID == keyId) { - val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + val signingSecKey: PGPSecretKey = + signingKey.getSecretKey(signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) - val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) - addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketsCallback) + val hashAlgorithm: HashAlgorithm = + negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod( + signingKey, + signingSubkey, + hashAlgorithm, + signatureType, + true, + subpacketsCallback) return this } } @@ -343,26 +364,30 @@ class SigningOptions { throw MissingSecretKeyException(of(signingKey), keyId) } - private fun addSigningMethod(signingKey: PGPSecretKeyRing, - signingSubkey: PGPPrivateKey, - hashAlgorithm: HashAlgorithm, - signatureType: DocumentSignatureType, - detached: Boolean, - subpacketCallback: Callback? = null) { + private fun addSigningMethod( + signingKey: PGPSecretKeyRing, + signingSubkey: PGPPrivateKey, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType, + detached: Boolean, + subpacketCallback: Callback? = null + ) { val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID) val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID) val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm) val bitStrength = signingSecretKey.publicKey.bitStrength if (!getPolicy().publicKeyAlgorithmPolicy.isAcceptable(publicKeyAlgorithm, bitStrength)) { throw UnacceptableSigningKeyException( - PublicKeyAlgorithmPolicyException( - of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength)) + PublicKeyAlgorithmPolicyException( + of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength)) } - val generator: PGPSignatureGenerator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType) + val generator: PGPSignatureGenerator = + createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType) // Subpackets - val hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey) + val hashedSubpackets = + SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey) val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets() if (subpacketCallback != null) { subpacketCallback.modifyHashedSubpackets(hashedSubpackets) @@ -372,80 +397,87 @@ class SigningOptions { generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets)) val signingMethod = - if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm) - else SigningMethod.inlineSignature(generator, hashAlgorithm) + if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm) + else SigningMethod.inlineSignature(generator, hashAlgorithm) (signingMethods as MutableMap)[signingKeyIdentifier] = signingMethod } /** * Negotiate, which hash algorithm to use. * - * - * This method gives the highest priority to the algorithm override, which can be set via [.overrideHashAlgorithm]. - * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm. - * Lastly, should no acceptable algorithm be found, the [Policies][Policy] default signature hash algorithm is - * used as a fallback. + * This method gives the highest priority to the algorithm override, which can be set via + * [.overrideHashAlgorithm]. After that, the signing keys hash algorithm preferences are + * iterated to find the first acceptable algorithm. Lastly, should no acceptable algorithm be + * found, the [Policies][Policy] default signature hash algorithm is used as a fallback. * * @param preferences preferences * @param policy policy * @return selected hash algorithm */ - private fun negotiateHashAlgorithm(preferences: Set, - policy: Policy): HashAlgorithm { - return _hashAlgorithmOverride ?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences) + private fun negotiateHashAlgorithm( + preferences: Set, + policy: Policy + ): HashAlgorithm { + return _hashAlgorithmOverride + ?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences) } @Throws(PGPException::class) - private fun createSignatureGenerator(privateKey: PGPPrivateKey, - hashAlgorithm: HashAlgorithm, - signatureType: DocumentSignatureType): PGPSignatureGenerator { + private fun createSignatureGenerator( + privateKey: PGPPrivateKey, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType + ): PGPSignatureGenerator { return ImplementationFactory.getInstance() - .getPGPContentSignerBuilder(privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId) - .let { csb -> - PGPSignatureGenerator(csb).also { it.init(signatureType.signatureType.code, privateKey) } + .getPGPContentSignerBuilder( + privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId) + .let { csb -> + PGPSignatureGenerator(csb).also { + it.init(signatureType.signatureType.code, privateKey) } + } } - companion object { - @JvmStatic - fun get() = SigningOptions() + @JvmStatic fun get() = SigningOptions() } - /** - * A method of signing. - */ - class SigningMethod private constructor( - val signatureGenerator: PGPSignatureGenerator, - val isDetached: Boolean, - val hashAlgorithm: HashAlgorithm + /** A method of signing. */ + class SigningMethod + private constructor( + val signatureGenerator: PGPSignatureGenerator, + val isDetached: Boolean, + val hashAlgorithm: HashAlgorithm ) { companion object { /** - * Inline-signature method. - * The resulting signature will be written into the message itself, together with a one-pass-signature packet. + * Inline-signature method. The resulting signature will be written into the message + * itself, together with a one-pass-signature packet. * * @param signatureGenerator signature generator * @param hashAlgorithm hash algorithm used to generate the signature * @return inline signing method */ @JvmStatic - fun inlineSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) = - SigningMethod(signatureGenerator, false, hashAlgorithm) + fun inlineSignature( + signatureGenerator: PGPSignatureGenerator, + hashAlgorithm: HashAlgorithm + ) = SigningMethod(signatureGenerator, false, hashAlgorithm) /** - * Detached signing method. - * The resulting signature will not be added to the message, and instead can be distributed separately - * to the signed message. + * Detached signing method. The resulting signature will not be added to the message, + * and instead can be distributed separately to the signed message. * * @param signatureGenerator signature generator * @param hashAlgorithm hash algorithm used to generate the signature * @return detached signing method */ @JvmStatic - fun detachedSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) = - SigningMethod(signatureGenerator, true, hashAlgorithm) + fun detachedSignature( + signatureGenerator: PGPSignatureGenerator, + hashAlgorithm: HashAlgorithm + ) = SigningMethod(signatureGenerator, true, hashAlgorithm) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/BcImplementationFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/BcImplementationFactory.kt index fec3a550..dcf594ea 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/BcImplementationFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/BcImplementationFactory.kt @@ -4,6 +4,9 @@ package org.pgpainless.implementation +import java.io.InputStream +import java.security.KeyPair +import java.util.* import org.bouncycastle.crypto.AsymmetricCipherKeyPair import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.bc.BcPGPObjectFactory @@ -27,70 +30,85 @@ import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.util.Passphrase -import java.io.InputStream -import java.security.KeyPair -import java.util.* class BcImplementationFactory : ImplementationFactory() { - override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider = BcPGPDigestCalculatorProvider() - override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider = BcPGPContentVerifierBuilderProvider() + override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider = + BcPGPDigestCalculatorProvider() + override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider = + BcPGPContentVerifierBuilderProvider() override val keyFingerprintCalculator: BcKeyFingerprintCalculator = BcKeyFingerprintCalculator() - override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, - digestCalculator: PGPDigestCalculator, - passphrase: Passphrase): PBESecretKeyEncryptor = - BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator) - .build(passphrase.getChars()) + override fun getPBESecretKeyEncryptor( + symmetricKeyAlgorithm: SymmetricKeyAlgorithm, + digestCalculator: PGPDigestCalculator, + passphrase: Passphrase + ): PBESecretKeyEncryptor = + BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator) + .build(passphrase.getChars()) - override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, - hashAlgorithm: HashAlgorithm, - s2kCount: Int, - passphrase: Passphrase): PBESecretKeyEncryptor = - BcPBESecretKeyEncryptorBuilder( - encryptionAlgorithm.algorithmId, - getPGPDigestCalculator(hashAlgorithm), - s2kCount) - .build(passphrase.getChars()) + override fun getPBESecretKeyEncryptor( + encryptionAlgorithm: SymmetricKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + s2kCount: Int, + passphrase: Passphrase + ): PBESecretKeyEncryptor = + BcPBESecretKeyEncryptorBuilder( + encryptionAlgorithm.algorithmId, getPGPDigestCalculator(hashAlgorithm), s2kCount) + .build(passphrase.getChars()) override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor = - BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider) - .build(passphrase.getChars()) + BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider).build(passphrase.getChars()) - override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder = - BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) + override fun getPGPContentSignerBuilder( + keyAlgorithm: Int, + hashAlgorithm: Int + ): PGPContentSignerBuilder = BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory = - BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider) + BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider) - override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory = - BcPublicKeyDataDecryptorFactory(privateKey) + override fun getPublicKeyDataDecryptorFactory( + privateKey: PGPPrivateKey + ): PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(privateKey) - override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory = - BcSessionKeyDataDecryptorFactory(sessionKey) + override fun getSessionKeyDataDecryptorFactory( + sessionKey: PGPSessionKey + ): SessionKeyDataDecryptorFactory = BcSessionKeyDataDecryptorFactory(sessionKey) - override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator = - BcPublicKeyKeyEncryptionMethodGenerator(key) + override fun getPublicKeyKeyEncryptionMethodGenerator( + key: PGPPublicKey + ): PublicKeyKeyEncryptionMethodGenerator = BcPublicKeyKeyEncryptionMethodGenerator(key) - override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator = - BcPBEKeyEncryptionMethodGenerator(passphrase.getChars()) + override fun getPBEKeyEncryptionMethodGenerator( + passphrase: Passphrase + ): PBEKeyEncryptionMethodGenerator = BcPBEKeyEncryptionMethodGenerator(passphrase.getChars()) override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder = - BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm) + BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm) - override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair = - BcPGPKeyPair( - publicKeyAlgorithm.algorithmId, - jceToBcKeyPair(publicKeyAlgorithm, keyPair, creationDate), - creationDate) + override fun getPGPKeyPair( + publicKeyAlgorithm: PublicKeyAlgorithm, + keyPair: KeyPair, + creationDate: Date + ): PGPKeyPair = + BcPGPKeyPair( + publicKeyAlgorithm.algorithmId, + jceToBcKeyPair(publicKeyAlgorithm, keyPair, creationDate), + creationDate) - override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory = BcPGPObjectFactory(inputStream) + override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory = + BcPGPObjectFactory(inputStream) - private fun jceToBcKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, - keyPair: KeyPair, - creationDate: Date): AsymmetricCipherKeyPair = - BcPGPKeyConverter().let { converter -> - JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair -> - AsymmetricCipherKeyPair(converter.getPublicKey(pair.publicKey), converter.getPrivateKey(pair.privateKey)) - } + private fun jceToBcKeyPair( + publicKeyAlgorithm: PublicKeyAlgorithm, + keyPair: KeyPair, + creationDate: Date + ): AsymmetricCipherKeyPair = + BcPGPKeyConverter().let { converter -> + JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair -> + AsymmetricCipherKeyPair( + converter.getPublicKey(pair.publicKey), + converter.getPrivateKey(pair.privateKey)) } -} \ No newline at end of file + } +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/ImplementationFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/ImplementationFactory.kt index 5ae653b4..58478379 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/ImplementationFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/ImplementationFactory.kt @@ -4,6 +4,9 @@ package org.pgpainless.implementation +import java.io.InputStream +import java.security.KeyPair +import java.util.* import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.* import org.pgpainless.algorithm.HashAlgorithm @@ -11,18 +14,13 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.util.Passphrase import org.pgpainless.util.SessionKey -import java.io.InputStream -import java.security.KeyPair -import java.util.* abstract class ImplementationFactory { companion object { - @JvmStatic - private var instance: ImplementationFactory = BcImplementationFactory() + @JvmStatic private var instance: ImplementationFactory = BcImplementationFactory() - @JvmStatic - fun getInstance() = instance + @JvmStatic fun getInstance() = instance @JvmStatic fun setFactoryImplementation(implementation: ImplementationFactory) = apply { @@ -38,56 +36,82 @@ abstract class ImplementationFactory { get() = getPGPDigestCalculator(HashAlgorithm.SHA1) @Throws(PGPException::class) - abstract fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, - digestCalculator: PGPDigestCalculator, - passphrase: Passphrase): PBESecretKeyEncryptor + abstract fun getPBESecretKeyEncryptor( + symmetricKeyAlgorithm: SymmetricKeyAlgorithm, + digestCalculator: PGPDigestCalculator, + passphrase: Passphrase + ): PBESecretKeyEncryptor @Throws(PGPException::class) abstract fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor @Throws(PGPException::class) - abstract fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, hashAlgorithm: HashAlgorithm, - s2kCount: Int, passphrase: Passphrase): PBESecretKeyEncryptor + abstract fun getPBESecretKeyEncryptor( + encryptionAlgorithm: SymmetricKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + s2kCount: Int, + passphrase: Passphrase + ): PBESecretKeyEncryptor fun getPGPDigestCalculator(hashAlgorithm: HashAlgorithm): PGPDigestCalculator = - getPGPDigestCalculator(hashAlgorithm.algorithmId) + getPGPDigestCalculator(hashAlgorithm.algorithmId) fun getPGPDigestCalculator(hashAlgorithm: Int): PGPDigestCalculator = - pgpDigestCalculatorProvider.get(hashAlgorithm) + pgpDigestCalculatorProvider.get(hashAlgorithm) - fun getPGPContentSignerBuilder(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm): PGPContentSignerBuilder = - getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId) + fun getPGPContentSignerBuilder( + keyAlgorithm: PublicKeyAlgorithm, + hashAlgorithm: HashAlgorithm + ): PGPContentSignerBuilder = + getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId) - abstract fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder + abstract fun getPGPContentSignerBuilder( + keyAlgorithm: Int, + hashAlgorithm: Int + ): PGPContentSignerBuilder @Throws(PGPException::class) abstract fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory - abstract fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory + abstract fun getPublicKeyDataDecryptorFactory( + privateKey: PGPPrivateKey + ): PublicKeyDataDecryptorFactory fun getSessionKeyDataDecryptorFactory(sessionKey: SessionKey): SessionKeyDataDecryptorFactory = - getSessionKeyDataDecryptorFactory(PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key)) + getSessionKeyDataDecryptorFactory( + PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key)) - abstract fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory + abstract fun getSessionKeyDataDecryptorFactory( + sessionKey: PGPSessionKey + ): SessionKeyDataDecryptorFactory - abstract fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator + abstract fun getPublicKeyKeyEncryptionMethodGenerator( + key: PGPPublicKey + ): PublicKeyKeyEncryptionMethodGenerator - abstract fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator + abstract fun getPBEKeyEncryptionMethodGenerator( + passphrase: Passphrase + ): PBEKeyEncryptionMethodGenerator - fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: SymmetricKeyAlgorithm): PGPDataEncryptorBuilder = - getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId) + fun getPGPDataEncryptorBuilder( + symmetricKeyAlgorithm: SymmetricKeyAlgorithm + ): PGPDataEncryptorBuilder = getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId) abstract fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder @Throws(PGPException::class) - abstract fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair + abstract fun getPGPKeyPair( + publicKeyAlgorithm: PublicKeyAlgorithm, + keyPair: KeyPair, + creationDate: Date + ): PGPKeyPair fun getPGPObjectFactory(bytes: ByteArray): PGPObjectFactory = - getPGPObjectFactory(bytes.inputStream()) + getPGPObjectFactory(bytes.inputStream()) abstract fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory override fun toString(): String { return javaClass.simpleName } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/JceImplementationFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/JceImplementationFactory.kt index 9a4d8e41..865f1e0d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/JceImplementationFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/implementation/JceImplementationFactory.kt @@ -4,6 +4,9 @@ package org.pgpainless.implementation +import java.io.InputStream +import java.security.KeyPair +import java.util.* import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.* import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator @@ -24,79 +27,86 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.provider.ProviderFactory import org.pgpainless.util.Passphrase -import java.io.InputStream -import java.security.KeyPair -import java.util.* class JceImplementationFactory : ImplementationFactory() { override val pgpDigestCalculatorProvider: PGPDigestCalculatorProvider = - JcaPGPDigestCalculatorProviderBuilder() - .setProvider(ProviderFactory.provider) - .build() + JcaPGPDigestCalculatorProviderBuilder().setProvider(ProviderFactory.provider).build() override val pgpContentVerifierBuilderProvider: PGPContentVerifierBuilderProvider = - JcaPGPContentVerifierBuilderProvider() - .setProvider(ProviderFactory.provider) + JcaPGPContentVerifierBuilderProvider().setProvider(ProviderFactory.provider) override val keyFingerprintCalculator: KeyFingerPrintCalculator = - JcaKeyFingerprintCalculator() - .setProvider(ProviderFactory.provider) + JcaKeyFingerprintCalculator().setProvider(ProviderFactory.provider) - override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, - digestCalculator: PGPDigestCalculator, - passphrase: Passphrase): PBESecretKeyEncryptor = - JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator) - .setProvider(ProviderFactory.provider) - .build(passphrase.getChars()) + override fun getPBESecretKeyEncryptor( + symmetricKeyAlgorithm: SymmetricKeyAlgorithm, + digestCalculator: PGPDigestCalculator, + passphrase: Passphrase + ): PBESecretKeyEncryptor = + JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator) + .setProvider(ProviderFactory.provider) + .build(passphrase.getChars()) - override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, - hashAlgorithm: HashAlgorithm, - s2kCount: Int, - passphrase: Passphrase): PBESecretKeyEncryptor = - JcePBESecretKeyEncryptorBuilder( - encryptionAlgorithm.algorithmId, - getPGPDigestCalculator(hashAlgorithm), - s2kCount) - .setProvider(ProviderFactory.provider) - .build(passphrase.getChars()) + override fun getPBESecretKeyEncryptor( + encryptionAlgorithm: SymmetricKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + s2kCount: Int, + passphrase: Passphrase + ): PBESecretKeyEncryptor = + JcePBESecretKeyEncryptorBuilder( + encryptionAlgorithm.algorithmId, getPGPDigestCalculator(hashAlgorithm), s2kCount) + .setProvider(ProviderFactory.provider) + .build(passphrase.getChars()) override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor = - JcePBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider) - .setProvider(ProviderFactory.provider) - .build(passphrase.getChars()) + JcePBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider) + .setProvider(ProviderFactory.provider) + .build(passphrase.getChars()) - override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder = - JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) - .setProvider(ProviderFactory.provider) + override fun getPGPContentSignerBuilder( + keyAlgorithm: Int, + hashAlgorithm: Int + ): PGPContentSignerBuilder = + JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) + .setProvider(ProviderFactory.provider) override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory = - JcePBEDataDecryptorFactoryBuilder(pgpDigestCalculatorProvider) - .setProvider(ProviderFactory.provider) - .build(passphrase.getChars()) + JcePBEDataDecryptorFactoryBuilder(pgpDigestCalculatorProvider) + .setProvider(ProviderFactory.provider) + .build(passphrase.getChars()) - override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory = - JcePublicKeyDataDecryptorFactoryBuilder() - .setProvider(ProviderFactory.provider) - .build(privateKey) + override fun getPublicKeyDataDecryptorFactory( + privateKey: PGPPrivateKey + ): PublicKeyDataDecryptorFactory = + JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(ProviderFactory.provider) + .build(privateKey) - override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory = - JceSessionKeyDataDecryptorFactoryBuilder() - .setProvider(ProviderFactory.provider) - .build(sessionKey) + override fun getSessionKeyDataDecryptorFactory( + sessionKey: PGPSessionKey + ): SessionKeyDataDecryptorFactory = + JceSessionKeyDataDecryptorFactoryBuilder() + .setProvider(ProviderFactory.provider) + .build(sessionKey) - override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator = - JcePublicKeyKeyEncryptionMethodGenerator(key) - .setProvider(ProviderFactory.provider) + override fun getPublicKeyKeyEncryptionMethodGenerator( + key: PGPPublicKey + ): PublicKeyKeyEncryptionMethodGenerator = + JcePublicKeyKeyEncryptionMethodGenerator(key).setProvider(ProviderFactory.provider) - override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator = - JcePBEKeyEncryptionMethodGenerator(passphrase.getChars()) - .setProvider(ProviderFactory.provider) + override fun getPBEKeyEncryptionMethodGenerator( + passphrase: Passphrase + ): PBEKeyEncryptionMethodGenerator = + JcePBEKeyEncryptionMethodGenerator(passphrase.getChars()) + .setProvider(ProviderFactory.provider) override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder = - JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm) - .setProvider(ProviderFactory.provider) + JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm).setProvider(ProviderFactory.provider) - override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair = - JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate) + override fun getPGPKeyPair( + publicKeyAlgorithm: PublicKeyAlgorithm, + keyPair: KeyPair, + creationDate: Date + ): PGPKeyPair = JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate) override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory = - PGPObjectFactory(inputStream, keyFingerprintCalculator) -} \ No newline at end of file + PGPObjectFactory(inputStream, keyFingerprintCalculator) +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt index fdcad306..c67c3983 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt @@ -4,16 +4,13 @@ package org.pgpainless.key +import java.nio.charset.Charset import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.util.encoders.Hex -import java.nio.charset.Charset -/** - * Abstract super class of different version OpenPGP fingerprints. - * - */ +/** Abstract super class of different version OpenPGP fingerprints. */ abstract class OpenPgpFingerprint : CharSequence, Comparable { val fingerprint: String val bytes: ByteArray @@ -26,39 +23,41 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable abstract fun getVersion(): Int /** - * Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. - * This method can be implemented for V4 and V5 fingerprints. - * V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated. + * Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. This + * method can be implemented for V4 and V5 fingerprints. V3 key-IDs cannot be derived from the + * fingerprint, but we don't care, since V3 is deprecated. * - * @see - * RFC-4880 §12.2: Key IDs and Fingerprints * @return key id + * @see RFC-4880 §12.2: Key IDs and + * Fingerprints */ abstract val keyId: Long constructor(fingerprint: String) { val prep = fingerprint.replace(" ", "").trim().uppercase() if (!isValid(prep)) { - throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.") + throw IllegalArgumentException( + "Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.") } this.fingerprint = prep this.bytes = Hex.decode(prep) } - constructor(bytes: ByteArray): this(Hex.toHexString(bytes)) + constructor(bytes: ByteArray) : this(Hex.toHexString(bytes)) - constructor(key: PGPPublicKey): this(key.fingerprint) { + constructor(key: PGPPublicKey) : this(key.fingerprint) { if (key.version != getVersion()) { throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.") } } - constructor(key: PGPSecretKey): this(key.publicKey) + constructor(key: PGPSecretKey) : this(key.publicKey) - constructor(keys: PGPKeyRing): this(keys.publicKey) + constructor(keys: PGPKeyRing) : this(keys.publicKey) /** * Check, whether the fingerprint consists of 40 valid hexadecimal characters. + * * @param fp fingerprint to check. * @return true if fingerprint is valid. */ @@ -69,7 +68,9 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable override fun get(index: Int) = fingerprint.get(index) - override fun subSequence(startIndex: Int, endIndex: Int) = fingerprint.subSequence(startIndex, endIndex) + override fun subSequence(startIndex: Int, endIndex: Int) = + fingerprint.subSequence(startIndex, endIndex) + override fun compareTo(other: OpenPgpFingerprint): Int { return fingerprint.compareTo(other.fingerprint) } @@ -87,49 +88,49 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable abstract fun prettyPrint(): String companion object { - @JvmStatic - val utf8: Charset = Charset.forName("UTF-8") + @JvmStatic val utf8: Charset = Charset.forName("UTF-8") /** - * Return the fingerprint of the given key. - * This method automatically matches key versions to fingerprint implementations. + * Return the fingerprint of the given key. This method automatically matches key versions + * to fingerprint implementations. + * + * @param key key + * @return fingerprint + */ + @JvmStatic fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey) + + /** + * Return the fingerprint of the given key. This method automatically matches key versions + * to fingerprint implementations. * * @param key key * @return fingerprint */ @JvmStatic - fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey) + fun of(key: PGPPublicKey): OpenPgpFingerprint = + when (key.version) { + 4 -> OpenPgpV4Fingerprint(key) + 5 -> OpenPgpV5Fingerprint(key) + 6 -> OpenPgpV6Fingerprint(key) + else -> + throw IllegalArgumentException( + "OpenPGP keys of version ${key.version} are not supported.") + } /** - * Return the fingerprint of the given key. - * This method automatically matches key versions to fingerprint implementations. - * - * @param key key - * @return fingerprint - */ - @JvmStatic - fun of (key: PGPPublicKey): OpenPgpFingerprint = when(key.version) { - 4 -> OpenPgpV4Fingerprint(key) - 5 -> OpenPgpV5Fingerprint(key) - 6 -> OpenPgpV6Fingerprint(key) - else -> throw IllegalArgumentException("OpenPGP keys of version ${key.version} are not supported.") - } - - /** - * Return the fingerprint of the primary key of the given key ring. - * This method automatically matches key versions to fingerprint implementations. + * Return the fingerprint of the primary key of the given key ring. This method + * automatically matches key versions to fingerprint implementations. * * @param ring key ring * @return fingerprint */ - @JvmStatic - fun of (keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey) + @JvmStatic fun of(keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey) /** - * Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. - * If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint. - * In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended - * to know the version of the key beforehand. + * Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. If the + * trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 + * fingerprint. In this case, we return a {@link _64DigitFingerprint}. Since this is + * ambiguous, it is generally recommended to know the version of the key beforehand. * * @param fingerprint fingerprint * @return parsed fingerprint @@ -146,7 +147,8 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable // Might be v5 or v6 :/ return _64DigitFingerprint(prep) } - throw IllegalArgumentException("Fingerprint does not appear to match any known fingerprint pattern.") + throw IllegalArgumentException( + "Fingerprint does not appear to match any known fingerprint pattern.") } /** @@ -159,6 +161,6 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable @JvmStatic @Deprecated("use the parse() methods of the versioned fingerprint subclasses instead.") fun parseFromBinary(binaryFingerprint: ByteArray): OpenPgpFingerprint = - parse(Hex.toHexString(binaryFingerprint)) + parse(Hex.toHexString(binaryFingerprint)) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV4Fingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV4Fingerprint.kt index fe0c4364..e02f0ae7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV4Fingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV4Fingerprint.kt @@ -4,22 +4,26 @@ package org.pgpainless.key -import org.bouncycastle.openpgp.PGPKeyRing -import org.bouncycastle.openpgp.PGPPublicKey -import org.bouncycastle.openpgp.PGPSecretKey -import org.bouncycastle.util.encoders.Hex import java.net.URI import java.nio.Buffer import java.nio.ByteBuffer import java.nio.charset.Charset +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.util.encoders.Hex -class OpenPgpV4Fingerprint: OpenPgpFingerprint { +class OpenPgpV4Fingerprint : OpenPgpFingerprint { - constructor(fingerprint: String): super(fingerprint) - constructor(bytes: ByteArray): super(bytes) - constructor(key: PGPPublicKey): super(key) - constructor(key: PGPSecretKey): super(key) - constructor(keys: PGPKeyRing): super(keys) + constructor(fingerprint: String) : super(fingerprint) + + constructor(bytes: ByteArray) : super(bytes) + + constructor(key: PGPPublicKey) : super(key) + + constructor(key: PGPSecretKey) : super(key) + + constructor(keys: PGPKeyRing) : super(keys) override fun getVersion() = 4 @@ -47,7 +51,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint { append(fingerprint, i * 4, (i + 1) * 4).append(' ') } append(' ') - for (i in 5 .. 8) { + for (i in 5..8) { append(fingerprint, i * 4, (i + 1) * 4).append(' ') } append(fingerprint, 36, 40) @@ -55,8 +59,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint { } companion object { - @JvmStatic - val SCHEME = "openpgp4fpr" + @JvmStatic val SCHEME = "openpgp4fpr" @JvmStatic fun fromUri(uri: URI): OpenPgpV4Fingerprint { @@ -66,4 +69,4 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint { return OpenPgpV4Fingerprint(uri.schemeSpecificPart) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV5Fingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV5Fingerprint.kt index b82a3482..7bc36cc9 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV5Fingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV5Fingerprint.kt @@ -8,18 +8,20 @@ import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSecretKey -/** - * This class represents a hex encoded uppercase OpenPGP v5 fingerprint. - */ -class OpenPgpV5Fingerprint: _64DigitFingerprint { +/** This class represents a hex encoded uppercase OpenPGP v5 fingerprint. */ +class OpenPgpV5Fingerprint : _64DigitFingerprint { - constructor(fingerprint: String): super(fingerprint) - constructor(key: PGPPublicKey): super(key) - constructor(key: PGPSecretKey): super(key) - constructor(keys: PGPKeyRing): super(keys) - constructor(bytes: ByteArray): super(bytes) + constructor(fingerprint: String) : super(fingerprint) + + constructor(key: PGPPublicKey) : super(key) + + constructor(key: PGPSecretKey) : super(key) + + constructor(keys: PGPKeyRing) : super(keys) + + constructor(bytes: ByteArray) : super(bytes) override fun getVersion(): Int { return 5 } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV6Fingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV6Fingerprint.kt index 11cf05b0..0b843ed2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV6Fingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpV6Fingerprint.kt @@ -8,18 +8,20 @@ import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSecretKey -/** - * This class represents a hex encoded, uppercase OpenPGP v6 fingerprint. - */ -class OpenPgpV6Fingerprint: _64DigitFingerprint { +/** This class represents a hex encoded, uppercase OpenPGP v6 fingerprint. */ +class OpenPgpV6Fingerprint : _64DigitFingerprint { - constructor(fingerprint: String): super(fingerprint) - constructor(key: PGPPublicKey): super(key) - constructor(key: PGPSecretKey): super(key) - constructor(keys: PGPKeyRing): super(keys) - constructor(bytes: ByteArray): super(bytes) + constructor(fingerprint: String) : super(fingerprint) + + constructor(key: PGPPublicKey) : super(key) + + constructor(key: PGPSecretKey) : super(key) + + constructor(keys: PGPKeyRing) : super(keys) + + constructor(bytes: ByteArray) : super(bytes) override fun getVersion(): Int { return 6 } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt index a793fc99..2aec7976 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt @@ -13,17 +13,30 @@ import org.bouncycastle.openpgp.PGPPublicKey * as well as the subkeys fingerprint. */ class SubkeyIdentifier( - val primaryKeyFingerprint: OpenPgpFingerprint, - val subkeyFingerprint: OpenPgpFingerprint) { + val primaryKeyFingerprint: OpenPgpFingerprint, + val subkeyFingerprint: OpenPgpFingerprint +) { - constructor(fingerprint: OpenPgpFingerprint): this(fingerprint, fingerprint) - constructor(keys: PGPKeyRing): this(keys.publicKey) - constructor(key: PGPPublicKey): this(OpenPgpFingerprint.of(key)) - constructor(keys: PGPKeyRing, keyId: Long): this( - OpenPgpFingerprint.of(keys.publicKey), - OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?: - throw NoSuchElementException("OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}"))) - constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint) + constructor(fingerprint: OpenPgpFingerprint) : this(fingerprint, fingerprint) + + constructor(keys: PGPKeyRing) : this(keys.publicKey) + + constructor(key: PGPPublicKey) : this(OpenPgpFingerprint.of(key)) + + constructor( + keys: PGPKeyRing, + keyId: Long + ) : this( + OpenPgpFingerprint.of(keys.publicKey), + OpenPgpFingerprint.of( + keys.getPublicKey(keyId) + ?: throw NoSuchElementException( + "OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}"))) + + constructor( + keys: PGPKeyRing, + subkeyFingerprint: OpenPgpFingerprint + ) : this(OpenPgpFingerprint.of(keys), subkeyFingerprint) val keyId = subkeyFingerprint.keyId val fingerprint = subkeyFingerprint @@ -34,7 +47,7 @@ class SubkeyIdentifier( val isPrimaryKey = primaryKeyId == subkeyId fun matches(fingerprint: OpenPgpFingerprint) = - primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint + primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint override fun equals(other: Any?): Boolean { if (other == null) { @@ -47,7 +60,8 @@ class SubkeyIdentifier( return false } - return primaryKeyFingerprint == other.primaryKeyFingerprint && subkeyFingerprint == other.subkeyFingerprint + return primaryKeyFingerprint == other.primaryKeyFingerprint && + subkeyFingerprint == other.subkeyFingerprint } override fun hashCode(): Int { @@ -55,4 +69,4 @@ class SubkeyIdentifier( } override fun toString(): String = "$subkeyFingerprint $primaryKeyFingerprint" -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/_64DigitFingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/_64DigitFingerprint.kt index 0f7dd705..279815bd 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/_64DigitFingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/_64DigitFingerprint.kt @@ -2,47 +2,47 @@ // // SPDX-License-Identifier: Apache-2.0 -package org.pgpainless.key; +package org.pgpainless.key -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.util.encoders.Hex; +import java.nio.Buffer +import java.nio.ByteBuffer +import java.nio.charset.Charset +import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.util.encoders.Hex /** - * This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. - * Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the - * key version. + * This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. Since both + * fingerprints use the same format, this class is used when parsing the fingerprint without knowing + * the key version. */ -open class _64DigitFingerprint: OpenPgpFingerprint { +open class _64DigitFingerprint : OpenPgpFingerprint { /** * Create an {@link _64DigitFingerprint}. * * @param fingerprint uppercase hexadecimal fingerprint of length 64 */ - constructor(fingerprint: String): super(fingerprint) - constructor(bytes: ByteArray): super(bytes) - constructor(key: PGPPublicKey): super(key) - constructor(key: PGPSecretKey): super(key) - constructor(keys: PGPKeyRing): super(keys) + constructor(fingerprint: String) : super(fingerprint) + + constructor(bytes: ByteArray) : super(bytes) + + constructor(key: PGPPublicKey) : super(key) + + constructor(key: PGPSecretKey) : super(key) + + constructor(keys: PGPKeyRing) : super(keys) override val keyId: Long get() { val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8"))) - val buf = ByteBuffer.wrap(bytes); + val buf = ByteBuffer.wrap(bytes) // The key id is the left-most 8 bytes (conveniently a long). // We have to cast here in order to be compatible with java 8 // https://github.com/eclipse/jetty.project/issues/3244 - (buf as Buffer).position(0); + (buf as Buffer).position(0) return buf.getLong() } @@ -62,13 +62,13 @@ open class _64DigitFingerprint: OpenPgpFingerprint { override fun prettyPrint(): String { return buildString { for (i in 0 until 4) { - append(fingerprint, i * 8, (i + 1) * 8).append(' '); + append(fingerprint, i * 8, (i + 1) * 8).append(' ') } - append(' '); + append(' ') for (i in 4 until 7) { - append(fingerprint, i * 8, (i + 1) * 8).append(' '); + append(fingerprint, i * 8, (i + 1) * 8).append(' ') } - append(fingerprint, 56, 64); + append(fingerprint, 56, 64) } } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt index a924e0f3..9499355c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt @@ -4,6 +4,7 @@ package org.pgpainless.key.certification +import java.util.* import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKey @@ -22,28 +23,27 @@ import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder import org.pgpainless.signature.subpackets.CertificationSubpackets -import java.util.* /** - * API for creating certifications and delegations (Signatures) on keys. - * This API can be used to sign another persons OpenPGP key. + * API for creating certifications and delegations (Signatures) on keys. This API can be used to + * sign another persons OpenPGP key. * - * A certification over a user-id is thereby used to attest, that the user believes that the user-id really belongs - * to the owner of the certificate. - * A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer. + * A certification over a user-id is thereby used to attest, that the user believes that the user-id + * really belongs to the owner of the certificate. A delegation over a key can be used to delegate + * trust by marking the certificate as a trusted introducer. */ class CertifyCertificate { /** - * Create a certification over a User-Id. - * By default, this method will use [CertificationType.GENERIC] to create the signature. + * Create a certification over a User-Id. By default, this method will use + * [CertificationType.GENERIC] to create the signature. * * @param userId user-id to certify * @param certificate certificate * @return API */ fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing): CertificationOnUserId = - userIdOnCertificate(userId, certificate, CertificationType.GENERIC) + userIdOnCertificate(userId, certificate, CertificationType.GENERIC) /** * Create a certification of the given [CertificationType] over a User-Id. @@ -53,36 +53,40 @@ class CertifyCertificate { * @param certificationType type of signature * @return API */ - fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing, certificationType: CertificationType) = - CertificationOnUserId(userId, certificate, certificationType) + fun userIdOnCertificate( + userId: String, + certificate: PGPPublicKeyRing, + certificationType: CertificationType + ) = CertificationOnUserId(userId, certificate, certificationType) /** - * Create a delegation (direct key signature) over a certificate. - * This can be used to mark a certificate as a trusted introducer - * (see [certificate] method with [Trustworthiness] argument). + * Create a delegation (direct key signature) over a certificate. This can be used to mark a + * certificate as a trusted introducer (see [certificate] method with [Trustworthiness] + * argument). * * @param certificate certificate * @return API */ fun certificate(certificate: PGPPublicKeyRing): DelegationOnCertificate = - certificate(certificate, null) + certificate(certificate, null) /** - * Create a delegation (direct key signature) containing a [org.bouncycastle.bcpg.sig.TrustSignature] packet - * over a certificate. - * This can be used to mark a certificate as a trusted introducer. + * Create a delegation (direct key signature) containing a + * [org.bouncycastle.bcpg.sig.TrustSignature] packet over a certificate. This can be used to + * mark a certificate as a trusted introducer. * * @param certificate certificate * @param trustworthiness trustworthiness of the certificate * @return API */ fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) = - DelegationOnCertificate(certificate, trustworthiness) + DelegationOnCertificate(certificate, trustworthiness) class CertificationOnUserId( - val userId: String, - val certificate: PGPPublicKeyRing, - val certificationType: CertificationType) { + val userId: String, + val certificate: PGPPublicKeyRing, + val certificationType: CertificationType + ) { /** * Create the certification using the given key. @@ -92,11 +96,14 @@ class CertifyCertificate { * @return API * @throws PGPException in case of an OpenPGP related error */ - fun withKey(certificationKey: PGPSecretKeyRing, - protector: SecretKeyRingProtector): CertificationOnUserIdWithSubpackets { + fun withKey( + certificationKey: PGPSecretKeyRing, + protector: SecretKeyRingProtector + ): CertificationOnUserIdWithSubpackets { val secretKey = getCertifyingSecretKey(certificationKey) - val sigBuilder = ThirdPartyCertificationSignatureBuilder( + val sigBuilder = + ThirdPartyCertificationSignatureBuilder( certificationType.asSignatureType(), secretKey, protector) return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder) @@ -104,9 +111,9 @@ class CertifyCertificate { } class CertificationOnUserIdWithSubpackets( - val certificate: PGPPublicKeyRing, - val userId: String, - val sigBuilder: ThirdPartyCertificationSignatureBuilder + val certificate: PGPPublicKeyRing, + val userId: String, + val sigBuilder: ThirdPartyCertificationSignatureBuilder ) { /** @@ -116,7 +123,9 @@ class CertifyCertificate { * @return result * @throws PGPException in case of an OpenPGP related error */ - fun buildWithSubpackets(subpacketCallback: CertificationSubpackets.Callback): CertificationResult { + fun buildWithSubpackets( + subpacketCallback: CertificationSubpackets.Callback + ): CertificationResult { sigBuilder.applyCallback(subpacketCallback) return build() } @@ -129,14 +138,16 @@ class CertifyCertificate { */ fun build(): CertificationResult { val signature = sigBuilder.build(certificate, userId) - val certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature) + val certifiedCertificate = + KeyRingUtils.injectCertification(certificate, userId, signature) return CertificationResult(certifiedCertificate, signature) } } class DelegationOnCertificate( - val certificate: PGPPublicKeyRing, - val trustworthiness: Trustworthiness?) { + val certificate: PGPPublicKeyRing, + val trustworthiness: Trustworthiness? + ) { /** * Build the delegation using the given certification key. @@ -146,20 +157,24 @@ class CertifyCertificate { * @return API * @throws PGPException in case of an OpenPGP related error */ - fun withKey(certificationKey: PGPSecretKeyRing, - protector: SecretKeyRingProtector): DelegationOnCertificateWithSubpackets { + fun withKey( + certificationKey: PGPSecretKeyRing, + protector: SecretKeyRingProtector + ): DelegationOnCertificateWithSubpackets { val secretKey = getCertifyingSecretKey(certificationKey) val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector) if (trustworthiness != null) { - sigBuilder.hashedSubpackets.setTrust(true, trustworthiness.depth, trustworthiness.amount) + sigBuilder.hashedSubpackets.setTrust( + true, trustworthiness.depth, trustworthiness.amount) } return DelegationOnCertificateWithSubpackets(certificate, sigBuilder) } } class DelegationOnCertificateWithSubpackets( - val certificate: PGPPublicKeyRing, - val sigBuilder: ThirdPartyDirectKeySignatureBuilder) { + val certificate: PGPPublicKeyRing, + val sigBuilder: ThirdPartyDirectKeySignatureBuilder + ) { /** * Apply the given signature subpackets and build the delegation signature. @@ -168,7 +183,9 @@ class CertifyCertificate { * @return result * @throws PGPException in case of an OpenPGP related error */ - fun buildWithSubpackets(subpacketsCallback: CertificationSubpackets.Callback): CertificationResult { + fun buildWithSubpackets( + subpacketsCallback: CertificationSubpackets.Callback + ): CertificationResult { sigBuilder.applyCallback(subpacketsCallback) return build() } @@ -182,7 +199,8 @@ class CertifyCertificate { fun build(): CertificationResult { val delegatedKey = certificate.publicKey val delegation = sigBuilder.build(delegatedKey) - val delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation) + val delegatedCertificate = + KeyRingUtils.injectCertification(certificate, delegatedKey, delegation) return CertificationResult(delegatedCertificate, delegation) } } @@ -194,8 +212,9 @@ class CertifyCertificate { * @param certification the newly created signature */ data class CertificationResult( - val certifiedCertificate: PGPPublicKeyRing, - val certification: PGPSignature) + val certifiedCertificate: PGPPublicKeyRing, + val certification: PGPSignature + ) companion object { @JvmStatic @@ -205,9 +224,7 @@ class CertifyCertificate { val fingerprint = info.fingerprint val certificationPubKey = info.getPublicKey(fingerprint) - requireNotNull(certificationPubKey) { - "Primary key cannot be null." - } + requireNotNull(certificationPubKey) { "Primary key cannot be null." } if (!info.isKeyValidlyBound(certificationPubKey.keyID)) { throw RevokedKeyException(fingerprint) } @@ -222,7 +239,7 @@ class CertifyCertificate { } return certificationKey.getSecretKey(certificationPubKey.keyID) - ?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID) + ?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/collection/PGPKeyRingCollection.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/collection/PGPKeyRingCollection.kt index 63151a4c..f69d4a08 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/collection/PGPKeyRingCollection.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/collection/PGPKeyRingCollection.kt @@ -4,32 +4,38 @@ package org.pgpainless.key.collection +import java.io.InputStream import org.bouncycastle.openpgp.* import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.util.ArmorUtils -import java.io.InputStream /** - * This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was inspired by - * [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection]. + * This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was + * inspired by [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection]. */ class PGPKeyRingCollection( - val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection, - val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection + val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection, + val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection ) { - constructor(encoding: ByteArray, isSilent: Boolean): this(encoding.inputStream(), isSilent) + constructor(encoding: ByteArray, isSilent: Boolean) : this(encoding.inputStream(), isSilent) - constructor(inputStream: InputStream, isSilent: Boolean): this(parse(inputStream, isSilent)) + constructor(inputStream: InputStream, isSilent: Boolean) : this(parse(inputStream, isSilent)) - constructor(collection: Collection, isSilent: Boolean): this(segment(collection, isSilent)) + constructor( + collection: Collection, + isSilent: Boolean + ) : this(segment(collection, isSilent)) - private constructor(arguments: Pair): this(arguments.first, arguments.second) + private constructor( + arguments: Pair + ) : this(arguments.first, arguments.second) /** * The number of rings in this collection. * - * @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this collection + * @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this + * collection */ val size: Int get() = pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size() @@ -41,11 +47,16 @@ class PGPKeyRingCollection( companion object { @JvmStatic - private fun parse(inputStream: InputStream, isSilent: Boolean): Pair { + private fun parse( + inputStream: InputStream, + isSilent: Boolean + ): Pair { val secretKeyRings = mutableListOf() val certificates = mutableListOf() // Double getDecoderStream because of #96 - val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream)) + val objectFactory = + ImplementationFactory.getInstance() + .getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream)) for (obj in objectFactory) { if (obj == null) { @@ -68,16 +79,21 @@ class PGPKeyRingCollection( } if (!isSilent) { - throw PGPException("${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" + + throw PGPException( + "${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" + " or ${PGPPublicKeyRing::class.java.simpleName} expected") } } - return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates) + return PGPSecretKeyRingCollection(secretKeyRings) to + PGPPublicKeyRingCollection(certificates) } @JvmStatic - private fun segment(collection: Collection, isSilent: Boolean): Pair { + private fun segment( + collection: Collection, + isSilent: Boolean + ): Pair { val secretKeyRings = mutableListOf() val certificates = mutableListOf() @@ -87,12 +103,14 @@ class PGPKeyRingCollection( } else if (keyRing is PGPPublicKeyRing) { certificates.add(keyRing) } else if (!isSilent) { - throw PGPException("${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" + + throw PGPException( + "${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" + " or ${PGPPublicKeyRing::class.java.simpleName} expected") } } - return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates) + return PGPSecretKeyRingCollection(secretKeyRings) to + PGPPublicKeyRingCollection(certificates) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt index f194638b..8404b652 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -4,6 +4,9 @@ package org.pgpainless.key.generation +import java.io.IOException +import java.security.KeyPairGenerator +import java.util.* import org.bouncycastle.extensions.unlock import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor @@ -21,10 +24,6 @@ import org.pgpainless.signature.subpackets.SelfSignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper import org.pgpainless.util.Passphrase -import java.io.IOException -import java.security.KeyPairGenerator -import java.util.* - class KeyRingBuilder : KeyRingBuilderInterface { @@ -49,19 +48,19 @@ class KeyRingBuilder : KeyRingBuilderInterface { userIds[userId.toString().trim()] = null } - override fun addUserId(userId: ByteArray): KeyRingBuilder = addUserId(Strings.fromUTF8ByteArray(userId)) + override fun addUserId(userId: ByteArray): KeyRingBuilder = + addUserId(Strings.fromUTF8ByteArray(userId)) override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply { if (expirationDate == null) { this.expirationDate = null return@apply } - this.expirationDate = expirationDate.let { - require(Date() < expirationDate) { - "Expiration date must be in the future." + this.expirationDate = + expirationDate.let { + require(Date() < expirationDate) { "Expiration date must be in the future." } + expirationDate } - expirationDate - } } override fun setPassphrase(passphrase: Passphrase): KeyRingBuilder = apply { @@ -85,17 +84,14 @@ class KeyRingBuilder : KeyRingBuilderInterface { private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify override fun build(): PGPSecretKeyRing { - val keyFingerprintCalculator = ImplementationFactory.getInstance() - .v4FingerprintCalculator + val keyFingerprintCalculator = ImplementationFactory.getInstance().v4FingerprintCalculator val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator) val secretKeyDecryptor = buildSecretKeyDecryptor() passphrase.clear() // Passphrase was used above, so we can get rid of it // generate primary key - requireNotNull(primaryKeySpec) { - "Primary Key spec required." - } + requireNotNull(primaryKeySpec) { "Primary Key spec required." } val certKey = generateKeyPair(primaryKeySpec!!) val signer = buildContentSigner(certKey) val signatureGenerator = PGPSignatureGenerator(signer) @@ -110,16 +106,28 @@ class KeyRingBuilder : KeyRingBuilderInterface { val generator = PGPSignatureSubpacketGenerator() SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator) val hashedSubPackets = generator.generate() - val ringGenerator = if (userIds.isEmpty()) { - PGPKeyRingGenerator(certKey, keyFingerprintCalculator, hashedSubPackets, null, signer, + val ringGenerator = + if (userIds.isEmpty()) { + PGPKeyRingGenerator( + certKey, + keyFingerprintCalculator, + hashedSubPackets, + null, + signer, secretKeyEncryptor) - } else { - userIds.keys.first().let { primaryUserId -> - PGPKeyRingGenerator(SignatureType.POSITIVE_CERTIFICATION.code, certKey, primaryUserId, - keyFingerprintCalculator, hashedSubPackets, null, signer, + } else { + userIds.keys.first().let { primaryUserId -> + PGPKeyRingGenerator( + SignatureType.POSITIVE_CERTIFICATION.code, + certKey, + primaryUserId, + keyFingerprintCalculator, + hashedSubPackets, + null, + signer, secretKeyEncryptor) + } } - } addSubKeys(certKey, ringGenerator) @@ -138,20 +146,26 @@ class KeyRingBuilder : KeyRingBuilderInterface { val additionalUserId = userIdIterator.next() val userIdString = additionalUserId.key val callback = additionalUserId.value - val subpackets = if (callback == null) { - hashedSubPacketGenerator.also { it.setPrimaryUserId(null) } - } else { - SignatureSubpackets.createHashedSubpackets(primaryPubKey).also { callback.modifyHashedSubpackets(it) } - } + val subpackets = + if (callback == null) { + hashedSubPacketGenerator.also { it.setPrimaryUserId(null) } + } else { + SignatureSubpackets.createHashedSubpackets(primaryPubKey).also { + callback.modifyHashedSubpackets(it) + } + } signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey) - signatureGenerator.setHashedSubpackets( - SignatureSubpacketsHelper.toVector(subpackets)) - val additionalUserIdSignature = signatureGenerator.generateCertification(userIdString, primaryPubKey) - primaryPubKey = PGPPublicKey.addCertification(primaryPubKey, userIdString, additionalUserIdSignature) + signatureGenerator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(subpackets)) + val additionalUserIdSignature = + signatureGenerator.generateCertification(userIdString, primaryPubKey) + primaryPubKey = + PGPPublicKey.addCertification( + primaryPubKey, userIdString, additionalUserIdSignature) } // Reassemble secret key ring with modified primary key - val primarySecretKey = PGPSecretKey( + val primarySecretKey = + PGPSecretKey( privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor) val secretKeyList = mutableListOf(primarySecretKey) while (secretKeys.hasNext()) { @@ -168,25 +182,34 @@ class KeyRingBuilder : KeyRingBuilderInterface { } else { var hashedSubpackets = subKeySpec.subpackets try { - hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(primaryKey, subKey, hashedSubpackets) + hashedSubpackets = + addPrimaryKeyBindingSignatureIfNecessary( + primaryKey, subKey, hashedSubpackets) } catch (e: IOException) { - throw PGPException("Exception while adding primary key binding signature to signing subkey.", e) + throw PGPException( + "Exception while adding primary key binding signature to signing subkey.", + e) } ringGenerator.addSubKey(subKey, hashedSubpackets, null) } } } - private fun addPrimaryKeyBindingSignatureIfNecessary(primaryKey: PGPKeyPair, subKey: PGPKeyPair, hashedSubpackets: PGPSignatureSubpacketVector): PGPSignatureSubpacketVector { + private fun addPrimaryKeyBindingSignatureIfNecessary( + primaryKey: PGPKeyPair, + subKey: PGPKeyPair, + hashedSubpackets: PGPSignatureSubpacketVector + ): PGPSignatureSubpacketVector { val keyFlagMask = hashedSubpackets.keyFlags if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) && - !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) { + !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) { return hashedSubpackets } val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey)) bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey) - val primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey) + val primaryKeyBindingSig = + bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey) val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets) subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig) return subpacketGenerator.generate() @@ -194,25 +217,29 @@ class KeyRingBuilder : KeyRingBuilderInterface { private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder { val hashAlgorithm = PGPainless.getPolicy().signatureHashAlgorithmPolicy.defaultHashAlgorithm - return ImplementationFactory.getInstance().getPGPContentSignerBuilder( - certKey.publicKey.algorithm, hashAlgorithm.algorithmId) + return ImplementationFactory.getInstance() + .getPGPContentSignerBuilder(certKey.publicKey.algorithm, hashAlgorithm.algorithmId) } - private fun buildSecretKeyEncryptor(keyFingerprintCalculator: PGPDigestCalculator): PBESecretKeyEncryptor? { - val keyEncryptionAlgorithm = PGPainless.getPolicy().symmetricKeyEncryptionAlgorithmPolicy.defaultSymmetricKeyAlgorithm - check(passphrase.isValid) { - "Passphrase was cleared." - } - return if (passphrase.isEmpty) null else ImplementationFactory.getInstance() - .getPBESecretKeyEncryptor(keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase) + private fun buildSecretKeyEncryptor( + keyFingerprintCalculator: PGPDigestCalculator + ): PBESecretKeyEncryptor? { + val keyEncryptionAlgorithm = + PGPainless.getPolicy() + .symmetricKeyEncryptionAlgorithmPolicy + .defaultSymmetricKeyAlgorithm + check(passphrase.isValid) { "Passphrase was cleared." } + return if (passphrase.isEmpty) null + else + ImplementationFactory.getInstance() + .getPBESecretKeyEncryptor( + keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase) } private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? { - check(passphrase.isValid) { - "Passphrase was cleared." - } - return if (passphrase.isEmpty) null else ImplementationFactory.getInstance() - .getPBESecretKeyDecryptor(passphrase) + check(passphrase.isValid) { "Passphrase was cleared." } + return if (passphrase.isEmpty) null + else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase) } companion object { @@ -222,16 +249,16 @@ class KeyRingBuilder : KeyRingBuilderInterface { fun generateKeyPair(spec: KeySpec): PGPKeyPair { spec.keyType.let { type -> // Create raw Key Pair - val keyPair = KeyPairGenerator.getInstance(type.name, ProviderFactory.provider) - .also { it.initialize(type.algorithmSpec) } - .generateKeyPair() + val keyPair = + KeyPairGenerator.getInstance(type.name, ProviderFactory.provider) + .also { it.initialize(type.algorithmSpec) } + .generateKeyPair() val keyCreationDate = spec.keyCreationDate ?: Date() // Form PGP Key Pair return ImplementationFactory.getInstance() - .getPGPKeyPair(type.algorithm, keyPair, keyCreationDate) + .getPGPKeyPair(type.algorithm, keyPair, keyCreationDate) } } } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt index 0ed301fe..ecc818b6 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt @@ -4,12 +4,12 @@ package org.pgpainless.key.generation -import org.bouncycastle.openpgp.PGPException -import org.bouncycastle.openpgp.PGPSecretKeyRing -import org.pgpainless.util.Passphrase import java.security.InvalidAlgorithmParameterException import java.security.NoSuchAlgorithmException import java.util.* +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.pgpainless.util.Passphrase interface KeyRingBuilderInterface> { @@ -29,6 +29,9 @@ interface KeyRingBuilderInterface> { fun setPassphrase(passphrase: Passphrase): B - @Throws(NoSuchAlgorithmException::class, PGPException::class, InvalidAlgorithmParameterException::class) + @Throws( + NoSuchAlgorithmException::class, + PGPException::class, + InvalidAlgorithmParameterException::class) fun build(): PGPSecretKeyRing -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt index 82215971..5e9fa7fd 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt @@ -17,8 +17,8 @@ import org.pgpainless.util.Passphrase class KeyRingTemplates { /** - * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, - * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. + * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a + * dedicated RSA subkey used for signing and a third RSA subkey used for encryption. * * @param userId userId or null * @param length length of the RSA keys @@ -26,160 +26,187 @@ class KeyRingTemplates { * @return key */ @JvmOverloads - fun rsaKeyRing(userId: CharSequence?, - length: RsaLength, - passphrase: Passphrase = Passphrase.emptyPassphrase() - ): PGPSecretKeyRing = buildKeyRing().apply { - setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER)) - addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA)) - addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) - setPassphrase(passphrase) - if (userId != null) { - addUserId(userId) - } - }.build() + fun rsaKeyRing( + userId: CharSequence?, + length: RsaLength, + passphrase: Passphrase = Passphrase.emptyPassphrase() + ): PGPSecretKeyRing = + buildKeyRing() + .apply { + setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER)) + addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA)) + addSubkey( + getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) + setPassphrase(passphrase) + if (userId != null) { + addUserId(userId) + } + } + .build() /** - * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, - * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. + * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a + * dedicated RSA subkey used for signing and a third RSA subkey used for encryption. * * @param userId userId or null * @param length length of the RSA keys - * @param password passphrase to encrypt the key with. Can be null or blank for unencrypted keys. + * @param password passphrase to encrypt the key with. Can be null or blank for unencrypted + * keys. * @return key */ - fun rsaKeyRing(userId: CharSequence?, - length: RsaLength, - password: String? - ): PGPSecretKeyRing = password.let { - if (it.isNullOrBlank()) { - rsaKeyRing(userId, length, Passphrase.emptyPassphrase()) - } else { - rsaKeyRing(userId, length, Passphrase.fromPassword(it)) + fun rsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?): PGPSecretKeyRing = + password.let { + if (it.isNullOrBlank()) { + rsaKeyRing(userId, length, Passphrase.emptyPassphrase()) + } else { + rsaKeyRing(userId, length, Passphrase.fromPassword(it)) + } } - } /** - * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. - * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. + * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The + * KeyPair consists of a single RSA master key which is used for signing, encryption and + * certification. * * @param userId user id. * @param length length in bits. * @param password Password of the key. Can be empty for unencrypted keys. - * * @return {@link PGPSecretKeyRing} containing the KeyPair. */ @JvmOverloads - fun simpleRsaKeyRing(userId: CharSequence?, - length: RsaLength, - passphrase: Passphrase = Passphrase.emptyPassphrase() - ): PGPSecretKeyRing = buildKeyRing().apply { - setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS)) - setPassphrase(passphrase) - if (userId != null) { - addUserId(userId.toString()) - } - }.build() + fun simpleRsaKeyRing( + userId: CharSequence?, + length: RsaLength, + passphrase: Passphrase = Passphrase.emptyPassphrase() + ): PGPSecretKeyRing = + buildKeyRing() + .apply { + setPrimaryKey( + getBuilder( + KeyType.RSA(length), + KeyFlag.CERTIFY_OTHER, + KeyFlag.SIGN_DATA, + KeyFlag.ENCRYPT_COMMS)) + setPassphrase(passphrase) + if (userId != null) { + addUserId(userId.toString()) + } + } + .build() /** - * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. - * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. + * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The + * KeyPair consists of a single RSA master key which is used for signing, encryption and + * certification. * * @param userId user id. * @param length length in bits. * @param password Password of the key. Can be null or blank for unencrypted keys. - * * @return {@link PGPSecretKeyRing} containing the KeyPair. */ - fun simpleRsaKeyRing(userId: CharSequence?, - length: RsaLength, - password: String? - ) = password.let { - if (it.isNullOrBlank()) { - simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase()) - } else { - simpleRsaKeyRing(userId, length, Passphrase.fromPassword(it)) + fun simpleRsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?) = + password.let { + if (it.isNullOrBlank()) { + simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase()) + } else { + simpleRsaKeyRing(userId, length, Passphrase.fromPassword(it)) + } } - } /** - * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. - * The EdDSA primary key is used for signing messages and certifying the sub key. - * The XDH subkey is used for encryption and decryption of messages. + * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The + * EdDSA primary key is used for signing messages and certifying the sub key. The XDH subkey is + * used for encryption and decryption of messages. * * @param userId user-id * @param passphrase Password of the private key. Can be empty for an unencrypted key. - * * @return {@link PGPSecretKeyRing} containing the key pairs. */ @JvmOverloads - fun simpleEcKeyRing(userId: CharSequence?, - passphrase: Passphrase = Passphrase.emptyPassphrase() - ): PGPSecretKeyRing = buildKeyRing().apply { - setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) - addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)) - setPassphrase(passphrase) - if (userId != null) { - addUserId(userId.toString()) - } - }.build() + fun simpleEcKeyRing( + userId: CharSequence?, + passphrase: Passphrase = Passphrase.emptyPassphrase() + ): PGPSecretKeyRing = + buildKeyRing() + .apply { + setPrimaryKey( + getBuilder( + KeyType.EDDSA(EdDSACurve._Ed25519), + KeyFlag.CERTIFY_OTHER, + KeyFlag.SIGN_DATA)) + addSubkey( + getBuilder( + KeyType.XDH(XDHSpec._X25519), + KeyFlag.ENCRYPT_STORAGE, + KeyFlag.ENCRYPT_COMMS)) + setPassphrase(passphrase) + if (userId != null) { + addUserId(userId.toString()) + } + } + .build() /** - * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. - * The EdDSA primary key is used for signing messages and certifying the sub key. - * The XDH subkey is used for encryption and decryption of messages. + * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The + * EdDSA primary key is used for signing messages and certifying the sub key. The XDH subkey is + * used for encryption and decryption of messages. * * @param userId user-id * @param passphrase Password of the private key. Can be null or blank for an unencrypted key. - * * @return {@link PGPSecretKeyRing} containing the key pairs. */ - fun simpleEcKeyRing(userId: CharSequence?, - password: String? - ): PGPSecretKeyRing = password.let { - if (it.isNullOrBlank()) { - simpleEcKeyRing(userId, Passphrase.emptyPassphrase()) - } else { - simpleEcKeyRing(userId, Passphrase.fromPassword(it)) + fun simpleEcKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing = + password.let { + if (it.isNullOrBlank()) { + simpleEcKeyRing(userId, Passphrase.emptyPassphrase()) + } else { + simpleEcKeyRing(userId, Passphrase.fromPassword(it)) + } } - } /** - * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify - * an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. + * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to + * certify an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. * * @param userId primary user id * @param passphrase passphrase for the private key. Can be empty for an unencrypted key. * @return key ring */ @JvmOverloads - fun modernKeyRing(userId: CharSequence?, - passphrase: Passphrase = Passphrase.emptyPassphrase() - ): PGPSecretKeyRing = buildKeyRing().apply { - setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) - addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) - addSubkey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA)) - setPassphrase(passphrase) - if (userId != null) { - addUserId(userId) - } - }.build() + fun modernKeyRing( + userId: CharSequence?, + passphrase: Passphrase = Passphrase.emptyPassphrase() + ): PGPSecretKeyRing = + buildKeyRing() + .apply { + setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) + addSubkey( + getBuilder( + KeyType.XDH(XDHSpec._X25519), + KeyFlag.ENCRYPT_COMMS, + KeyFlag.ENCRYPT_STORAGE)) + addSubkey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA)) + setPassphrase(passphrase) + if (userId != null) { + addUserId(userId) + } + } + .build() /** - * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify - * an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. + * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to + * certify an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. * * @param userId primary user id * @param password passphrase for the private key. Can be null or blank for an unencrypted key. * @return key ring */ - fun modernKeyRing(userId: CharSequence?, - password: String? - ): PGPSecretKeyRing = password.let { - if (it.isNullOrBlank()) { - modernKeyRing(userId, Passphrase.emptyPassphrase()) - } else { - modernKeyRing(userId, Passphrase.fromPassword(it)) + fun modernKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing = + password.let { + if (it.isNullOrBlank()) { + modernKeyRing(userId, Passphrase.emptyPassphrase()) + } else { + modernKeyRing(userId, Passphrase.fromPassword(it)) + } } - } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt index fd0b8635..bc8d5599 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt @@ -4,18 +4,18 @@ package org.pgpainless.key.generation +import java.util.* import org.bouncycastle.openpgp.PGPSignatureSubpacketVector import org.pgpainless.algorithm.KeyFlag import org.pgpainless.key.generation.type.KeyType import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper -import java.util.* data class KeySpec( - val keyType: KeyType, - val subpacketGenerator: SignatureSubpackets, - val isInheritedSubPackets: Boolean, - val keyCreationDate: Date + val keyType: KeyType, + val subpacketGenerator: SignatureSubpackets, + val isInheritedSubPackets: Boolean, + val keyCreationDate: Date ) { val subpackets: PGPSignatureSubpacketVector @@ -25,4 +25,4 @@ data class KeySpec( @JvmStatic fun getBuilder(type: KeyType, vararg flags: KeyFlag) = KeySpecBuilder(type, *flags) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt index a430e796..03291f2d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt @@ -4,41 +4,47 @@ package org.pgpainless.key.generation +import java.util.* import org.pgpainless.PGPainless import org.pgpainless.algorithm.* import org.pgpainless.key.generation.type.KeyType import org.pgpainless.signature.subpackets.SelfSignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil -import java.util.* -class KeySpecBuilder constructor( - private val type: KeyType, - private val keyFlags: List, +class KeySpecBuilder +constructor( + private val type: KeyType, + private val keyFlags: List, ) : KeySpecBuilderInterface { private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets() private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite - private var preferredCompressionAlgorithms: Set = algorithmSuite.compressionAlgorithms + private var preferredCompressionAlgorithms: Set = + algorithmSuite.compressionAlgorithms private var preferredHashAlgorithms: Set = algorithmSuite.hashAlgorithms - private var preferredSymmetricAlgorithms: Set = algorithmSuite.symmetricKeyAlgorithms + private var preferredSymmetricAlgorithms: Set = + algorithmSuite.symmetricKeyAlgorithms private var keyCreationDate = Date() - constructor(type: KeyType, vararg keyFlags: KeyFlag): this(type, listOf(*keyFlags)) + constructor(type: KeyType, vararg keyFlags: KeyFlag) : this(type, listOf(*keyFlags)) init { SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, *keyFlags.toTypedArray()) } - override fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder = apply { - this.preferredCompressionAlgorithms = algorithms.toSet() - } + override fun overridePreferredCompressionAlgorithms( + vararg algorithms: CompressionAlgorithm + ): KeySpecBuilder = apply { this.preferredCompressionAlgorithms = algorithms.toSet() } - override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder = apply { - this.preferredHashAlgorithms = algorithms.toSet() - } + override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder = + apply { + this.preferredHashAlgorithms = algorithms.toSet() + } - override fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder = apply { + override fun overridePreferredSymmetricKeyAlgorithms( + vararg algorithms: SymmetricKeyAlgorithm + ): KeySpecBuilder = apply { require(!algorithms.contains(SymmetricKeyAlgorithm.NULL)) { "NULL (unencrypted) is an invalid symmetric key algorithm preference." } @@ -50,14 +56,14 @@ class KeySpecBuilder constructor( } override fun build(): KeySpec { - return hashedSubpackets.apply { - setKeyFlags(keyFlags) - setPreferredCompressionAlgorithms(preferredCompressionAlgorithms) - setPreferredHashAlgorithms(preferredHashAlgorithms) - setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms) - setFeatures(Feature.MODIFICATION_DETECTION) - }.let { - KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate) - } + return hashedSubpackets + .apply { + setKeyFlags(keyFlags) + setPreferredCompressionAlgorithms(preferredCompressionAlgorithms) + setPreferredHashAlgorithms(preferredHashAlgorithms) + setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms) + setFeatures(Feature.MODIFICATION_DETECTION) + } + .let { KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt index fcafb9c1..7fb767e4 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt @@ -4,20 +4,24 @@ package org.pgpainless.key.generation +import java.util.* import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm -import java.util.* interface KeySpecBuilderInterface { - fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder + fun overridePreferredCompressionAlgorithms( + vararg algorithms: CompressionAlgorithm + ): KeySpecBuilder fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder - fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder + fun overridePreferredSymmetricKeyAlgorithms( + vararg algorithms: SymmetricKeyAlgorithm + ): KeySpecBuilder fun setKeyCreationDate(creationDate: Date): KeySpecBuilder fun build(): KeySpec -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt index 377fbb94..1ff63604 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyLength.kt @@ -7,4 +7,4 @@ package org.pgpainless.key.generation.type interface KeyLength { val length: Int -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyType.kt index 105962b9..bc1497f9 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyType.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/KeyType.kt @@ -4,6 +4,7 @@ package org.pgpainless.key.generation.type +import java.security.spec.AlgorithmParameterSpec import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.key.generation.type.ecc.EllipticCurve import org.pgpainless.key.generation.type.ecc.ecdh.ECDH @@ -14,7 +15,6 @@ import org.pgpainless.key.generation.type.rsa.RSA import org.pgpainless.key.generation.type.rsa.RsaLength import org.pgpainless.key.generation.type.xdh.XDH import org.pgpainless.key.generation.type.xdh.XDHSpec -import java.security.spec.AlgorithmParameterSpec @Suppress("INAPPLICABLE_JVM_NAME") // https://youtrack.jetbrains.com/issue/KT-31420 interface KeyType { @@ -35,20 +35,22 @@ interface KeyType { /** * Return the strength of the key in bits. + * * @return strength of the key in bits */ val bitStrength: Int /** - * Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the key. + * Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the + * key. * * @return algorithm parameter spec */ val algorithmSpec: AlgorithmParameterSpec /** - * Return true if the key that is generated from this type is able to carry the SIGN_DATA key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}. + * Return true if the key that is generated from this type is able to carry the SIGN_DATA key + * flag. See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}. * * @return true if the key can sign. */ @@ -56,8 +58,8 @@ interface KeyType { @JvmName("canSign") get() = algorithm.signingCapable /** - * Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}. + * Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER + * key flag. See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}. * * @return true if the key is able to certify other keys */ @@ -65,8 +67,8 @@ interface KeyType { @JvmName("canCertify") get() = canSign /** - * Return true if the key that is generated from this type is able to carry the AUTHENTICATION key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}. + * Return true if the key that is generated from this type is able to carry the AUTHENTICATION + * key flag. See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}. * * @return true if the key can be used for authentication purposes. */ @@ -74,8 +76,8 @@ interface KeyType { @JvmName("canAuthenticate") get() = canSign /** - * Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}. + * Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS + * key flag. See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}. * * @return true if the key can encrypt communication */ @@ -83,8 +85,8 @@ interface KeyType { @JvmName("canEncryptCommunication") get() = algorithm.encryptionCapable /** - * Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag. - * See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}. + * Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE + * key flag. See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}. * * @return true if the key can encrypt for storage */ @@ -92,19 +94,14 @@ interface KeyType { @JvmName("canEncryptStorage") get() = algorithm.encryptionCapable companion object { - @JvmStatic - fun RSA(length: RsaLength): RSA = RSA.withLength(length) + @JvmStatic fun RSA(length: RsaLength): RSA = RSA.withLength(length) - @JvmStatic - fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve) + @JvmStatic fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve) - @JvmStatic - fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve) + @JvmStatic fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve) - @JvmStatic - fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve) + @JvmStatic fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve) - @JvmStatic - fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve) + @JvmStatic fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/EllipticCurve.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/EllipticCurve.kt index 287df67f..d9b51cb3 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/EllipticCurve.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/EllipticCurve.kt @@ -4,19 +4,20 @@ package org.pgpainless.key.generation.type.ecc - /** * Elliptic curves for use with [org.pgpainless.key.generation.type.ecc.ecdh.ECDH] and - * [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA]. - * For curve25519 related curve definitions see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve]. + * [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA]. For curve25519 related curve definitions + * see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve]. */ -enum class EllipticCurve( - val curveName: String, - val bitStrength: Int -) { - _P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see https://tools.ietf.org/search/rfc4492#page-32 - _P384("secp384r1", 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32 - _P521("secp521r1", 521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32 +enum class EllipticCurve(val curveName: String, val bitStrength: Int) { + _P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see + // https://tools.ietf.org/search/rfc4492#page-32 + _P384( + "secp384r1", + 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32 + _P521( + "secp521r1", + 521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32 _SECP256K1("secp256k1", 256), _BRAINPOOLP256R1("brainpoolP256r1", 256), _BRAINPOOLP384R1("brainpoolP384r1", 384), @@ -24,4 +25,4 @@ enum class EllipticCurve( ; fun getName(): String = curveName -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.kt index 6bab2fcc..04e196e0 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.kt @@ -16,7 +16,6 @@ class ECDH private constructor(val curve: EllipticCurve) : KeyType { override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) companion object { - @JvmStatic - fun fromCurve(curve: EllipticCurve) = ECDH(curve) + @JvmStatic fun fromCurve(curve: EllipticCurve) = ECDH(curve) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.kt index b7a0b94f..1784b49d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.kt @@ -16,7 +16,6 @@ class ECDSA private constructor(val curve: EllipticCurve) : KeyType { override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) companion object { - @JvmStatic - fun fromCurve(curve: EllipticCurve) = ECDSA(curve) + @JvmStatic fun fromCurve(curve: EllipticCurve) = ECDSA(curve) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSA.kt index d1e51a8e..6130328a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSA.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSA.kt @@ -15,7 +15,6 @@ class EdDSA private constructor(val curve: EdDSACurve) : KeyType { override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) companion object { - @JvmStatic - fun fromCurve(curve: EdDSACurve) = EdDSA(curve) + @JvmStatic fun fromCurve(curve: EdDSACurve) = EdDSA(curve) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSACurve.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSACurve.kt index 52b6949b..943c8237 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSACurve.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/eddsa/EdDSACurve.kt @@ -4,11 +4,9 @@ package org.pgpainless.key.generation.type.eddsa -enum class EdDSACurve( - val curveName: String, - val bitStrength: Int) { +enum class EdDSACurve(val curveName: String, val bitStrength: Int) { _Ed25519("ed25519", 256), ; fun getName() = curveName -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamal.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamal.kt index 6cfbc8a7..d925fc3d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamal.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamal.kt @@ -22,7 +22,6 @@ class ElGamal private constructor(length: ElGamalLength) : KeyType { override val algorithmSpec = ElGamalParameterSpec(length.p, length.g) companion object { - @JvmStatic - fun withLength(length: ElGamalLength) = ElGamal(length) + @JvmStatic fun withLength(length: ElGamalLength) = ElGamal(length) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamalLength.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamalLength.kt index 9eae195c..2d29b88d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamalLength.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/elgamal/ElGamalLength.kt @@ -4,61 +4,54 @@ package org.pgpainless.key.generation.type.elgamal -import org.pgpainless.key.generation.type.KeyLength import java.math.BigInteger +import org.pgpainless.key.generation.type.KeyLength /** * The following primes are taken from RFC-3526. * - * @see - * RFC-3526: More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE) - * + * @see RFC-3526: More Modular Exponential (MODP) + * Diffie-Hellman groups for Internet Key Exchange (IKE) * @deprecated the use of ElGamal keys is no longer recommended. */ - @Deprecated("The use of ElGamal keys is no longer recommended.") -enum class ElGamalLength( - override val length: Int, - p: String, - g: String -) : KeyLength { +enum class ElGamalLength(override val length: Int, p: String, g: String) : KeyLength { - /** - * prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }. - * generator: 2 - */ - _1536(1536, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", "2"), + /** prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }. generator: 2 */ + _1536( + 1536, + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", + "2"), - /** - * prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }. - * generator: 2 - */ - _2048(2048, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", "2"), + /** prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }. generator: 2 */ + _2048( + 2048, + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", + "2"), - /** - * prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }. - * generator: 2 - */ - _3072(3072, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", "2"), + /** prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }. generator: 2 */ + _3072( + 3072, + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + "2"), - /** - * prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }. - * generator: 2 - */ - _4096(4096, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", "2"), + /** prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }. generator: 2 */ + _4096( + 4096, + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", + "2"), - /** - * prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }. - * generator: 2 - */ - _6144(6144, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF", "2"), + /** prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }. generator: 2 */ + _6144( + 6144, + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF", + "2"), - /** - * prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }. - * generator: 2 - */ - _8192(8192, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF", "2") - ; + /** prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }. generator: 2 */ + _8192( + 8192, + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF", + "2"); val p: BigInteger val g: BigInteger @@ -67,6 +60,4 @@ enum class ElGamalLength( this.p = BigInteger(p, 16) this.g = BigInteger(g, 16) } - - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RSA.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RSA.kt index 1f8c0509..39ddbbbb 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RSA.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RSA.kt @@ -4,14 +4,12 @@ package org.pgpainless.key.generation.type.rsa +import java.security.spec.RSAKeyGenParameterSpec import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.key.generation.type.KeyType -import java.security.spec.RSAKeyGenParameterSpec -/** - * Key type that specifies the RSA_GENERAL algorithm. - */ -class RSA private constructor(length: RsaLength): KeyType { +/** Key type that specifies the RSA_GENERAL algorithm. */ +class RSA private constructor(length: RsaLength) : KeyType { override val name = "RSA" override val algorithm = PublicKeyAlgorithm.RSA_GENERAL @@ -19,7 +17,6 @@ class RSA private constructor(length: RsaLength): KeyType { override val algorithmSpec = RSAKeyGenParameterSpec(length.length, RSAKeyGenParameterSpec.F4) companion object { - @JvmStatic - fun withLength(length: RsaLength) = RSA(length) + @JvmStatic fun withLength(length: RsaLength) = RSA(length) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RsaLength.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RsaLength.kt index ae8bb804..7837a1f5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RsaLength.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/rsa/RsaLength.kt @@ -14,4 +14,4 @@ enum class RsaLength(override val length: Int) : KeyLength { _3072(3072), _4096(4096), _8192(8192) -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDH.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDH.kt index 06888237..8a95fc3b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDH.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDH.kt @@ -8,14 +8,13 @@ import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.key.generation.type.KeyType -class XDH private constructor(spec: XDHSpec): KeyType { +class XDH private constructor(spec: XDHSpec) : KeyType { override val name = "XDH" override val algorithm = PublicKeyAlgorithm.ECDH override val bitStrength = spec.bitStrength override val algorithmSpec = ECNamedCurveGenParameterSpec(spec.algorithmName) companion object { - @JvmStatic - fun fromSpec(spec: XDHSpec) = XDH(spec) + @JvmStatic fun fromSpec(spec: XDHSpec) = XDH(spec) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDHSpec.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDHSpec.kt index 9486365f..36fcfcaa 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDHSpec.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/type/xdh/XDHSpec.kt @@ -4,12 +4,9 @@ package org.pgpainless.key.generation.type.xdh -enum class XDHSpec( - val algorithmName: String, - val curveName: String, - val bitStrength: Int) { +enum class XDHSpec(val algorithmName: String, val curveName: String, val bitStrength: Int) { _X25519("X25519", "curve25519", 256), ; fun getName() = algorithmName -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt index f49a6ca4..a6891f0f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt @@ -11,78 +11,79 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil -abstract class KeyAccessor( - protected val info: KeyRingInfo, - protected val key: SubkeyIdentifier -) { +abstract class KeyAccessor(protected val info: KeyRingInfo, protected val key: SubkeyIdentifier) { /** - * Depending on the way we address the key (key-id or user-id), return the respective [PGPSignature] - * which contains the algorithm preferences we are going to use. + * Depending on the way we address the key (key-id or user-id), return the respective + * [PGPSignature] which contains the algorithm preferences we are going to use. + * *

- * If we address a key via its user-id, we want to rely on the algorithm preferences in the user-id certification, - * while we would instead rely on those in the direct-key signature if we'd address the key by key-id. + * If we address a key via its user-id, we want to rely on the algorithm preferences in the + * user-id certification, while we would instead rely on those in the direct-key signature if + * we'd address the key by key-id. * * @return signature */ abstract val signatureWithPreferences: PGPSignature - /** - * Preferred symmetric key encryption algorithms. - */ + /** Preferred symmetric key encryption algorithms. */ val preferredSymmetricKeyAlgorithms: Set - get() = SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences) + get() = + SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences) - /** - * Preferred hash algorithms. - */ + /** Preferred hash algorithms. */ val preferredHashAlgorithms: Set get() = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signatureWithPreferences) - /** - * Preferred compression algorithms. - */ + /** Preferred compression algorithms. */ val preferredCompressionAlgorithms: Set - get() = SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences) + get() = + SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences) /** - * Address the key via a user-id (e.g. `Alice `). - * In this case we are sourcing preferred algorithms from the user-id certification first. + * Address the key via a user-id (e.g. `Alice `). In this case we are + * sourcing preferred algorithms from the user-id certification first. */ - class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence): KeyAccessor(info, key) { + class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence) : + KeyAccessor(info, key) { override val signatureWithPreferences: PGPSignature - get() = checkNotNull(info.getLatestUserIdCertification(userId.toString())) { - "No valid user-id certification signature found for '$userId'." - } + get() = + checkNotNull(info.getLatestUserIdCertification(userId.toString())) { + "No valid user-id certification signature found for '$userId'." + } } /** - * Address the key via key-id. - * In this case we are sourcing preferred algorithms from the keys direct-key signature first. + * Address the key via key-id. In this case we are sourcing preferred algorithms from the keys + * direct-key signature first. */ class ViaKeyId(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) { override val signatureWithPreferences: PGPSignature get() { - // If the key is located by Key ID, the algorithm of the primary User ID of the key provides the + // If the key is located by Key ID, the algorithm of the primary User ID of the key + // provides the // preferred symmetric algorithm. - info.primaryUserId?.let { - userId -> info.getLatestUserIdCertification(userId).let { if (it != null) return it } + info.primaryUserId?.let { userId -> + info.getLatestUserIdCertification(userId).let { if (it != null) return it } } - return checkNotNull(info.latestDirectKeySelfSignature) { "No valid signature found." } + return checkNotNull(info.latestDirectKeySelfSignature) { + "No valid signature found." + } } } - class SubKey(info: KeyRingInfo, key: SubkeyIdentifier): KeyAccessor(info, key) { + class SubKey(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) { override val signatureWithPreferences: PGPSignature - get() = checkNotNull( + get() = + checkNotNull( if (key.isPrimaryKey) { - info.latestDirectKeySelfSignature ?: - info.primaryUserId?.let { info.getLatestUserIdCertification(it) } + info.latestDirectKeySelfSignature + ?: info.primaryUserId?.let { info.getLatestUserIdCertification(it) } } else { info.getCurrentSubkeyBindingSignature(key.subkeyId) + }) { + "No valid signature found." } - ) { "No valid signature found." } - } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt index 652cf22d..f510af3e 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt @@ -12,64 +12,70 @@ import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSecretKey @Deprecated("Deprecated in favor of extension functions to PGPSecretKey and PGPPublicKey.") -class KeyInfo private constructor( - val secretKey: PGPSecretKey?, - val publicKey: PGPPublicKey) { +class KeyInfo private constructor(val secretKey: PGPSecretKey?, val publicKey: PGPPublicKey) { - constructor(secretKey: PGPSecretKey): this(secretKey, secretKey.publicKey) - constructor(publicKey: PGPPublicKey): this(null, publicKey) + constructor(secretKey: PGPSecretKey) : this(secretKey, secretKey.publicKey) + + constructor(publicKey: PGPPublicKey) : this(null, publicKey) /** - * Return the name of the elliptic curve used by this key, or throw an [IllegalArgumentException] if the key - * is not based on elliptic curves, or on an unknown curve. + * Return the name of the elliptic curve used by this key, or throw an + * [IllegalArgumentException] if the key is not based on elliptic curves, or on an unknown + * curve. */ - @Deprecated("Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.", - ReplaceWith("publicKey.getCurveName()")) + @Deprecated( + "Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.", + ReplaceWith("publicKey.getCurveName()")) val curveName: String get() = publicKey.getCurveName() /** - * Return true, if the secret key is encrypted. - * This method returns false, if the secret key is null. + * Return true, if the secret key is encrypted. This method returns false, if the secret key is + * null. */ - @Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", - ReplaceWith("secretKey.isEncrypted()")) + @Deprecated( + "Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isEncrypted()")) val isEncrypted: Boolean get() = secretKey?.isEncrypted() ?: false /** - * Return true, if the secret key is decrypted. - * This method returns true, if the secret key is null. + * Return true, if the secret key is decrypted. This method returns true, if the secret key is + * null. */ - @Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", - ReplaceWith("secretKey.isDecrypted()")) + @Deprecated( + "Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isDecrypted()")) val isDecrypted: Boolean get() = secretKey?.isDecrypted() ?: true /** - * Return true, if the secret key is using the GNU_DUMMY_S2K s2k type. - * This method returns false, if the secret key is null. + * Return true, if the secret key is using the GNU_DUMMY_S2K s2k type. This method returns + * false, if the secret key is null. */ - @Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", - ReplaceWith("secretKey.hasDummyS2K()")) + @Deprecated( + "Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", + ReplaceWith("secretKey.hasDummyS2K()")) val hasDummyS2K: Boolean - @JvmName("hasDummyS2K") - get() = secretKey?.hasDummyS2K() ?: false + @JvmName("hasDummyS2K") get() = secretKey?.hasDummyS2K() ?: false companion object { @JvmStatic - @Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", - ReplaceWith("secretKey.isEncrypted()")) + @Deprecated( + "Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isEncrypted()")) fun isEncrypted(secretKey: PGPSecretKey?) = secretKey.isEncrypted() @JvmStatic - @Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", - ReplaceWith("secretKey.isDecrypted()")) + @Deprecated( + "Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isDecrypted()")) fun isDecrypted(secretKey: PGPSecretKey?) = secretKey.isDecrypted() @JvmStatic - @Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", - ReplaceWith("secretKey.hasDummyS2K()")) + @Deprecated( + "Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", + ReplaceWith("secretKey.hasDummyS2K()")) fun hasDummyS2K(secretKey: PGPSecretKey?) = secretKey.hasDummyS2K() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt index 208d8060..9f0bbc87 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt @@ -4,6 +4,7 @@ package org.pgpainless.key.info +import java.util.* import openpgp.openPgpKeyId import org.bouncycastle.extensions.* import org.bouncycastle.openpgp.* @@ -19,240 +20,210 @@ import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil.Companion.getKeyExpirationTimeAsDate import org.pgpainless.util.DateUtil import org.slf4j.LoggerFactory -import java.util.* class KeyRingInfo( - val keys: PGPKeyRing, - val policy: Policy = PGPainless.getPolicy(), - val referenceDate: Date = Date()) { + val keys: PGPKeyRing, + val policy: Policy = PGPainless.getPolicy(), + val referenceDate: Date = Date() +) { @JvmOverloads - constructor(keys: PGPKeyRing, referenceDate: Date = Date()): this(keys, PGPainless.getPolicy(), referenceDate) + constructor( + keys: PGPKeyRing, + referenceDate: Date = Date() + ) : this(keys, PGPainless.getPolicy(), referenceDate) private val signatures: Signatures = Signatures(keys, referenceDate, policy) - /** - * Primary {@link PGPPublicKey}.´ - */ + /** Primary {@link PGPPublicKey}.´ */ val publicKey: PGPPublicKey = KeyRingUtils.requirePrimaryPublicKeyFrom(keys) - /** - * Primary key ID. - */ + /** Primary key ID. */ val keyId: Long = publicKey.keyID - /** - * Primary key fingerprint. - */ + /** Primary key fingerprint. */ val fingerprint: OpenPgpFingerprint = OpenPgpFingerprint.of(keys) - /** - * All User-IDs (valid, expired, revoked). - */ + /** All User-IDs (valid, expired, revoked). */ val userIds: List = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey) - /** - * Primary User-ID. - */ + /** Primary User-ID. */ val primaryUserId = findPrimaryUserId() - /** - * Revocation State. - */ + /** Revocation State. */ val revocationState = signatures.primaryKeyRevocation.toRevocationState() /** * Return the date on which the primary key was revoked, or null if it has not yet been revoked. * * @return revocation date or null */ - val revocationDate: Date? = if (revocationState.isSoftRevocation()) revocationState.date else null + val revocationDate: Date? = + if (revocationState.isSoftRevocation()) revocationState.date else null /** * Primary [PGPSecretKey] of this key ring or null if the key ring is not a [PGPSecretKeyRing]. */ - val secretKey: PGPSecretKey? = when(keys) { - is PGPSecretKeyRing -> keys.secretKey!! - else -> null - } + val secretKey: PGPSecretKey? = + when (keys) { + is PGPSecretKeyRing -> keys.secretKey!! + else -> null + } - /** - * OpenPGP key version. - */ + /** OpenPGP key version. */ val version: Int = publicKey.version /** - * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. - * The first key in the list being the primary key. - * Note that the list is unmodifiable. + * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. The first key in the list + * being the primary key. Note that the list is unmodifiable. * * @return list of public keys */ val publicKeys: List = keys.publicKeys.asSequence().toList() - /** - * All secret keys. - * If the key ring is a [PGPPublicKeyRing], then return an empty list. - */ - val secretKeys: List = when(keys) { - is PGPSecretKeyRing -> keys.secretKeys.asSequence().toList() - else -> listOf() - } + /** All secret keys. If the key ring is a [PGPPublicKeyRing], then return an empty list. */ + val secretKeys: List = + when (keys) { + is PGPSecretKeyRing -> keys.secretKeys.asSequence().toList() + else -> listOf() + } - /** - * List of valid public subkeys. - */ - val validSubkeys: List = keys.publicKeys.asSequence() - .filter { isKeyValidlyBound(it.keyID) } - .toList() + /** List of valid public subkeys. */ + val validSubkeys: List = + keys.publicKeys.asSequence().filter { isKeyValidlyBound(it.keyID) }.toList() - /** - * List of valid user-IDs. - */ + /** List of valid user-IDs. */ val validUserIds: List = userIds.filter { isUserIdBound(it) } - /** - * List of valid and expired user-IDs. - */ - val validAndExpiredUserIds: List = userIds.filter { - val certification = signatures.userIdCertifications[it] ?: return@filter false - val revocation = signatures.userIdRevocations[it] ?: return@filter true - return@filter !revocation.isHardRevocation && certification.creationTime > revocation.creationTime - } + /** List of valid and expired user-IDs. */ + val validAndExpiredUserIds: List = + userIds.filter { + val certification = signatures.userIdCertifications[it] ?: return@filter false + val revocation = signatures.userIdRevocations[it] ?: return@filter true + return@filter !revocation.isHardRevocation && + certification.creationTime > revocation.creationTime + } - /** - * List of email addresses that can be extracted from the user-IDs. - */ - val emailAddresses: List = userIds.mapNotNull { - PATTERN_EMAIL_FROM_USERID.matcher(it).let { m1 -> - if (m1.find()) m1.group(1) - else PATTERN_EMAIL_EXPLICIT.matcher(it).let { m2 -> - if(m2.find()) m2.group(1) else null + /** List of email addresses that can be extracted from the user-IDs. */ + val emailAddresses: List = + userIds.mapNotNull { + PATTERN_EMAIL_FROM_USERID.matcher(it).let { m1 -> + if (m1.find()) m1.group(1) + else + PATTERN_EMAIL_EXPLICIT.matcher(it).let { m2 -> + if (m2.find()) m2.group(1) else null + } } } - } - /** - * Newest direct-key self-signature on the primary key. - */ + /** Newest direct-key self-signature on the primary key. */ val latestDirectKeySelfSignature: PGPSignature? = signatures.primaryKeySelfSignature - /** - * Newest primary-key revocation self-signature. - */ + /** Newest primary-key revocation self-signature. */ val revocationSelfSignature: PGPSignature? = signatures.primaryKeyRevocation - /** - * Public-key encryption-algorithm of the primary key. - */ + /** Public-key encryption-algorithm of the primary key. */ val algorithm: PublicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(publicKey.algorithm) - /** - * Creation date of the primary key. - */ + /** Creation date of the primary key. */ val creationDate: Date = publicKey.creationTime!! - /** - * Latest date at which the key was modified (either by adding a subkey or self-signature). - */ + /** Latest date at which the key was modified (either by adding a subkey or self-signature). */ val lastModified: Date = getMostRecentSignature()?.creationTime ?: getLatestKeyCreationDate() - /** - * True, if the underlying keyring is a [PGPSecretKeyRing]. - */ + /** True, if the underlying keyring is a [PGPSecretKeyRing]. */ val isSecretKey: Boolean = keys is PGPSecretKeyRing - /** - * True, if there are no encrypted secret keys. - */ - val isFullyDecrypted: Boolean = !isSecretKey || secretKeys.all { it.hasDummyS2K() || it.isDecrypted() } + /** True, if there are no encrypted secret keys. */ + val isFullyDecrypted: Boolean = + !isSecretKey || secretKeys.all { it.hasDummyS2K() || it.isDecrypted() } - /** - * True, if there are only encrypted secret keys. - */ - val isFullyEncrypted: Boolean = isSecretKey && secretKeys.none { !it.hasDummyS2K() && it.isDecrypted() } + /** True, if there are only encrypted secret keys. */ + val isFullyEncrypted: Boolean = + isSecretKey && secretKeys.none { !it.hasDummyS2K() && it.isDecrypted() } - /** - * List of public keys, whose secret key counterparts can be used to decrypt messages. - */ - val decryptionSubkeys: List = keys.publicKeys.asSequence().filter { - if (it.keyID != keyId) { - if (signatures.subkeyBindings[it.keyID] == null) { - LOGGER.debug("Subkey ${it.keyID.openPgpKeyId()} has no binding signature.") - return@filter false + /** List of public keys, whose secret key counterparts can be used to decrypt messages. */ + val decryptionSubkeys: List = + keys.publicKeys + .asSequence() + .filter { + if (it.keyID != keyId) { + if (signatures.subkeyBindings[it.keyID] == null) { + LOGGER.debug("Subkey ${it.keyID.openPgpKeyId()} has no binding signature.") + return@filter false + } + } + if (!it.isEncryptionKey) { + LOGGER.debug("(Sub-?)Key ${it.keyID.openPgpKeyId()} is not encryption-capable.") + return@filter false + } + return@filter true } - } - if (!it.isEncryptionKey) { - LOGGER.debug("(Sub-?)Key ${it.keyID.openPgpKeyId()} is not encryption-capable.") - return@filter false - } - return@filter true - }.toList() + .toList() - /** - * Expiration date of the primary key. - */ + /** Expiration date of the primary key. */ val primaryKeyExpirationDate: Date? get() { - val directKeyExpirationDate: Date? = latestDirectKeySelfSignature?.let { getKeyExpirationTimeAsDate(it, publicKey) } - val possiblyExpiredPrimaryUserId = getPossiblyExpiredPrimaryUserId() - val primaryUserIdCertification = possiblyExpiredPrimaryUserId?.let { getLatestUserIdCertification(it) } - val userIdExpirationDate: Date? = primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) } + val directKeyExpirationDate: Date? = + latestDirectKeySelfSignature?.let { getKeyExpirationTimeAsDate(it, publicKey) } + val possiblyExpiredPrimaryUserId = getPossiblyExpiredPrimaryUserId() + val primaryUserIdCertification = + possiblyExpiredPrimaryUserId?.let { getLatestUserIdCertification(it) } + val userIdExpirationDate: Date? = + primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) } - if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) { - throw NoSuchElementException("No direct-key signature and no user-id signature found.") + if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) { + throw NoSuchElementException( + "No direct-key signature and no user-id signature found.") + } + if (directKeyExpirationDate != null && userIdExpirationDate == null) { + return directKeyExpirationDate + } + if (directKeyExpirationDate == null) { + return userIdExpirationDate + } + return if (directKeyExpirationDate < userIdExpirationDate) directKeyExpirationDate + else userIdExpirationDate } - if (directKeyExpirationDate != null && userIdExpirationDate == null) { - return directKeyExpirationDate - } - if (directKeyExpirationDate == null) { - return userIdExpirationDate - } - return if (directKeyExpirationDate < userIdExpirationDate) - directKeyExpirationDate - else userIdExpirationDate - } - /** - * List of all subkeys that can be used to sign a message. - */ - val signingSubkeys: List = validSubkeys.filter { getKeyFlagsOf(it.keyID).contains(KeyFlag.SIGN_DATA) } + /** List of all subkeys that can be used to sign a message. */ + val signingSubkeys: List = + validSubkeys.filter { getKeyFlagsOf(it.keyID).contains(KeyFlag.SIGN_DATA) } - /** - * Whether the key is usable for encryption. - */ + /** Whether the key is usable for encryption. */ val isUsableForEncryption: Boolean = isUsableForEncryption(EncryptionPurpose.ANY) /** - * Whether the key is capable of signing messages. - * This field is also true, if the key contains a subkey that is capable of signing messages, but where the secret - * key is unavailable, e.g. because it was moved to a smart-card. + * Whether the key is capable of signing messages. This field is also true, if the key contains + * a subkey that is capable of signing messages, but where the secret key is unavailable, e.g. + * because it was moved to a smart-card. * * To check for keys that are actually usable to sign messages, use [isUsableForSigning]. */ val isSigningCapable: Boolean = isKeyValidlyBound(keyId) && signingSubkeys.isNotEmpty() - /** - * Whether the key is actually usable to sign messages. - */ - val isUsableForSigning: Boolean = isSigningCapable && signingSubkeys.any { isSecretKeyAvailable(it.keyID) } + /** Whether the key is actually usable to sign messages. */ + val isUsableForSigning: Boolean = + isSigningCapable && signingSubkeys.any { isSecretKeyAvailable(it.keyID) } - /** - * [HashAlgorithm] preferences of the primary user-ID or if absent, of the primary key. - */ + /** [HashAlgorithm] preferences of the primary user-ID or if absent, of the primary key. */ val preferredHashAlgorithms: Set - get() = primaryUserId?.let { getPreferredHashAlgorithms(it) } ?: getPreferredHashAlgorithms(keyId) + get() = + primaryUserId?.let { getPreferredHashAlgorithms(it) } + ?: getPreferredHashAlgorithms(keyId) /** * [SymmetricKeyAlgorithm] preferences of the primary user-ID or if absent of the primary key. */ val preferredSymmetricKeyAlgorithms: Set - get() = primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) } ?: getPreferredSymmetricKeyAlgorithms(keyId) + get() = + primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) } + ?: getPreferredSymmetricKeyAlgorithms(keyId) - /** - * [CompressionAlgorithm] preferences of the primary user-ID or if absent, the primary key. - */ + /** [CompressionAlgorithm] preferences of the primary user-ID or if absent, the primary key. */ val preferredCompressionAlgorithms: Set - get() = primaryUserId?.let { getPreferredCompressionAlgorithms(it) } ?: getPreferredCompressionAlgorithms(keyId) + get() = + primaryUserId?.let { getPreferredCompressionAlgorithms(it) } + ?: getPreferredCompressionAlgorithms(keyId) /** * Return the expiration date of the subkey with the provided fingerprint. @@ -272,13 +243,19 @@ class KeyRingInfo( */ fun getSubkeyExpirationDate(keyId: Long): Date? { if (publicKey.keyID == keyId) return primaryKeyExpirationDate - val subkey = getPublicKey(keyId) ?: throw NoSuchElementException("No subkey with key-ID ${keyId.openPgpKeyId()} found.") - val bindingSig = getCurrentSubkeyBindingSignature(keyId) ?: throw AssertionError("Subkey has no valid binding signature.") + val subkey = + getPublicKey(keyId) + ?: throw NoSuchElementException( + "No subkey with key-ID ${keyId.openPgpKeyId()} found.") + val bindingSig = + getCurrentSubkeyBindingSignature(keyId) + ?: throw AssertionError("Subkey has no valid binding signature.") return bindingSig.getKeyExpirationDate(subkey.creationTime) } /** - * Return the date after which the key can no longer be used to perform the given use-case, caused by expiration. + * Return the date after which the key can no longer be used to perform the given use-case, + * caused by expiration. * * @return expiration date for the given use-case */ @@ -289,17 +266,21 @@ class KeyRingInfo( val primaryKeyExpiration = primaryKeyExpirationDate val keysWithFlag: List = getKeysWithKeyFlag(use) - if (keysWithFlag.isEmpty()) throw NoSuchElementException("No key with the required key flag found.") + if (keysWithFlag.isEmpty()) + throw NoSuchElementException("No key with the required key flag found.") var nonExpiring = false - val latestSubkeyExpiration = keysWithFlag.map { key -> - getSubkeyExpirationDate(key.keyID).also { if (it == null) nonExpiring = true } - }.filterNotNull().maxByOrNull { it } + val latestSubkeyExpiration = + keysWithFlag + .map { key -> + getSubkeyExpirationDate(key.keyID).also { if (it == null) nonExpiring = true } + } + .filterNotNull() + .maxByOrNull { it } if (nonExpiring) return primaryKeyExpiration return if (primaryKeyExpiration == null) latestSubkeyExpiration - else if (latestSubkeyExpiration == null) - primaryKeyExpiration + else if (latestSubkeyExpiration == null) primaryKeyExpiration else minOf(primaryKeyExpiration, latestSubkeyExpiration) } @@ -318,17 +299,24 @@ class KeyRingInfo( * @param flag flag * @return keys with flag */ - fun getKeysWithKeyFlag(flag: KeyFlag): List = publicKeys.filter { getKeyFlagsOf(it.keyID).contains(flag) } + fun getKeysWithKeyFlag(flag: KeyFlag): List = + publicKeys.filter { getKeyFlagsOf(it.keyID).contains(flag) } /** * Return a list of all subkeys which can be used to encrypt a message for the given user-ID. * * @return encryption subkeys */ - fun getEncryptionSubkeys(userId: CharSequence?, purpose: EncryptionPurpose): List { + fun getEncryptionSubkeys( + userId: CharSequence?, + purpose: EncryptionPurpose + ): List { if (userId != null && !isUserIdValid(userId)) { - throw UnboundUserIdException(OpenPgpFingerprint.of(keys), userId.toString(), - getLatestUserIdCertification(userId), getUserIdRevocation(userId)) + throw UnboundUserIdException( + OpenPgpFingerprint.of(keys), + userId.toString(), + getLatestUserIdCertification(userId), + getUserIdRevocation(userId)) } return getEncryptionSubkeys(purpose) } @@ -341,42 +329,53 @@ class KeyRingInfo( fun getEncryptionSubkeys(purpose: EncryptionPurpose): List { primaryKeyExpirationDate?.let { if (it < referenceDate) { - LOGGER.debug("Certificate is expired: Primary key is expired on ${DateUtil.formatUTCDate(it)}") + LOGGER.debug( + "Certificate is expired: Primary key is expired on ${DateUtil.formatUTCDate(it)}") return listOf() } } - return keys.publicKeys.asSequence().filter { - if (!isKeyValidlyBound(it.keyID)) { - LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is not validly bound.") - return@filter false - } - - getSubkeyExpirationDate(it.keyID)?.let { exp -> - if (exp < referenceDate) { - LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is expired on ${DateUtil.formatUTCDate(exp)}.") + return keys.publicKeys + .asSequence() + .filter { + if (!isKeyValidlyBound(it.keyID)) { + LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is not validly bound.") return@filter false } - } - if (!it.isEncryptionKey) { - LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} algorithm is not capable of encryption.") - return@filter false - } + getSubkeyExpirationDate(it.keyID)?.let { exp -> + if (exp < referenceDate) { + LOGGER.debug( + "(Sub?)-Key ${it.keyID.openPgpKeyId()} is expired on ${DateUtil.formatUTCDate(exp)}.") + return@filter false + } + } - val keyFlags = getKeyFlagsOf(it.keyID) - when (purpose) { - EncryptionPurpose.COMMUNICATIONS -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) - EncryptionPurpose.STORAGE -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_STORAGE) - EncryptionPurpose.ANY -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || keyFlags.contains(KeyFlag.ENCRYPT_STORAGE) + if (!it.isEncryptionKey) { + LOGGER.debug( + "(Sub?)-Key ${it.keyID.openPgpKeyId()} algorithm is not capable of encryption.") + return@filter false + } + + val keyFlags = getKeyFlagsOf(it.keyID) + when (purpose) { + EncryptionPurpose.COMMUNICATIONS -> + return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) + EncryptionPurpose.STORAGE -> + return@filter keyFlags.contains(KeyFlag.ENCRYPT_STORAGE) + EncryptionPurpose.ANY -> + return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || + keyFlags.contains(KeyFlag.ENCRYPT_STORAGE) + } } - }.toList() + .toList() } /** * Return, whether the key is usable for encryption, given the purpose. * - * @return true, if the key can be used to encrypt a message according to the encryption-purpose. + * @return true, if the key can be used to encrypt a message according to the + * encryption-purpose. */ fun isUsableForEncryption(purpose: EncryptionPurpose): Boolean { return isKeyValidlyBound(keyId) && getEncryptionSubkeys(purpose).isNotEmpty() @@ -387,29 +386,30 @@ class KeyRingInfo( * * @return possibly expired primary user-ID */ - fun getPossiblyExpiredPrimaryUserId(): String? = primaryUserId ?: userIds - .mapNotNull { userId -> - getLatestUserIdCertification(userId)?.let { userId to it } - } - .sortedByDescending { it.second.creationTime } - .maxByOrNull { it.second.hashedSubPackets.isPrimaryUserID }?.first + fun getPossiblyExpiredPrimaryUserId(): String? = + primaryUserId + ?: userIds + .mapNotNull { userId -> getLatestUserIdCertification(userId)?.let { userId to it } } + .sortedByDescending { it.second.creationTime } + .maxByOrNull { it.second.hashedSubPackets.isPrimaryUserID } + ?.first - /** - * Return the most-recently created self-signature on the key. - */ + /** Return the most-recently created self-signature on the key. */ private fun getMostRecentSignature(): PGPSignature? = - setOfNotNull(latestDirectKeySelfSignature, revocationSelfSignature).asSequence() - .plus(signatures.userIdCertifications.values) - .plus(signatures.userIdRevocations.values) - .plus(signatures.subkeyBindings.values) - .plus(signatures.subkeyRevocations.values) - .maxByOrNull { creationDate } + setOfNotNull(latestDirectKeySelfSignature, revocationSelfSignature) + .asSequence() + .plus(signatures.userIdCertifications.values) + .plus(signatures.userIdRevocations.values) + .plus(signatures.subkeyBindings.values) + .plus(signatures.subkeyRevocations.values) + .maxByOrNull { creationDate } /** * Return the creation time of the latest added subkey. * * @return latest key creation time */ - fun getLatestKeyCreationDate(): Date = validSubkeys.maxByOrNull { creationDate }?.creationTime + fun getLatestKeyCreationDate(): Date = + validSubkeys.maxByOrNull { creationDate }?.creationTime ?: throw AssertionError("Apparently there is no validly bound key in this key ring.") /** @@ -417,57 +417,63 @@ class KeyRingInfo( * * @return latest self-certification for the given user-ID. */ - fun getLatestUserIdCertification(userId: CharSequence): PGPSignature? = signatures.userIdCertifications[userId] + fun getLatestUserIdCertification(userId: CharSequence): PGPSignature? = + signatures.userIdCertifications[userId] /** * Return the latest revocation self-signature for the given user-ID * * @return latest user-ID revocation for the given user-ID */ - fun getUserIdRevocation(userId: CharSequence): PGPSignature? = signatures.userIdRevocations[userId] + fun getUserIdRevocation(userId: CharSequence): PGPSignature? = + signatures.userIdRevocations[userId] /** * Return the current binding signature for the subkey with the given key-ID. * * @return current subkey binding signature */ - fun getCurrentSubkeyBindingSignature(keyId: Long): PGPSignature? = signatures.subkeyBindings[keyId] + fun getCurrentSubkeyBindingSignature(keyId: Long): PGPSignature? = + signatures.subkeyBindings[keyId] /** * Return the current revocation signature for the subkey with the given key-ID. * * @return current subkey revocation signature */ - fun getSubkeyRevocationSignature(keyId: Long): PGPSignature? = signatures.subkeyRevocations[keyId] + fun getSubkeyRevocationSignature(keyId: Long): PGPSignature? = + signatures.subkeyRevocations[keyId] /** * Return a list of {@link KeyFlag KeyFlags} that apply to the subkey with the provided key id. + * * @param keyId key-id * @return list of key flags */ fun getKeyFlagsOf(keyId: Long): List = - if (keyId == publicKey.keyID) { - latestDirectKeySelfSignature?.let { sig -> - SignatureSubpacketsUtil.parseKeyFlags(sig)?.let { flags -> - return flags - } + if (keyId == publicKey.keyID) { + latestDirectKeySelfSignature?.let { sig -> + SignatureSubpacketsUtil.parseKeyFlags(sig)?.let { flags -> + return flags } - - primaryUserId?.let { - SignatureSubpacketsUtil.parseKeyFlags(getLatestUserIdCertification(it))?.let { flags -> - return flags - } - } - listOf() - } else { - getCurrentSubkeyBindingSignature(keyId)?.let { - SignatureSubpacketsUtil.parseKeyFlags(it)?.let { flags -> - return flags - } - } - listOf() } + primaryUserId?.let { + SignatureSubpacketsUtil.parseKeyFlags(getLatestUserIdCertification(it))?.let { flags + -> + return flags + } + } + listOf() + } else { + getCurrentSubkeyBindingSignature(keyId)?.let { + SignatureSubpacketsUtil.parseKeyFlags(it)?.let { flags -> + return flags + } + } + listOf() + } + /** * Return a list of {@link KeyFlag KeyFlags} that apply to the given user-id. * @@ -475,13 +481,15 @@ class KeyRingInfo( * @return key flags */ fun getKeyFlagsOf(userId: CharSequence): List = - if (!isUserIdValid(userId)) { - listOf() - } else { - getLatestUserIdCertification(userId)?.let { - SignatureSubpacketsUtil.parseKeyFlags(it) ?: listOf() - } ?: throw AssertionError("While user-id '$userId' was reported as valid, there appears to be no certification for it.") + if (!isUserIdValid(userId)) { + listOf() + } else { + getLatestUserIdCertification(userId)?.let { + SignatureSubpacketsUtil.parseKeyFlags(it) ?: listOf() } + ?: throw AssertionError( + "While user-id '$userId' was reported as valid, there appears to be no certification for it.") + } /** * Return the public key with the given key id from the provided key ring. @@ -497,13 +505,15 @@ class KeyRingInfo( * @param keyId key id * @return secret key or null */ - fun getSecretKey(keyId: Long): PGPSecretKey? = when(keys) { - is PGPSecretKeyRing -> keys.getSecretKey(keyId) - else -> null - } + fun getSecretKey(keyId: Long): PGPSecretKey? = + when (keys) { + is PGPSecretKeyRing -> keys.getSecretKey(keyId) + else -> null + } /** - * Return true, if the secret-key with the given key-ID is available (i.e. not moved to a smart-card). + * Return true, if the secret-key with the given key-ID is available (i.e. not moved to a + * smart-card). * * @return availability of the secret key */ @@ -511,7 +521,8 @@ class KeyRingInfo( return getSecretKey(keyId)?.let { return if (it.s2K == null) true // Unencrypted key else it.s2K.type !in 100..110 // Secret key on smart-card - } ?: false // Missing secret key + } + ?: false // Missing secret key } /** @@ -520,7 +531,8 @@ class KeyRingInfo( * @param fingerprint fingerprint * @return public key or null */ - fun getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = keys.getPublicKey(fingerprint.keyId) + fun getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = + keys.getPublicKey(fingerprint.keyId) /** * Return the secret key with the given fingerprint. @@ -528,21 +540,21 @@ class KeyRingInfo( * @param fingerprint fingerprint * @return secret key or null */ - fun getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? = when(keys) { - is PGPSecretKeyRing -> keys.getSecretKey(fingerprint.keyId) - else -> null - } + fun getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? = + when (keys) { + is PGPSecretKeyRing -> keys.getSecretKey(fingerprint.keyId) + else -> null + } /** * Return the public key matching the given [SubkeyIdentifier]. * * @return public key - * @throws IllegalArgumentException if the identifier's primary key does not match the primary key of the key. + * @throws IllegalArgumentException if the identifier's primary key does not match the primary + * key of the key. */ fun getPublicKey(identifier: SubkeyIdentifier): PGPPublicKey? { - require(identifier.primaryKeyId == publicKey.keyID) { - "Mismatching primary key ID." - } + require(identifier.primaryKeyId == publicKey.keyID) { "Mismatching primary key ID." } return getPublicKey(identifier.subkeyId) } @@ -550,17 +562,19 @@ class KeyRingInfo( * Return the secret key matching the given [SubkeyIdentifier]. * * @return secret key - * @throws IllegalArgumentException if the identifier's primary key does not match the primary key of the key. + * @throws IllegalArgumentException if the identifier's primary key does not match the primary + * key of the key. */ - fun getSecretKey(identifier: SubkeyIdentifier): PGPSecretKey? = when(keys) { - is PGPSecretKeyRing -> { - require(identifier.primaryKeyId == publicKey.keyID) { - "Mismatching primary key ID." + fun getSecretKey(identifier: SubkeyIdentifier): PGPSecretKey? = + when (keys) { + is PGPSecretKeyRing -> { + require(identifier.primaryKeyId == publicKey.keyID) { + "Mismatching primary key ID." + } + keys.getSecretKey(identifier.subkeyId) } - keys.getSecretKey(identifier.subkeyId) + else -> null } - else -> null - } /** * Return true if the public key with the given key id is bound to the key ring properly. @@ -573,7 +587,8 @@ class KeyRingInfo( // Primary key -> Check Primary Key Revocation if (publicKey.keyID == this.publicKey.keyID) { - return if (signatures.primaryKeyRevocation != null && signatures.primaryKeyRevocation.isHardRevocation) { + return if (signatures.primaryKeyRevocation != null && + signatures.primaryKeyRevocation.isHardRevocation) { false } else signatures.primaryKeyRevocation == null } @@ -594,16 +609,18 @@ class KeyRingInfo( false } else { // Key is soft-revoked, not yet re-bound - (revocation.isExpired(referenceDate) || !revocation.creationTime.after(binding.creationTime)) + (revocation.isExpired(referenceDate) || + !revocation.creationTime.after(binding.creationTime)) } } else true } /** * Return the current primary user-id of the key ring. + * *

- * Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, - * this method returns the first user-id on the key, otherwise null. + * Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, this + * method returns the first user-id on the key, otherwise null. * * @return primary user-id or null */ @@ -612,94 +629,85 @@ class KeyRingInfo( return null } - return signatures.userIdCertifications.filter { (_, certification) -> - certification.hashedSubPackets.isPrimaryUserID - }.entries.maxByOrNull { (_, certification) -> - certification.creationTime - }?.key ?: signatures.userIdCertifications.keys.firstOrNull() + return signatures.userIdCertifications + .filter { (_, certification) -> certification.hashedSubPackets.isPrimaryUserID } + .entries + .maxByOrNull { (_, certification) -> certification.creationTime } + ?.key + ?: signatures.userIdCertifications.keys.firstOrNull() } - /** - * Return true, if the primary user-ID, as well as the given user-ID are valid and bound. - */ + /** Return true, if the primary user-ID, as well as the given user-ID are valid and bound. */ fun isUserIdValid(userId: CharSequence) = - if (primaryUserId == null) { - false - } else { - isUserIdBound(primaryUserId) && (if (userId == primaryUserId) true else isUserIdBound(userId)) - } + if (primaryUserId == null) { + false + } else { + isUserIdBound(primaryUserId) && + (if (userId == primaryUserId) true else isUserIdBound(userId)) + } - /** - * Return true, if the given user-ID is validly bound. - */ + /** Return true, if the given user-ID is validly bound. */ fun isUserIdBound(userId: CharSequence) = - signatures.userIdCertifications[userId]?.let { sig -> - if (sig.isExpired(referenceDate)) { - // certification expired - return false + signatures.userIdCertifications[userId]?.let { sig -> + if (sig.isExpired(referenceDate)) { + // certification expired + return false + } + if (sig.hashedSubPackets.isPrimaryUserID) { + getKeyExpirationTimeAsDate(sig, publicKey)?.let { expirationDate -> + // key expired? + if (expirationDate < referenceDate) return false } - if (sig.hashedSubPackets.isPrimaryUserID) { - getKeyExpirationTimeAsDate(sig, publicKey)?.let { expirationDate -> - // key expired? - if (expirationDate < referenceDate) return false - } + } + signatures.userIdRevocations[userId]?.let { rev -> + if (rev.isHardRevocation) { + return false // hard revoked -> invalid } - signatures.userIdRevocations[userId]?.let { rev -> - if (rev.isHardRevocation) { - return false // hard revoked -> invalid - } - sig.creationTime > rev.creationTime// re-certification after soft revocation? - } ?: true // certification, but no revocation - } ?: false // no certification + sig.creationTime > rev.creationTime // re-certification after soft revocation? + } + ?: true // certification, but no revocation + } + ?: false // no certification - /** - * [HashAlgorithm] preferences of the given user-ID. - */ + /** [HashAlgorithm] preferences of the given user-ID. */ fun getPreferredHashAlgorithms(userId: CharSequence): Set { return getKeyAccessor(userId, keyId).preferredHashAlgorithms } - /** - * [HashAlgorithm] preferences of the given key. - */ + /** [HashAlgorithm] preferences of the given key. */ fun getPreferredHashAlgorithms(keyId: Long): Set { return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredHashAlgorithms } - /** - * [SymmetricKeyAlgorithm] preferences of the given user-ID. - */ + /** [SymmetricKeyAlgorithm] preferences of the given user-ID. */ fun getPreferredSymmetricKeyAlgorithms(userId: CharSequence): Set { return getKeyAccessor(userId, keyId).preferredSymmetricKeyAlgorithms } - /** - * [SymmetricKeyAlgorithm] preferences of the given key. - */ + /** [SymmetricKeyAlgorithm] preferences of the given key. */ fun getPreferredSymmetricKeyAlgorithms(keyId: Long): Set { - return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredSymmetricKeyAlgorithms + return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)) + .preferredSymmetricKeyAlgorithms } - /** - * [CompressionAlgorithm] preferences of the given user-ID. - */ + /** [CompressionAlgorithm] preferences of the given user-ID. */ fun getPreferredCompressionAlgorithms(userId: CharSequence): Set { return getKeyAccessor(userId, keyId).preferredCompressionAlgorithms } - /** - * [CompressionAlgorithm] preferences of the given key. - */ + /** [CompressionAlgorithm] preferences of the given key. */ fun getPreferredCompressionAlgorithms(keyId: Long): Set { - return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredCompressionAlgorithms + return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)) + .preferredCompressionAlgorithms } val isUsableForThirdPartyCertification: Boolean = - isKeyValidlyBound(keyId) && getKeyFlagsOf(keyId).contains(KeyFlag.CERTIFY_OTHER) + isKeyValidlyBound(keyId) && getKeyFlagsOf(keyId).contains(KeyFlag.CERTIFY_OTHER) private fun getKeyAccessor(userId: CharSequence?, keyId: Long): KeyAccessor { if (getPublicKey(keyId) == null) { - throw NoSuchElementException("No subkey with key-id ${keyId.openPgpKeyId()} found on this key.") + throw NoSuchElementException( + "No subkey with key-id ${keyId.openPgpKeyId()} found on this key.") } if (userId != null && !userIds.contains(userId)) { throw NoSuchElementException("No user-id '$userId' found on this key.") @@ -713,26 +721,24 @@ class KeyRingInfo( companion object { - /** - * Evaluate the key for the given signature. - */ + /** Evaluate the key for the given signature. */ @JvmStatic - fun evaluateForSignature(keys: PGPKeyRing, signature: PGPSignature) = KeyRingInfo(keys, signature.creationTime!!) + fun evaluateForSignature(keys: PGPKeyRing, signature: PGPSignature) = + KeyRingInfo(keys, signature.creationTime!!) - private val PATTERN_EMAIL_FROM_USERID = "<([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)>".toPattern() - private val PATTERN_EMAIL_EXPLICIT = "^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$".toPattern() - - @JvmStatic - private val LOGGER = LoggerFactory.getLogger(KeyRingInfo::class.java) + private val PATTERN_EMAIL_FROM_USERID = + "<([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)>".toPattern() + private val PATTERN_EMAIL_EXPLICIT = + "^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$".toPattern() + @JvmStatic private val LOGGER = LoggerFactory.getLogger(KeyRingInfo::class.java) } - private class Signatures( - val keys: PGPKeyRing, - val referenceDate: Date, - val policy: Policy) { - val primaryKeyRevocation: PGPSignature? = SignaturePicker.pickCurrentRevocationSelfSignature(keys, policy, referenceDate) - val primaryKeySelfSignature: PGPSignature? = SignaturePicker.pickLatestDirectKeySignature(keys, policy, referenceDate) + private class Signatures(val keys: PGPKeyRing, val referenceDate: Date, val policy: Policy) { + val primaryKeyRevocation: PGPSignature? = + SignaturePicker.pickCurrentRevocationSelfSignature(keys, policy, referenceDate) + val primaryKeySelfSignature: PGPSignature? = + SignaturePicker.pickLatestDirectKeySignature(keys, policy, referenceDate) val userIdRevocations = mutableMapOf() val userIdCertifications = mutableMapOf() val subkeyRevocations = mutableMapOf() @@ -740,21 +746,21 @@ class KeyRingInfo( init { KeyRingUtils.getUserIdsIgnoringInvalidUTF8(keys.publicKey).forEach { userId -> - SignaturePicker.pickCurrentUserIdRevocationSignature(keys, userId, policy, referenceDate)?.let { - userIdRevocations[userId] = it - } - SignaturePicker.pickLatestUserIdCertificationSignature(keys, userId, policy, referenceDate)?.let { - userIdCertifications[userId] = it - } + SignaturePicker.pickCurrentUserIdRevocationSignature( + keys, userId, policy, referenceDate) + ?.let { userIdRevocations[userId] = it } + SignaturePicker.pickLatestUserIdCertificationSignature( + keys, userId, policy, referenceDate) + ?.let { userIdCertifications[userId] = it } } keys.publicKeys.asSequence().drop(1).forEach { subkey -> - SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keys, subkey, policy, referenceDate)?.let { - subkeyRevocations[subkey.keyID] = it - } - SignaturePicker.pickLatestSubkeyBindingSignature(keys, subkey, policy, referenceDate)?.let { - subkeyBindings[subkey.keyID] = it - } + SignaturePicker.pickCurrentSubkeyBindingRevocationSignature( + keys, subkey, policy, referenceDate) + ?.let { subkeyRevocations[subkey.keyID] = it } + SignaturePicker.pickLatestSubkeyBindingSignature( + keys, subkey, policy, referenceDate) + ?.let { subkeyBindings[subkey.keyID] = it } } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt index 3425145b..891d64e7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt @@ -4,6 +4,10 @@ package org.pgpainless.key.modification.secretkeyring +import java.util.* +import java.util.function.Predicate +import javax.annotation.Nonnull +import kotlin.NoSuchElementException import org.bouncycastle.bcpg.sig.KeyExpirationTime import org.bouncycastle.extensions.getKeyExpirationDate import org.bouncycastle.extensions.publicKeyAlgorithm @@ -30,19 +34,17 @@ import org.pgpainless.signature.builder.* import org.pgpainless.signature.subpackets.* import org.pgpainless.util.Passphrase import org.pgpainless.util.selection.userid.SelectUserId -import java.util.* -import java.util.function.Predicate -import javax.annotation.Nonnull -import kotlin.NoSuchElementException class SecretKeyRingEditor( - var secretKeyRing: PGPSecretKeyRing, - override val referenceTime: Date = Date() + var secretKeyRing: PGPSecretKeyRing, + override val referenceTime: Date = Date() ) : SecretKeyRingEditorInterface { - override fun addUserId(userId: CharSequence, - callback: SelfSignatureSubpackets.Callback?, - protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + override fun addUserId( + userId: CharSequence, + callback: SelfSignatureSubpackets.Callback?, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface { val sanitizedUserId = sanitizeUserId(userId).toString() val primaryKey = secretKeyRing.secretKey @@ -51,17 +53,28 @@ class SecretKeyRingEditor( "User-ID $userId is hard revoked and cannot be re-certified." } - val (hashAlgorithmPreferences, symmetricKeyAlgorithmPreferences, compressionAlgorithmPreferences) = try { - Triple(info.preferredHashAlgorithms, info.preferredSymmetricKeyAlgorithms, info.preferredCompressionAlgorithms) - } catch (e : IllegalStateException) { // missing user-id sig - val algorithmSuite = AlgorithmSuite.defaultAlgorithmSuite - Triple(algorithmSuite.hashAlgorithms, algorithmSuite.symmetricKeyAlgorithms, algorithmSuite.compressionAlgorithms) - } + val ( + hashAlgorithmPreferences, + symmetricKeyAlgorithmPreferences, + compressionAlgorithmPreferences) = + try { + Triple( + info.preferredHashAlgorithms, + info.preferredSymmetricKeyAlgorithms, + info.preferredCompressionAlgorithms) + } catch (e: IllegalStateException) { // missing user-id sig + val algorithmSuite = AlgorithmSuite.defaultAlgorithmSuite + Triple( + algorithmSuite.hashAlgorithms, + algorithmSuite.symmetricKeyAlgorithms, + algorithmSuite.compressionAlgorithms) + } - val builder = SelfSignatureBuilder(primaryKey, protector).apply { - hashedSubpackets.setSignatureCreationTime(referenceTime) - setSignatureType(SignatureType.POSITIVE_CERTIFICATION) - } + val builder = + SelfSignatureBuilder(primaryKey, protector).apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + setSignatureType(SignatureType.POSITIVE_CERTIFICATION) + } builder.hashedSubpackets.apply { setKeyFlags(info.getKeyFlagsOf(primaryKey.keyID)) setPreferredHashAlgorithms(hashAlgorithmPreferences) @@ -70,66 +83,109 @@ class SecretKeyRingEditor( setFeatures(Feature.MODIFICATION_DETECTION) } builder.applyCallback(callback) - secretKeyRing = injectCertification(secretKeyRing, sanitizedUserId, builder.build(primaryKey.publicKey, sanitizedUserId)) + secretKeyRing = + injectCertification( + secretKeyRing, + sanitizedUserId, + builder.build(primaryKey.publicKey, sanitizedUserId)) return this } - override fun addPrimaryUserId(userId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + override fun addPrimaryUserId( + userId: CharSequence, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface { val uid = sanitizeUserId(userId) val primaryKey = secretKeyRing.publicKey var info = inspectKeyRing(secretKeyRing, referenceTime) val primaryUserId = info.primaryUserId - val signature = if (primaryUserId == null) info.latestDirectKeySelfSignature else info.getLatestUserIdCertification(primaryUserId) + val signature = + if (primaryUserId == null) info.latestDirectKeySelfSignature + else info.getLatestUserIdCertification(primaryUserId) val previousKeyExpiration = signature?.getKeyExpirationDate(primaryKey.creationTime) // Add new primary user-id signature - addUserId(uid, object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { - hashedSubpackets.apply { - setPrimaryUserId() - if (previousKeyExpiration != null) setKeyExpirationTime(primaryKey, previousKeyExpiration) - else setKeyExpirationTime(null) + addUserId( + uid, + object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + hashedSubpackets.apply { + setPrimaryUserId() + if (previousKeyExpiration != null) + setKeyExpirationTime(primaryKey, previousKeyExpiration) + else setKeyExpirationTime(null) + } } - } - }, protector) + }, + protector) // unmark previous primary user-ids to be non-primary info = inspectKeyRing(secretKeyRing, referenceTime) - info.validAndExpiredUserIds.filterNot { it == uid }.forEach { otherUserId -> - if (info.getLatestUserIdCertification(otherUserId)!!.hashedSubPackets.isPrimaryUserID) { - // We need to unmark this user-id as primary - addUserId(otherUserId, object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { - hashedSubpackets.apply { - setPrimaryUserId(null) - setKeyExpirationTime(null) // non-primary - } - } - }, protector) + info.validAndExpiredUserIds + .filterNot { it == uid } + .forEach { otherUserId -> + if (info + .getLatestUserIdCertification(otherUserId)!! + .hashedSubPackets + .isPrimaryUserID) { + // We need to unmark this user-id as primary + addUserId( + otherUserId, + object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets( + hashedSubpackets: SelfSignatureSubpackets + ) { + hashedSubpackets.apply { + setPrimaryUserId(null) + setKeyExpirationTime(null) // non-primary + } + } + }, + protector) + } } - } return this } - @Deprecated("Use of SelectUserId class is deprecated.", replaceWith = ReplaceWith("removeUserId(protector, predicate)")) - override fun removeUserId(selector: SelectUserId, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { - return revokeUserIds(selector, protector, RevocationAttributes.createCertificateRevocation() + @Deprecated( + "Use of SelectUserId class is deprecated.", + replaceWith = ReplaceWith("removeUserId(protector, predicate)")) + override fun removeUserId( + selector: SelectUserId, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface { + return revokeUserIds( + selector, + protector, + RevocationAttributes.createCertificateRevocation() .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) .withoutDescription()) } - override fun removeUserId(protector: SecretKeyRingProtector, predicate: (String) -> Boolean): SecretKeyRingEditorInterface { - return revokeUserIds(protector, RevocationAttributes.createCertificateRevocation() + override fun removeUserId( + protector: SecretKeyRingProtector, + predicate: (String) -> Boolean + ): SecretKeyRingEditorInterface { + return revokeUserIds( + protector, + RevocationAttributes.createCertificateRevocation() .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) .withoutDescription(), - predicate) + predicate) } - override fun removeUserId(userId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + override fun removeUserId( + userId: CharSequence, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface { return removeUserId(protector) { uid -> userId == uid } } - override fun replaceUserId(oldUserId: CharSequence, newUserId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + override fun replaceUserId( + oldUserId: CharSequence, + newUserId: CharSequence, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface { val oldUID = sanitizeUserId(oldUserId) val newUID = sanitizeUserId(newUserId) require(oldUID.isNotBlank()) { "Old user-ID cannot be empty." } @@ -137,148 +193,230 @@ class SecretKeyRingEditor( val info = inspectKeyRing(secretKeyRing, referenceTime) if (!info.isUserIdValid(oldUID)) { - throw NoSuchElementException("Key does not carry user-ID '$oldUID', or it is not valid.") + throw NoSuchElementException( + "Key does not carry user-ID '$oldUID', or it is not valid.") } - val oldCertification = info.getLatestUserIdCertification(oldUID) + val oldCertification = + info.getLatestUserIdCertification(oldUID) ?: throw AssertionError("Certification for old user-ID MUST NOT be null.") - addUserId(newUID, object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { - SignatureSubpacketsHelper.applyFrom(oldCertification.hashedSubPackets, hashedSubpackets as SignatureSubpackets) - if (oldUID == info.primaryUserId && !oldCertification.hashedSubPackets.isPrimaryUserID) { - hashedSubpackets.setPrimaryUserId() + addUserId( + newUID, + object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + SignatureSubpacketsHelper.applyFrom( + oldCertification.hashedSubPackets, hashedSubpackets as SignatureSubpackets) + if (oldUID == info.primaryUserId && + !oldCertification.hashedSubPackets.isPrimaryUserID) { + hashedSubpackets.setPrimaryUserId() + } } - } - override fun modifyUnhashedSubpackets(unhashedSubpackets: SelfSignatureSubpackets) { - SignatureSubpacketsHelper.applyFrom(oldCertification.unhashedSubPackets, unhashedSubpackets as SignatureSubpackets) - } - }, protector) + override fun modifyUnhashedSubpackets(unhashedSubpackets: SelfSignatureSubpackets) { + SignatureSubpacketsHelper.applyFrom( + oldCertification.unhashedSubPackets, + unhashedSubpackets as SignatureSubpackets) + } + }, + protector) return revokeUserId(oldUID, protector) } - override fun addSubKey(keySpec: KeySpec, - subkeyPassphrase: Passphrase, - protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { - val callback = object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { - SignatureSubpacketsHelper.applyFrom(keySpec.subpackets, hashedSubpackets as SignatureSubpackets) + override fun addSubKey( + keySpec: KeySpec, + subkeyPassphrase: Passphrase, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface { + val callback = + object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + SignatureSubpacketsHelper.applyFrom( + keySpec.subpackets, hashedSubpackets as SignatureSubpackets) + } } - } return addSubKey(keySpec, subkeyPassphrase, callback, protector) } - override fun addSubKey(keySpec: KeySpec, - subkeyPassphrase: Passphrase, - callback: SelfSignatureSubpackets.Callback?, - protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + override fun addSubKey( + keySpec: KeySpec, + subkeyPassphrase: Passphrase, + callback: SelfSignatureSubpackets.Callback?, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface { val keyPair = KeyRingBuilder.generateKeyPair(keySpec) - val subkeyProtector = PasswordBasedSecretKeyRingProtector.forKeyId(keyPair.keyID, subkeyPassphrase) + val subkeyProtector = + PasswordBasedSecretKeyRingProtector.forKeyId(keyPair.keyID, subkeyPassphrase) val keyFlags = KeyFlag.fromBitmask(keySpec.subpackets.keyFlags).toMutableList() - return addSubKey(keyPair, callback, subkeyProtector, protector, keyFlags.removeFirst(), *keyFlags.toTypedArray()) + return addSubKey( + keyPair, + callback, + subkeyProtector, + protector, + keyFlags.removeFirst(), + *keyFlags.toTypedArray()) } - override fun addSubKey(subkey: PGPKeyPair, - callback: SelfSignatureSubpackets.Callback?, - subkeyProtector: SecretKeyRingProtector, - primaryKeyProtector: SecretKeyRingProtector, - keyFlag: KeyFlag, - vararg keyFlags: KeyFlag): SecretKeyRingEditorInterface { + override fun addSubKey( + subkey: PGPKeyPair, + callback: SelfSignatureSubpackets.Callback?, + subkeyProtector: SecretKeyRingProtector, + primaryKeyProtector: SecretKeyRingProtector, + keyFlag: KeyFlag, + vararg keyFlags: KeyFlag + ): SecretKeyRingEditorInterface { val flags = listOf(keyFlag).plus(keyFlags) val subkeyAlgorithm = subkey.publicKey.publicKeyAlgorithm SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm) val bitStrength = subkey.publicKey.bitStrength - require(PGPainless.getPolicy().publicKeyAlgorithmPolicy.isAcceptable(subkeyAlgorithm, bitStrength)) { - "Public key algorithm policy violation: $subkeyAlgorithm with bit strength $bitStrength is not acceptable." - } + require( + PGPainless.getPolicy() + .publicKeyAlgorithmPolicy + .isAcceptable(subkeyAlgorithm, bitStrength)) { + "Public key algorithm policy violation: $subkeyAlgorithm with bit strength $bitStrength is not acceptable." + } val primaryKey = secretKeyRing.secretKey val info = inspectKeyRing(secretKeyRing, referenceTime) - val hashAlgorithm = HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) + val hashAlgorithm = + HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) .negotiateHashAlgorithm(info.preferredHashAlgorithms) - var secretSubkey = PGPSecretKey(subkey.privateKey, subkey.publicKey, + var secretSubkey = + PGPSecretKey( + subkey.privateKey, + subkey.publicKey, ImplementationFactory.getInstance().v4FingerprintCalculator, - false, subkeyProtector.getEncryptor(subkey.keyID)) - val skBindingBuilder = SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector, hashAlgorithm) + false, + subkeyProtector.getEncryptor(subkey.keyID)) + val skBindingBuilder = + SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector, hashAlgorithm) skBindingBuilder.apply { hashedSubpackets.setSignatureCreationTime(referenceTime) hashedSubpackets.setKeyFlags(flags) if (subkeyAlgorithm.isSigningCapable()) { - val pkBindingBuilder = PrimaryKeyBindingSignatureBuilder(secretSubkey, subkeyProtector, hashAlgorithm) + val pkBindingBuilder = + PrimaryKeyBindingSignatureBuilder(secretSubkey, subkeyProtector, hashAlgorithm) pkBindingBuilder.hashedSubpackets.setSignatureCreationTime(referenceTime) hashedSubpackets.addEmbeddedSignature(pkBindingBuilder.build(primaryKey.publicKey)) } applyCallback(callback) } - secretSubkey = KeyRingUtils.secretKeyPlusSignature(secretSubkey, skBindingBuilder.build(secretSubkey.publicKey)) + secretSubkey = + KeyRingUtils.secretKeyPlusSignature( + secretSubkey, skBindingBuilder.build(secretSubkey.publicKey)) secretKeyRing = KeyRingUtils.keysPlusSecretKey(secretKeyRing, secretSubkey) return this } - override fun revoke(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { + override fun revoke( + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): SecretKeyRingEditorInterface { return revoke(protector, callbackFromRevocationAttributes(revocationAttributes)) } - override fun revoke(protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { + override fun revoke( + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback? + ): SecretKeyRingEditorInterface { return revokeSubKey(secretKeyRing.secretKey.keyID, protector, callback) } - override fun revokeSubKey(subkeyId: Long, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { - return revokeSubKey(subkeyId, protector, callbackFromRevocationAttributes(revocationAttributes)) + override fun revokeSubKey( + subkeyId: Long, + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): SecretKeyRingEditorInterface { + return revokeSubKey( + subkeyId, protector, callbackFromRevocationAttributes(revocationAttributes)) } - override fun revokeSubKey(subkeyId: Long, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { + override fun revokeSubKey( + subkeyId: Long, + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback? + ): SecretKeyRingEditorInterface { val revokeeSubKey = secretKeyRing.requirePublicKey(subkeyId) val subkeyRevocation = generateRevocation(protector, revokeeSubKey, callback) secretKeyRing = injectCertification(secretKeyRing, revokeeSubKey, subkeyRevocation) return this } - override fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { + override fun revokeUserId( + userId: CharSequence, + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): SecretKeyRingEditorInterface { if (revocationAttributes != null) { - require(revocationAttributes.reason == RevocationAttributes.Reason.NO_REASON || - revocationAttributes.reason == RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) { - "Revocation reason must either be NO_REASON or USER_ID_NO_LONGER_VALID" - } + require( + revocationAttributes.reason == RevocationAttributes.Reason.NO_REASON || + revocationAttributes.reason == + RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) { + "Revocation reason must either be NO_REASON or USER_ID_NO_LONGER_VALID" + } } - return revokeUserId(userId, protector, object : RevocationSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { - if (revocationAttributes != null) { - hashedSubpackets.setRevocationReason(false, revocationAttributes) + return revokeUserId( + userId, + protector, + object : RevocationSignatureSubpackets.Callback { + override fun modifyHashedSubpackets( + hashedSubpackets: RevocationSignatureSubpackets + ) { + if (revocationAttributes != null) { + hashedSubpackets.setRevocationReason(false, revocationAttributes) + } } - } - }) + }) } - override fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { + override fun revokeUserId( + userId: CharSequence, + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback? + ): SecretKeyRingEditorInterface { return revokeUserIds(protector, callback, SelectUserId.exactMatch(sanitizeUserId(userId))) } - override fun revokeUserIds(protector: SecretKeyRingProtector, - revocationAttributes: RevocationAttributes?, - predicate: (String) -> Boolean): SecretKeyRingEditorInterface { - return revokeUserIds(protector, object : RevocationSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { - if (revocationAttributes != null) hashedSubpackets.setRevocationReason(revocationAttributes) - } - }, predicate) + override fun revokeUserIds( + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes?, + predicate: (String) -> Boolean + ): SecretKeyRingEditorInterface { + return revokeUserIds( + protector, + object : RevocationSignatureSubpackets.Callback { + override fun modifyHashedSubpackets( + hashedSubpackets: RevocationSignatureSubpackets + ) { + if (revocationAttributes != null) + hashedSubpackets.setRevocationReason(revocationAttributes) + } + }, + predicate) } - override fun revokeUserIds(protector: SecretKeyRingProtector, - callback: RevocationSignatureSubpackets.Callback?, - predicate: (String) -> Boolean): SecretKeyRingEditorInterface { - selectUserIds(predicate).also { - if (it.isEmpty()) throw NoSuchElementException("No matching user-ids found on the key.") - }.forEach { userId -> doRevokeUserId(userId, protector, callback) } + override fun revokeUserIds( + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback?, + predicate: (String) -> Boolean + ): SecretKeyRingEditorInterface { + selectUserIds(predicate) + .also { + if (it.isEmpty()) + throw NoSuchElementException("No matching user-ids found on the key.") + } + .forEach { userId -> doRevokeUserId(userId, protector, callback) } return this } - override fun setExpirationDate(expiration: Date?, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { + override fun setExpirationDate( + expiration: Date?, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface { require(secretKeyRing.secretKey.isMasterKey) { "OpenPGP key does not appear to contain a primary secret key." } @@ -286,14 +424,19 @@ class SecretKeyRingEditor( val prevDirectKeySig = getPreviousDirectKeySignature() // reissue direct key sig if (prevDirectKeySig != null) { - secretKeyRing = injectCertification(secretKeyRing, secretKeyRing.publicKey, + secretKeyRing = + injectCertification( + secretKeyRing, + secretKeyRing.publicKey, reissueDirectKeySignature(expiration, protector, prevDirectKeySig)) } - val primaryUserId = inspectKeyRing(secretKeyRing, referenceTime).getPossiblyExpiredPrimaryUserId() + val primaryUserId = + inspectKeyRing(secretKeyRing, referenceTime).getPossiblyExpiredPrimaryUserId() if (primaryUserId != null) { val prevUserIdSig = getPreviousUserIdSignatures(primaryUserId) - val userIdSig = reissuePrimaryUserIdSig(expiration, protector, primaryUserId, prevUserIdSig!!) + val userIdSig = + reissuePrimaryUserIdSig(expiration, protector, primaryUserId, prevUserIdSig!!) secretKeyRing = injectCertification(secretKeyRing, primaryUserId, userIdSig) } @@ -303,9 +446,15 @@ class SecretKeyRingEditor( continue } - val prevUserIdSig = info.getLatestUserIdCertification(userId) ?: throw AssertionError("A valid user-id shall never have no user-id signature.") + val prevUserIdSig = + info.getLatestUserIdCertification(userId) + ?: throw AssertionError( + "A valid user-id shall never have no user-id signature.") if (prevUserIdSig.hashedSubPackets.isPrimaryUserID) { - secretKeyRing = injectCertification(secretKeyRing, primaryUserId!!, + secretKeyRing = + injectCertification( + secretKeyRing, + primaryUserId!!, reissueNonPrimaryUserId(protector, userId, prevUserIdSig)) } } @@ -313,7 +462,10 @@ class SecretKeyRingEditor( return this } - override fun createMinimalRevocationCertificate(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPPublicKeyRing { + override fun createMinimalRevocationCertificate( + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): PGPPublicKeyRing { // Check reason if (revocationAttributes != null) { require(RevocationAttributes.Reason.isKeyRevocation(revocationAttributes.reason)) { @@ -328,30 +480,67 @@ class SecretKeyRingEditor( return PGPPublicKeyRing(listOf(primaryKey)) } - override fun createRevocation(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { - return generateRevocation(protector, secretKeyRing.publicKey, callbackFromRevocationAttributes(revocationAttributes)) + override fun createRevocation( + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): PGPSignature { + return generateRevocation( + protector, + secretKeyRing.publicKey, + callbackFromRevocationAttributes(revocationAttributes)) } - override fun createRevocation(subkeyId: Long, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { - return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyId), callbackFromRevocationAttributes(revocationAttributes)) + override fun createRevocation( + subkeyId: Long, + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): PGPSignature { + return generateRevocation( + protector, + secretKeyRing.requirePublicKey(subkeyId), + callbackFromRevocationAttributes(revocationAttributes)) } - override fun createRevocation(subkeyId: Long, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): PGPSignature { + override fun createRevocation( + subkeyId: Long, + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback? + ): PGPSignature { return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyId), callback) } - override fun createRevocation(subkeyFingerprint: OpenPgpFingerprint, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { - return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyFingerprint), callbackFromRevocationAttributes(revocationAttributes)) + override fun createRevocation( + subkeyFingerprint: OpenPgpFingerprint, + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): PGPSignature { + return generateRevocation( + protector, + secretKeyRing.requirePublicKey(subkeyFingerprint), + callbackFromRevocationAttributes(revocationAttributes)) } - override fun changePassphraseFromOldPassphrase(oldPassphrase: Passphrase, oldProtectionSettings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { - return WithKeyRingEncryptionSettingsImpl(this, null, - PasswordBasedSecretKeyRingProtector(oldProtectionSettings, SolitaryPassphraseProvider(oldPassphrase))) + override fun changePassphraseFromOldPassphrase( + oldPassphrase: Passphrase, + oldProtectionSettings: KeyRingProtectionSettings + ): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { + return WithKeyRingEncryptionSettingsImpl( + this, + null, + PasswordBasedSecretKeyRingProtector( + oldProtectionSettings, SolitaryPassphraseProvider(oldPassphrase))) } - override fun changeSubKeyPassphraseFromOldPassphrase(keyId: Long, oldPassphrase: Passphrase, oldProtectionSettings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { - return WithKeyRingEncryptionSettingsImpl(this, keyId, - CachingSecretKeyRingProtector(mapOf(keyId to oldPassphrase), oldProtectionSettings, null)) + override fun changeSubKeyPassphraseFromOldPassphrase( + keyId: Long, + oldPassphrase: Passphrase, + oldProtectionSettings: KeyRingProtectionSettings + ): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { + return WithKeyRingEncryptionSettingsImpl( + this, + keyId, + CachingSecretKeyRingProtector( + mapOf(keyId to oldPassphrase), oldProtectionSettings, null)) } override fun done(): PGPSecretKeyRing { @@ -359,45 +548,52 @@ class SecretKeyRingEditor( } private fun sanitizeUserId(userId: CharSequence): CharSequence = - // TODO: Further research how to sanitize user IDs. - // e.g. what about newlines? - userId.toString().trim() + // TODO: Further research how to sanitize user IDs. + // e.g. what about newlines? + userId.toString().trim() private fun callbackFromRevocationAttributes(attributes: RevocationAttributes?) = - object : RevocationSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { - if (attributes != null) { - hashedSubpackets.setRevocationReason(attributes) - } + object : RevocationSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { + if (attributes != null) { + hashedSubpackets.setRevocationReason(attributes) } } + } - private fun generateRevocation(protector: SecretKeyRingProtector, - revokeeSubkey: PGPPublicKey, - callback: RevocationSignatureSubpackets.Callback?): PGPSignature { + private fun generateRevocation( + protector: SecretKeyRingProtector, + revokeeSubkey: PGPPublicKey, + callback: RevocationSignatureSubpackets.Callback? + ): PGPSignature { val primaryKey = secretKeyRing.secretKey val signatureType = - if (revokeeSubkey.isMasterKey) SignatureType.KEY_REVOCATION - else SignatureType.SUBKEY_REVOCATION + if (revokeeSubkey.isMasterKey) SignatureType.KEY_REVOCATION + else SignatureType.SUBKEY_REVOCATION return RevocationSignatureBuilder(signatureType, primaryKey, protector) - .apply { applyCallback(callback) } - .build(revokeeSubkey) + .apply { applyCallback(callback) } + .build(revokeeSubkey) } - private fun doRevokeUserId(userId: CharSequence, - protector: SecretKeyRingProtector, - callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { - RevocationSignatureBuilder(SignatureType.CERTIFICATION_REVOCATION, secretKeyRing.secretKey, protector).apply { - hashedSubpackets.setSignatureCreationTime(referenceTime) - applyCallback(callback) - }.let { - secretKeyRing = injectCertification(secretKeyRing, userId, it.build(userId.toString())) - } + private fun doRevokeUserId( + userId: CharSequence, + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback? + ): SecretKeyRingEditorInterface { + RevocationSignatureBuilder( + SignatureType.CERTIFICATION_REVOCATION, secretKeyRing.secretKey, protector) + .apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + applyCallback(callback) + } + .let { + secretKeyRing = + injectCertification(secretKeyRing, userId, it.build(userId.toString())) + } return this } - private fun getPreviousDirectKeySignature(): PGPSignature? { val info = inspectKeyRing(secretKeyRing, referenceTime) return info.latestDirectKeySelfSignature @@ -410,88 +606,109 @@ class SecretKeyRingEditor( @Throws(PGPException::class) private fun reissueNonPrimaryUserId( - secretKeyRingProtector: SecretKeyRingProtector, - userId: String, - prevUserIdSig: PGPSignature): PGPSignature { - val builder = SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig) + secretKeyRingProtector: SecretKeyRingProtector, + userId: String, + prevUserIdSig: PGPSignature + ): PGPSignature { + val builder = + SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig) builder.hashedSubpackets.setSignatureCreationTime(referenceTime) - builder.applyCallback(object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { - // unmark as primary - hashedSubpackets.setPrimaryUserId(null) - } - }) + builder.applyCallback( + object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + // unmark as primary + hashedSubpackets.setPrimaryUserId(null) + } + }) return builder.build(secretKeyRing.publicKey, userId) } @Throws(PGPException::class) private fun reissuePrimaryUserIdSig( - expiration: Date?, - @Nonnull secretKeyRingProtector: SecretKeyRingProtector, - @Nonnull primaryUserId: String, - @Nonnull prevUserIdSig: PGPSignature): PGPSignature { + expiration: Date?, + @Nonnull secretKeyRingProtector: SecretKeyRingProtector, + @Nonnull primaryUserId: String, + @Nonnull prevUserIdSig: PGPSignature + ): PGPSignature { return SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig) - .apply { - hashedSubpackets.setSignatureCreationTime(referenceTime) - applyCallback(object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + .apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + applyCallback( + object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets( + hashedSubpackets: SelfSignatureSubpackets + ) { if (expiration != null) { - hashedSubpackets.setKeyExpirationTime(true, secretKeyRing.publicKey.creationTime, expiration) + hashedSubpackets.setKeyExpirationTime( + true, secretKeyRing.publicKey.creationTime, expiration) } else { hashedSubpackets.setKeyExpirationTime(KeyExpirationTime(true, 0)) } hashedSubpackets.setPrimaryUserId() } }) - }.build(secretKeyRing.publicKey, primaryUserId) + } + .build(secretKeyRing.publicKey, primaryUserId) } @Throws(PGPException::class) private fun reissueDirectKeySignature( - expiration: Date?, - secretKeyRingProtector: SecretKeyRingProtector, - prevDirectKeySig: PGPSignature): PGPSignature { - return DirectKeySelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevDirectKeySig) - .apply { - hashedSubpackets.setSignatureCreationTime(referenceTime) - applyCallback(object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { + expiration: Date?, + secretKeyRingProtector: SecretKeyRingProtector, + prevDirectKeySig: PGPSignature + ): PGPSignature { + return DirectKeySelfSignatureBuilder( + secretKeyRing.secretKey, secretKeyRingProtector, prevDirectKeySig) + .apply { + hashedSubpackets.setSignatureCreationTime(referenceTime) + applyCallback( + object : SelfSignatureSubpackets.Callback { + override fun modifyHashedSubpackets( + hashedSubpackets: SelfSignatureSubpackets + ) { if (expiration != null) { - hashedSubpackets.setKeyExpirationTime(secretKeyRing.publicKey.creationTime, expiration) + hashedSubpackets.setKeyExpirationTime( + secretKeyRing.publicKey.creationTime, expiration) } else { hashedSubpackets.setKeyExpirationTime(null) } } }) - }.build(secretKeyRing.publicKey) + } + .build(secretKeyRing.publicKey) } private fun selectUserIds(predicate: Predicate): List = - inspectKeyRing(secretKeyRing).validUserIds.filter { predicate.test(it) } + inspectKeyRing(secretKeyRing).validUserIds.filter { predicate.test(it) } private class WithKeyRingEncryptionSettingsImpl( - private val editor: SecretKeyRingEditor, - private val keyId: Long?, - private val oldProtector: SecretKeyRingProtector) : SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { + private val editor: SecretKeyRingEditor, + private val keyId: Long?, + private val oldProtector: SecretKeyRingProtector + ) : SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { override fun withSecureDefaultSettings(): SecretKeyRingEditorInterface.WithPassphrase { return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings()) } - override fun withCustomSettings(settings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithPassphrase { + override fun withCustomSettings( + settings: KeyRingProtectionSettings + ): SecretKeyRingEditorInterface.WithPassphrase { return WithPassphraseImpl(editor, keyId, oldProtector, settings) } } private class WithPassphraseImpl( - private val editor: SecretKeyRingEditor, - private val keyId: Long?, - private val oldProtector: SecretKeyRingProtector, - private val newProtectionSettings: KeyRingProtectionSettings + private val editor: SecretKeyRingEditor, + private val keyId: Long?, + private val oldProtector: SecretKeyRingProtector, + private val newProtectionSettings: KeyRingProtectionSettings ) : SecretKeyRingEditorInterface.WithPassphrase { override fun toNewPassphrase(passphrase: Passphrase): SecretKeyRingEditorInterface { - val protector = PasswordBasedSecretKeyRingProtector(newProtectionSettings, SolitaryPassphraseProvider(passphrase)) + val protector = + PasswordBasedSecretKeyRingProtector( + newProtectionSettings, SolitaryPassphraseProvider(passphrase)) val secretKeys = changePassphrase(keyId, editor.secretKeyRing, oldProtector, protector) editor.secretKeyRing = secretKeys return editor @@ -504,5 +721,4 @@ class SecretKeyRingEditor( return editor } } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt index 8a26161b..b8fb993c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterface.kt @@ -4,6 +4,10 @@ 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 @@ -15,18 +19,12 @@ import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets import org.pgpainless.signature.subpackets.SelfSignatureSubpackets import org.pgpainless.util.Passphrase import org.pgpainless.util.selection.userid.SelectUserId -import java.io.IOException -import java.security.InvalidAlgorithmParameterException -import java.security.NoSuchAlgorithmException -import java.util.* -import java.util.function.Predicate interface SecretKeyRingEditorInterface { /** - * Editors reference time. - * This time is used as creation date for new signatures, or as reference when evaluating expiration of - * existing signatures. + * 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 @@ -36,11 +34,11 @@ interface SecretKeyRingEditorInterface { * @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) + fun addUserId(userId: CharSequence, protector: SecretKeyRingProtector) = + addUserId(userId, null, protector) /** * Add a user-id to the key ring. @@ -49,105 +47,126 @@ interface SecretKeyRingEditorInterface { * @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 + 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. + * 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 + 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. + * 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)")) + @Deprecated( + "Use of SelectUserId class is deprecated.", + ReplaceWith("removeUserId(protector, predicate)")) @Throws(PGPException::class) fun removeUserId(selector: SelectUserId, protector: SecretKeyRingProtector) = - removeUserId(protector, selector) + 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. + * 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 + 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 + * 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 + 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. + * 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 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 + 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]. + * 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 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 + @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. @@ -159,140 +178,140 @@ interface SecretKeyRingEditorInterface { * @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 + 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. + * 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 + 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. + * 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 + 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. + * 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) + 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. + * 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) + 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. + * 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?) + 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. + * 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 + 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. + * 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 + * 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 + fun revokeSubKey( + subkeyId: Long, + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback? + ): SecretKeyRingEditorInterface /** * Hard-revoke the given userID. @@ -300,11 +319,11 @@ interface SecretKeyRingEditorInterface { * @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?) + fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector) = + revokeUserId(userId, protector, null as RevocationAttributes?) /** * Revoke the given userID using the provided revocation attributes. @@ -313,83 +332,85 @@ interface SecretKeyRingEditorInterface { * @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 + 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. + * 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 + 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. + * 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. + * 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) + @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. + * 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. + * 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 + 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. + * 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. + * 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. * @@ -397,24 +418,25 @@ interface SecretKeyRingEditorInterface { * @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) + @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. + * 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. + * 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. * @@ -422,56 +444,61 @@ interface SecretKeyRingEditorInterface { * @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 + 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. + * 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 in case we cannot generate a new self-signature with the changed + * expiration date */ @Throws(PGPException::class) - fun setExpirationDate(expiration: Date?, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface + fun setExpirationDate( + expiration: Date?, + 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. + * 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 + 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. + * 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 + fun createRevocation( + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): PGPSignature /** * Create a detached revocation certificate, which can be used to revoke the specified subkey. @@ -481,13 +508,14 @@ interface SecretKeyRingEditorInterface { * @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 + fun createRevocation( + subkeyId: Long, + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): PGPSignature /** * Create a detached revocation certificate, which can be used to revoke the specified subkey. @@ -497,13 +525,14 @@ interface SecretKeyRingEditorInterface { * @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 + fun createRevocation( + subkeyId: Long, + protector: SecretKeyRingProtector, + callback: RevocationSignatureSubpackets.Callback? + ): PGPSignature /** * Create a detached revocation certificate, which can be used to revoke the specified subkey. @@ -513,13 +542,14 @@ interface SecretKeyRingEditorInterface { * @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 + fun createRevocation( + subkeyFingerprint: OpenPgpFingerprint, + protector: SecretKeyRingProtector, + revocationAttributes: RevocationAttributes? + ): PGPSignature /** * Change the passphrase of the whole key ring. @@ -527,8 +557,9 @@ interface SecretKeyRingEditorInterface { * @param oldPassphrase old passphrase (empty, if the key was unprotected) * @return next builder step */ - fun changePassphraseFromOldPassphrase( - oldPassphrase: Passphrase) = changePassphraseFromOldPassphrase(oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings()) + fun changePassphraseFromOldPassphrase(oldPassphrase: Passphrase) = + changePassphraseFromOldPassphrase( + oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings()) /** * Change the passphrase of the whole key ring. @@ -538,27 +569,30 @@ interface SecretKeyRingEditorInterface { * @return next builder step */ fun changePassphraseFromOldPassphrase( - oldPassphrase: Passphrase, - oldProtectionSettings: KeyRingProtectionSettings = KeyRingProtectionSettings.secureDefaultSettings()): WithKeyRingEncryptionSettings + 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. + * 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()) + 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. + * 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) @@ -566,15 +600,16 @@ interface SecretKeyRingEditorInterface { * @return next builder step */ fun changeSubKeyPassphraseFromOldPassphrase( - keyId: Long, - oldPassphrase: Passphrase, - oldProtectionSettings: KeyRingProtectionSettings): WithKeyRingEncryptionSettings + 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]. + * 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 */ @@ -596,7 +631,6 @@ interface SecretKeyRingEditorInterface { * * @param passphrase passphrase * @return editor builder - * * @throws PGPException in case the passphrase cannot be changed */ @Throws(PGPException::class) @@ -606,17 +640,21 @@ interface SecretKeyRingEditorInterface { * Leave the key unprotected. * * @return editor builder - * * @throws PGPException in case the passphrase cannot be changed */ - @Throws(PGPException::class) - fun toNoPassphrase(): SecretKeyRingEditorInterface + @Throws(PGPException::class) fun toNoPassphrase(): SecretKeyRingEditorInterface } /** * Return the [PGPSecretKeyRing]. + * * @return the key */ fun done(): PGPSecretKeyRing - fun addSubKey(keySpec: KeySpec, subkeyPassphrase: Passphrase, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface -} \ No newline at end of file + + fun addSubKey( + keySpec: KeySpec, + subkeyPassphrase: Passphrase, + protector: SecretKeyRingProtector + ): SecretKeyRingEditorInterface +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/parsing/KeyRingReader.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/parsing/KeyRingReader.kt index 388294d5..6f6bde61 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/parsing/KeyRingReader.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/parsing/KeyRingReader.kt @@ -4,124 +4,118 @@ package org.pgpainless.key.parsing +import java.io.IOException +import java.io.InputStream +import java.nio.charset.Charset +import kotlin.jvm.Throws import org.bouncycastle.openpgp.* import org.bouncycastle.util.io.Streams import org.pgpainless.PGPainless import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.collection.PGPKeyRingCollection import org.pgpainless.util.ArmorUtils -import java.io.IOException -import java.io.InputStream -import java.nio.charset.Charset -import kotlin.jvm.Throws class KeyRingReader { /** - * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given [InputStream]. + * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given + * [InputStream]. * * @param inputStream inputStream containing the OpenPGP key or certificate * @return key ring * @throws IOException in case of an IO error */ @Throws(IOException::class) - fun keyRing(inputStream: InputStream): PGPKeyRing? = - readKeyRing(inputStream) + fun keyRing(inputStream: InputStream): PGPKeyRing? = readKeyRing(inputStream) /** - * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given byte array. + * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given byte + * array. * * @param bytes byte array containing the OpenPGP key or certificate * @return key ring * @throws IOException in case of an IO error */ @Throws(IOException::class) - fun keyRing(bytes: ByteArray): PGPKeyRing? = - keyRing(bytes.inputStream()) + fun keyRing(bytes: ByteArray): PGPKeyRing? = keyRing(bytes.inputStream()) /** - * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given - * ASCII armored string. + * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given ASCII + * armored string. * * @param asciiArmored ASCII armored OpenPGP key or certificate * @return key ring * @throws IOException in case of an IO error */ @Throws(IOException::class) - fun keyRing(asciiArmored: String): PGPKeyRing? = - keyRing(asciiArmored.toByteArray(UTF8)) + fun keyRing(asciiArmored: String): PGPKeyRing? = keyRing(asciiArmored.toByteArray(UTF8)) @Throws(IOException::class) - fun publicKeyRing(inputStream: InputStream): PGPPublicKeyRing? = - readPublicKeyRing(inputStream) + fun publicKeyRing(inputStream: InputStream): PGPPublicKeyRing? = readPublicKeyRing(inputStream) @Throws(IOException::class) - fun publicKeyRing(bytes: ByteArray): PGPPublicKeyRing? = - publicKeyRing(bytes.inputStream()) + fun publicKeyRing(bytes: ByteArray): PGPPublicKeyRing? = publicKeyRing(bytes.inputStream()) @Throws(IOException::class) fun publicKeyRing(asciiArmored: String): PGPPublicKeyRing? = - publicKeyRing(asciiArmored.toByteArray(UTF8)) + publicKeyRing(asciiArmored.toByteArray(UTF8)) @Throws(IOException::class) fun publicKeyRingCollection(inputStream: InputStream): PGPPublicKeyRingCollection = - readPublicKeyRingCollection(inputStream) + readPublicKeyRingCollection(inputStream) @Throws(IOException::class) fun publicKeyRingCollection(bytes: ByteArray): PGPPublicKeyRingCollection = - publicKeyRingCollection(bytes.inputStream()) + publicKeyRingCollection(bytes.inputStream()) @Throws(IOException::class) fun publicKeyRingCollection(asciiArmored: String): PGPPublicKeyRingCollection = - publicKeyRingCollection(asciiArmored.toByteArray(UTF8)) + publicKeyRingCollection(asciiArmored.toByteArray(UTF8)) @Throws(IOException::class) - fun secretKeyRing(inputStream: InputStream): PGPSecretKeyRing? = - readSecretKeyRing(inputStream) + fun secretKeyRing(inputStream: InputStream): PGPSecretKeyRing? = readSecretKeyRing(inputStream) @Throws(IOException::class) - fun secretKeyRing(bytes: ByteArray): PGPSecretKeyRing? = - secretKeyRing(bytes.inputStream()) + fun secretKeyRing(bytes: ByteArray): PGPSecretKeyRing? = secretKeyRing(bytes.inputStream()) @Throws(IOException::class) fun secretKeyRing(asciiArmored: String): PGPSecretKeyRing? = - secretKeyRing(asciiArmored.toByteArray(UTF8)) + secretKeyRing(asciiArmored.toByteArray(UTF8)) @Throws(IOException::class) fun secretKeyRingCollection(inputStream: InputStream): PGPSecretKeyRingCollection = - readSecretKeyRingCollection(inputStream) + readSecretKeyRingCollection(inputStream) @Throws(IOException::class) fun secretKeyRingCollection(bytes: ByteArray): PGPSecretKeyRingCollection = - secretKeyRingCollection(bytes.inputStream()) + secretKeyRingCollection(bytes.inputStream()) @Throws(IOException::class) fun secretKeyRingCollection(asciiArmored: String): PGPSecretKeyRingCollection = - secretKeyRingCollection(asciiArmored.toByteArray(UTF8)) + secretKeyRingCollection(asciiArmored.toByteArray(UTF8)) @Throws(IOException::class) fun keyRingCollection(inptStream: InputStream, isSilent: Boolean): PGPKeyRingCollection = - readKeyRingCollection(inptStream, isSilent) + readKeyRingCollection(inptStream, isSilent) @Throws(IOException::class) fun keyRingCollection(bytes: ByteArray, isSilent: Boolean): PGPKeyRingCollection = - keyRingCollection(bytes.inputStream(), isSilent) + keyRingCollection(bytes.inputStream(), isSilent) @Throws(IOException::class) fun keyRingCollection(asciiArmored: String, isSilent: Boolean): PGPKeyRingCollection = - keyRingCollection(asciiArmored.toByteArray(UTF8), isSilent) + keyRingCollection(asciiArmored.toByteArray(UTF8), isSilent) companion object { private const val MAX_ITERATIONS = 10000 - @JvmStatic - val UTF8: Charset = charset("UTF8") - + @JvmStatic val UTF8: Charset = charset("UTF8") /** - * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given [InputStream]. - * This method will attempt to read at most

maxIterations
objects from the stream before aborting. - * The first [PGPPublicKeyRing] or [PGPSecretKeyRing] will be returned. + * Read a [PGPKeyRing] (either [PGPSecretKeyRing] or [PGPPublicKeyRing]) from the given + * [InputStream]. This method will attempt to read at most
maxIterations
objects + * from the stream before aborting. The first [PGPPublicKeyRing] or [PGPSecretKeyRing] will + * be returned. * * @param inputStream inputStream containing the OpenPGP key or certificate * @param maxIterations maximum number of objects that are read before the method will abort @@ -131,10 +125,13 @@ class KeyRingReader { @JvmStatic @JvmOverloads @Throws(IOException::class) - fun readKeyRing(inputStream: InputStream, - maxIterations: Int = MAX_ITERATIONS): PGPKeyRing? { - val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - ArmorUtils.getDecoderStream(inputStream)) + fun readKeyRing( + inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS + ): PGPKeyRing? { + val objectFactory = + ImplementationFactory.getInstance() + .getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream)) try { for ((i, next) in objectFactory.withIndex()) { @@ -152,30 +149,31 @@ class KeyRingReader { } continue } - } catch (e : PGPRuntimeOperationException) { + } catch (e: PGPRuntimeOperationException) { throw e.cause!! } return null } /** - * Read a public key ring from the provided [InputStream]. - * If more than maxIterations PGP packets are encountered before a [PGPPublicKeyRing] is read, - * an [IOException] is thrown. + * Read a public key ring from the provided [InputStream]. If more than maxIterations PGP + * packets are encountered before a [PGPPublicKeyRing] is read, an [IOException] is thrown. * * @param inputStream input stream * @param maxIterations max iterations before abort * @return public key ring - * * @throws IOException in case of an IO error or exceeding of max iterations */ @JvmStatic @JvmOverloads @Throws(IOException::class) - fun readPublicKeyRing(inputStream: InputStream, - maxIterations: Int = MAX_ITERATIONS): PGPPublicKeyRing? { - val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - ArmorUtils.getDecoderStream(inputStream)) + fun readPublicKeyRing( + inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS + ): PGPPublicKeyRing? { + val objectFactory = + ImplementationFactory.getInstance() + .getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream)) try { for ((i, next) in objectFactory.withIndex()) { @@ -190,31 +188,33 @@ class KeyRingReader { } continue } - } catch (e : PGPRuntimeOperationException) { + } catch (e: PGPRuntimeOperationException) { throw e.cause!! } return null } /** - * Read a public key ring collection from the provided [InputStream]. - * If more than maxIterations PGP packets are encountered before the stream is exhausted, - * an [IOException] is thrown. - * If the stream contain secret key packets, their public key parts are extracted and returned. + * Read a public key ring collection from the provided [InputStream]. If more than + * maxIterations PGP packets are encountered before the stream is exhausted, an + * [IOException] is thrown. If the stream contain secret key packets, their public key parts + * are extracted and returned. * * @param inputStream input stream * @param maxIterations max iterations before abort * @return public key ring collection - * * @throws IOException in case of an IO error or exceeding of max iterations */ @JvmStatic @JvmOverloads @Throws(IOException::class) - fun readPublicKeyRingCollection(inputStream: InputStream, - maxIterations: Int = MAX_ITERATIONS): PGPPublicKeyRingCollection { - val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - ArmorUtils.getDecoderStream(inputStream)) + fun readPublicKeyRingCollection( + inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS + ): PGPPublicKeyRingCollection { + val objectFactory = + ImplementationFactory.getInstance() + .getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream)) val certificates = mutableListOf() try { for ((i, next) in objectFactory.withIndex()) { @@ -237,30 +237,31 @@ class KeyRingReader { continue } } - } catch (e : PGPRuntimeOperationException) { + } catch (e: PGPRuntimeOperationException) { throw e.cause!! } return PGPPublicKeyRingCollection(certificates) } /** - * Read a secret key ring from the provided [InputStream]. - * If more than maxIterations PGP packets are encountered before a [PGPSecretKeyRing] is read, - * an [IOException] is thrown. + * Read a secret key ring from the provided [InputStream]. If more than maxIterations PGP + * packets are encountered before a [PGPSecretKeyRing] is read, an [IOException] is thrown. * * @param inputStream input stream * @param maxIterations max iterations before abort * @return public key ring - * * @throws IOException in case of an IO error or exceeding of max iterations */ @JvmStatic @JvmOverloads @Throws(IOException::class) - fun readSecretKeyRing(inputStream: InputStream, - maxIterations: Int = MAX_ITERATIONS): PGPSecretKeyRing? { + fun readSecretKeyRing( + inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS + ): PGPSecretKeyRing? { val decoderStream = ArmorUtils.getDecoderStream(inputStream) - val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream) + val objectFactory = + ImplementationFactory.getInstance().getPGPObjectFactory(decoderStream) try { for ((i, next) in objectFactory.withIndex()) { @@ -275,30 +276,32 @@ class KeyRingReader { return next } } - } catch (e : PGPRuntimeOperationException) { + } catch (e: PGPRuntimeOperationException) { throw e.cause!! } return null } /** - * Read a secret key ring collection from the provided [InputStream]. - * If more than maxIterations PGP packets are encountered before the stream is exhausted, - * an [IOException] is thrown. + * Read a secret key ring collection from the provided [InputStream]. If more than + * maxIterations PGP packets are encountered before the stream is exhausted, an + * [IOException] is thrown. * * @param inputStream input stream * @param maxIterations max iterations before abort * @return secret key ring collection - * * @throws IOException in case of an IO error or exceeding of max iterations */ @JvmStatic @JvmOverloads @Throws(IOException::class) - fun readSecretKeyRingCollection(inputStream: InputStream, - maxIterations: Int = MAX_ITERATIONS): PGPSecretKeyRingCollection { - val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( - ArmorUtils.getDecoderStream(inputStream)) + fun readSecretKeyRingCollection( + inputStream: InputStream, + maxIterations: Int = MAX_ITERATIONS + ): PGPSecretKeyRingCollection { + val objectFactory = + ImplementationFactory.getInstance() + .getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream)) val secretKeys = mutableListOf() try { @@ -318,7 +321,7 @@ class KeyRingReader { continue } } - } catch (e : PGPRuntimeOperationException) { + } catch (e: PGPRuntimeOperationException) { throw e.cause!! } return PGPSecretKeyRingCollection(secretKeys) @@ -326,8 +329,9 @@ class KeyRingReader { @JvmStatic @Throws(IOException::class) - fun readKeyRingCollection(inputStream: InputStream, isSilent: Boolean): PGPKeyRingCollection = - PGPKeyRingCollection(inputStream, isSilent) + fun readKeyRingCollection( + inputStream: InputStream, + isSilent: Boolean + ): PGPKeyRingCollection = PGPKeyRingCollection(inputStream, isSilent) } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/BaseSecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/BaseSecretKeyRingProtector.kt index 450ca7f5..c5db2086 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/BaseSecretKeyRingProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/BaseSecretKeyRingProtector.kt @@ -10,31 +10,35 @@ import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider /** - * Basic [SecretKeyRingProtector] implementation that respects the users [KeyRingProtectionSettings] when encrypting keys. + * Basic [SecretKeyRingProtector] implementation that respects the users [KeyRingProtectionSettings] + * when encrypting keys. */ open class BaseSecretKeyRingProtector( - private val passphraseProvider: SecretKeyPassphraseProvider, - private val protectionSettings: KeyRingProtectionSettings + private val passphraseProvider: SecretKeyPassphraseProvider, + private val protectionSettings: KeyRingProtectionSettings ) : SecretKeyRingProtector { - constructor(passphraseProvider: SecretKeyPassphraseProvider): - this(passphraseProvider, KeyRingProtectionSettings.secureDefaultSettings()) + constructor( + passphraseProvider: SecretKeyPassphraseProvider + ) : this(passphraseProvider, KeyRingProtectionSettings.secureDefaultSettings()) override fun hasPassphraseFor(keyId: Long): Boolean = passphraseProvider.hasPassphrase(keyId) override fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? = - passphraseProvider.getPassphraseFor(keyId)?.let { - if (it.isEmpty) null - else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(it) - } + passphraseProvider.getPassphraseFor(keyId)?.let { + if (it.isEmpty) null + else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(it) + } override fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? = - passphraseProvider.getPassphraseFor(keyId)?.let { - if (it.isEmpty) null - else ImplementationFactory.getInstance().getPBESecretKeyEncryptor( + passphraseProvider.getPassphraseFor(keyId)?.let { + if (it.isEmpty) null + else + ImplementationFactory.getInstance() + .getPBESecretKeyEncryptor( protectionSettings.encryptionAlgorithm, protectionSettings.hashAlgorithm, protectionSettings.s2kCount, it) - } -} \ No newline at end of file + } +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt index 32f69f70..5a3ff47f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/CachingSecretKeyRingProtector.kt @@ -13,8 +13,8 @@ import org.pgpainless.util.Passphrase /** * Implementation of the [SecretKeyRingProtector] which holds a map of key ids and their passwords. - * In case the needed passphrase is not contained in the map, the `missingPassphraseCallback` will be consulted, - * and the passphrase is added to the map. + * In case the needed passphrase is not contained in the map, the `missingPassphraseCallback` will + * be consulted, and the passphrase is added to the map. * * If you need to unlock multiple [PGPKeyRing] instances, it is advised to use a separate * [CachingSecretKeyRingProtector] instance for each ring. @@ -25,29 +25,33 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras private val protector: SecretKeyRingProtector private val provider: SecretKeyPassphraseProvider? - constructor(): this(null) + constructor() : this(null) - constructor(missingPassphraseCallback: SecretKeyPassphraseProvider?): this( - mapOf(), - KeyRingProtectionSettings.secureDefaultSettings(), - missingPassphraseCallback) + constructor( + missingPassphraseCallback: SecretKeyPassphraseProvider? + ) : this( + mapOf(), + KeyRingProtectionSettings.secureDefaultSettings(), + missingPassphraseCallback) - constructor(passphrases: Map, - protectionSettings: KeyRingProtectionSettings, - missingPassphraseCallback: SecretKeyPassphraseProvider?) { + constructor( + passphrases: Map, + protectionSettings: KeyRingProtectionSettings, + missingPassphraseCallback: SecretKeyPassphraseProvider? + ) { this.cache = passphrases.toMutableMap() this.protector = PasswordBasedSecretKeyRingProtector(protectionSettings, this) this.provider = missingPassphraseCallback } /** - * Add a passphrase to the cache. - * If the cache already contains a passphrase for the given key-id, a [IllegalArgumentException] is thrown. - * The reason for this is to prevent accidental override of passphrases when dealing with multiple key rings - * containing a key with the same key-id but different passphrases. + * Add a passphrase to the cache. If the cache already contains a passphrase for the given + * key-id, a [IllegalArgumentException] is thrown. The reason for this is to prevent accidental + * override of passphrases when dealing with multiple key rings containing a key with the same + * key-id but different passphrases. * - * If you can ensure that there will be no key-id clash, and you want to replace the passphrase, you can use - * [replacePassphrase] to replace the passphrase. + * If you can ensure that there will be no key-id clash, and you want to replace the passphrase, + * you can use [replacePassphrase] to replace the passphrase. * * @param keyId id of the key * @param passphrase passphrase @@ -55,7 +59,7 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras fun addPassphrase(keyId: Long, passphrase: Passphrase) = apply { require(!cache.containsKey(keyId)) { "The cache already holds a passphrase for ID ${keyId.openPgpKeyId()}.\n" + - "If you want to replace this passphrase, use replacePassphrase(Long, Passphrase) instead." + "If you want to replace this passphrase, use replacePassphrase(Long, Passphrase) instead." } cache[keyId] = passphrase } @@ -66,22 +70,20 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras * @param keyId keyId * @param passphrase passphrase */ - fun replacePassphrase(keyId: Long, passphrase: Passphrase) = apply { - cache[keyId] = passphrase - } + fun replacePassphrase(keyId: Long, passphrase: Passphrase) = apply { cache[keyId] = passphrase } /** - * Remember the given passphrase for all keys in the given key ring. - * If for the key-id of any key on the key ring the cache already contains a passphrase, a - * [IllegalArgumentException] is thrown before any changes are committed to the cache. - * This is to prevent accidental passphrase override when dealing with multiple key rings containing - * keys with conflicting key-ids. + * Remember the given passphrase for all keys in the given key ring. If for the key-id of any + * key on the key ring the cache already contains a passphrase, a [IllegalArgumentException] is + * thrown before any changes are committed to the cache. This is to prevent accidental + * passphrase override when dealing with multiple key rings containing keys with conflicting + * key-ids. * - * If you can ensure that there will be no key-id clashes, and you want to replace the passphrases for the key ring, - * use [replacePassphrase] instead. + * If you can ensure that there will be no key-id clashes, and you want to replace the + * passphrases for the key ring, use [replacePassphrase] instead. * - * If you need to unlock multiple [PGPKeyRing], it is advised to use a separate [CachingSecretKeyRingProtector] - * instance for each ring. + * If you need to unlock multiple [PGPKeyRing], it is advised to use a separate + * [CachingSecretKeyRingProtector] instance for each ring. * * @param keyRing key ring * @param passphrase passphrase @@ -91,14 +93,12 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras keyRing.publicKeys.forEach { require(!cache.containsKey(it.keyID)) { "The cache already holds a passphrase for the key with ID ${it.keyID.openPgpKeyId()}.\n" + - "If you want to replace the passphrase, use replacePassphrase(PGPKeyRing, Passphrase) instead." + "If you want to replace the passphrase, use replacePassphrase(PGPKeyRing, Passphrase) instead." } } // only then instert - keyRing.publicKeys.forEach { - cache[it.keyID] = passphrase - } + keyRing.publicKeys.forEach { cache[it.keyID] = passphrase } } /** @@ -118,7 +118,7 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras * @param passphrase passphrase */ fun addPassphrase(key: PGPPublicKey, passphrase: Passphrase) = - addPassphrase(key.keyID, passphrase) + addPassphrase(key.keyID, passphrase) /** * Remember the given passphrase for the key with the given fingerprint. @@ -127,17 +127,14 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras * @param passphrase passphrase */ fun addPassphrase(fingerprint: OpenPgpFingerprint, passphrase: Passphrase) = - addPassphrase(fingerprint.keyId, passphrase) + addPassphrase(fingerprint.keyId, passphrase) /** - * Remove a passphrase from the cache. - * The passphrase will be cleared and then removed. + * Remove a passphrase from the cache. The passphrase will be cleared and then removed. * * @param keyId id of the key */ - fun forgetPassphrase(keyId: Long) = apply { - cache.remove(keyId)?.clear() - } + fun forgetPassphrase(keyId: Long) = apply { cache.remove(keyId)?.clear() } /** * Forget the passphrase to all keys in the provided key ring. @@ -153,15 +150,11 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras * * @param key key */ - fun forgetPassphrase(key: PGPPublicKey) = apply { - forgetPassphrase(key.keyID) - } + fun forgetPassphrase(key: PGPPublicKey) = apply { forgetPassphrase(key.keyID) } override fun getPassphraseFor(keyId: Long): Passphrase? { - return if (hasPassphrase(keyId)) - cache[keyId] - else - provider?.getPassphraseFor(keyId)?.also { cache[keyId] = it } + return if (hasPassphrase(keyId)) cache[keyId] + else provider?.getPassphraseFor(keyId)?.also { cache[keyId] = it } } override fun hasPassphrase(keyId: Long) = cache[keyId]?.isValid ?: false @@ -171,4 +164,4 @@ class CachingSecretKeyRingProtector : SecretKeyRingProtector, SecretKeyPassphras override fun getDecryptor(keyId: Long) = protector.getDecryptor(keyId) override fun getEncryptor(keyId: Long) = protector.getEncryptor(keyId) -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/KeyRingProtectionSettings.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/KeyRingProtectionSettings.kt index 6158d322..c7566f6d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/KeyRingProtectionSettings.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/KeyRingProtectionSettings.kt @@ -8,49 +8,48 @@ import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm /** - * Secret key protection settings for iterated and salted S2K. - * The salt gets randomly chosen by the library each time. - * Note, that the s2kCount is the already encoded single-octet number. - * - * @see Encoding Formula + * Secret key protection settings for iterated and salted S2K. The salt gets randomly chosen by the + * library each time. Note, that the s2kCount is the already encoded single-octet number. * * @param encryptionAlgorithm encryption algorithm * @param hashAlgorithm hash algorithm * @param s2kCount encoded (!) s2k iteration count + * @see Encoding Formula */ data class KeyRingProtectionSettings( - val encryptionAlgorithm: SymmetricKeyAlgorithm, - val hashAlgorithm: HashAlgorithm, - val s2kCount: Int + val encryptionAlgorithm: SymmetricKeyAlgorithm, + val hashAlgorithm: HashAlgorithm, + val s2kCount: Int ) { /** - * Create a [KeyRingProtectionSettings] object using the given encryption algorithm, [HashAlgorithm.SHA1] and - * 65536 iterations. - * It is okay to use SHA1 here, since we don't care about collisions. + * Create a [KeyRingProtectionSettings] object using the given encryption algorithm, + * [HashAlgorithm.SHA1] and 65536 iterations. It is okay to use SHA1 here, since we don't care + * about collisions. * * @param encryptionAlgorithm encryption algorithm */ - constructor(encryptionAlgorithm: SymmetricKeyAlgorithm): this(encryptionAlgorithm, HashAlgorithm.SHA1, 0x60) + constructor( + encryptionAlgorithm: SymmetricKeyAlgorithm + ) : this(encryptionAlgorithm, HashAlgorithm.SHA1, 0x60) init { require(encryptionAlgorithm != SymmetricKeyAlgorithm.NULL) { "Unencrypted is not allowed here!" } - require(s2kCount > 0) { - "s2kCount cannot be less than 1." - } + require(s2kCount > 0) { "s2kCount cannot be less than 1." } } companion object { /** - * Secure default settings using [SymmetricKeyAlgorithm.AES_256], [HashAlgorithm.SHA256] - * and an iteration count of 65536. + * Secure default settings using [SymmetricKeyAlgorithm.AES_256], [HashAlgorithm.SHA256] and + * an iteration count of 65536. * * @return secure protection settings */ @JvmStatic - fun secureDefaultSettings() = KeyRingProtectionSettings(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA256, 0x60) + fun secureDefaultSettings() = + KeyRingProtectionSettings(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA256, 0x60) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.kt index 1b8df815..74ef881f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.kt @@ -10,54 +10,64 @@ import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProv import org.pgpainless.util.Passphrase /** - * Provides [PBESecretKeyDecryptor] and [PBESecretKeyEncryptor] objects while getting the passphrases - * from a [SecretKeyPassphraseProvider] and using settings from an [KeyRingProtectionSettings]. + * Provides [PBESecretKeyDecryptor] and [PBESecretKeyEncryptor] objects while getting the + * passphrases from a [SecretKeyPassphraseProvider] and using settings from an + * [KeyRingProtectionSettings]. */ class PasswordBasedSecretKeyRingProtector : BaseSecretKeyRingProtector { - constructor(passphraseProvider: SecretKeyPassphraseProvider): super(passphraseProvider) + constructor(passphraseProvider: SecretKeyPassphraseProvider) : super(passphraseProvider) /** - * Constructor. - * Passphrases for keys are sourced from the `passphraseProvider` and decryptors/encryptors are constructed - * following the settings given in `settings`. + * Constructor. Passphrases for keys are sourced from the `passphraseProvider` and + * decryptors/encryptors are constructed following the settings given in `settings`. * * @param settings S2K settings etc. * @param passphraseProvider provider which provides passphrases. */ - constructor(settings: KeyRingProtectionSettings, - passphraseProvider: SecretKeyPassphraseProvider): super(passphraseProvider, settings) + constructor( + settings: KeyRingProtectionSettings, + passphraseProvider: SecretKeyPassphraseProvider + ) : super(passphraseProvider, settings) companion object { @JvmStatic - fun forKey(keyRing: PGPKeyRing, passphrase: Passphrase): PasswordBasedSecretKeyRingProtector { + fun forKey( + keyRing: PGPKeyRing, + passphrase: Passphrase + ): PasswordBasedSecretKeyRingProtector { return object : SecretKeyPassphraseProvider { - override fun getPassphraseFor(keyId: Long): Passphrase? { - return if (hasPassphrase(keyId)) passphrase else null - } + override fun getPassphraseFor(keyId: Long): Passphrase? { + return if (hasPassphrase(keyId)) passphrase else null + } - override fun hasPassphrase(keyId: Long): Boolean { - return keyRing.getPublicKey(keyId) != null + override fun hasPassphrase(keyId: Long): Boolean { + return keyRing.getPublicKey(keyId) != null + } } - }.let { PasswordBasedSecretKeyRingProtector(it) } + .let { PasswordBasedSecretKeyRingProtector(it) } } @JvmStatic fun forKey(key: PGPSecretKey, passphrase: Passphrase): PasswordBasedSecretKeyRingProtector = - forKeyId(key.publicKey.keyID, passphrase) + forKeyId(key.publicKey.keyID, passphrase) @JvmStatic - fun forKeyId(singleKeyId: Long, passphrase: Passphrase): PasswordBasedSecretKeyRingProtector { + fun forKeyId( + singleKeyId: Long, + passphrase: Passphrase + ): PasswordBasedSecretKeyRingProtector { return object : SecretKeyPassphraseProvider { - override fun getPassphraseFor(keyId: Long): Passphrase? { - return if (hasPassphrase(keyId)) passphrase else null - } + override fun getPassphraseFor(keyId: Long): Passphrase? { + return if (hasPassphrase(keyId)) passphrase else null + } - override fun hasPassphrase(keyId: Long): Boolean { - return keyId == singleKeyId + override fun hasPassphrase(keyId: Long): Boolean { + return keyId == singleKeyId + } } - }.let { PasswordBasedSecretKeyRingProtector(it) } + .let { PasswordBasedSecretKeyRingProtector(it) } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt index 8bdac8d6..5e86d950 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt @@ -4,6 +4,7 @@ package org.pgpainless.key.protection +import kotlin.jvm.Throws import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKeyRing @@ -12,14 +13,14 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider import org.pgpainless.util.Passphrase -import kotlin.jvm.Throws /** * Task of the [SecretKeyRingProtector] is to map encryptor/decryptor objects to key-ids. - * [PBESecretKeyEncryptor]/[PBESecretKeyDecryptor] are used to encrypt/decrypt secret keys using a passphrase. + * [PBESecretKeyEncryptor]/[PBESecretKeyDecryptor] are used to encrypt/decrypt secret keys using a + * passphrase. * - * While it is easy to create an implementation of this interface that fits your needs, there are a bunch of - * implementations ready for use. + * While it is easy to create an implementation of this interface that fits your needs, there are a + * bunch of implementations ready for use. */ interface SecretKeyRingProtector { @@ -32,56 +33,61 @@ interface SecretKeyRingProtector { fun hasPassphraseFor(keyId: Long): Boolean /** - * Return a decryptor for the key of id `keyId`. - * This method returns null if the key is unprotected. + * Return a decryptor for the key of id `keyId`. This method returns null if the key is + * unprotected. * * @param keyId id of the key * @return decryptor for the key */ - @Throws(PGPException::class) - fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? + @Throws(PGPException::class) fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? /** - * Return an encryptor for the key of id `keyId`. - * This method returns null if the key is unprotected. + * Return an encryptor for the key of id `keyId`. This method returns null if the key is + * unprotected. * * @param keyId id of the key * @return encryptor for the key */ - @Throws(PGPException::class) - fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? + @Throws(PGPException::class) fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? companion object { /** - * Return a protector for secret keys. - * The protector maintains an in-memory cache of passphrases and can be extended with new passphrases - * at runtime. + * Return a protector for secret keys. The protector maintains an in-memory cache of + * passphrases and can be extended with new passphrases at runtime. * - * See [CachingSecretKeyRingProtector] for how to memorize/forget additional passphrases during runtime. + * See [CachingSecretKeyRingProtector] for how to memorize/forget additional passphrases + * during runtime. * * @param missingPassphraseCallback callback that is used to provide missing passphrases. * @return caching secret key protector */ @JvmStatic fun defaultSecretKeyRingProtector( - missingPassphraseCallback: SecretKeyPassphraseProvider? - ): CachingSecretKeyRingProtector = CachingSecretKeyRingProtector( - mapOf(), KeyRingProtectionSettings.secureDefaultSettings(), missingPassphraseCallback) + missingPassphraseCallback: SecretKeyPassphraseProvider? + ): CachingSecretKeyRingProtector = + CachingSecretKeyRingProtector( + mapOf(), + KeyRingProtectionSettings.secureDefaultSettings(), + missingPassphraseCallback) /** * Use the provided passphrase to lock/unlock all keys in the provided key ring. * - * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object. - * For other keys that are not present in the ring, it will return null. + * This protector will use the provided passphrase to lock/unlock all subkeys present in the + * provided keys object. For other keys that are not present in the ring, it will return + * null. * * @param passphrase passphrase * @param keys key ring * @return protector */ @JvmStatic - fun unlockEachKeyWith(passphrase: Passphrase, keys: PGPSecretKeyRing): SecretKeyRingProtector = - fromPassphraseMap(keys.map { it.keyID }.associateWith { passphrase }) + fun unlockEachKeyWith( + passphrase: Passphrase, + keys: PGPSecretKeyRing + ): SecretKeyRingProtector = + fromPassphraseMap(keys.map { it.keyID }.associateWith { passphrase }) /** * Use the provided passphrase to unlock any key. @@ -91,11 +97,11 @@ interface SecretKeyRingProtector { */ @JvmStatic fun unlockAnyKeyWith(passphrase: Passphrase): SecretKeyRingProtector = - BaseSecretKeyRingProtector(SolitaryPassphraseProvider(passphrase)) + BaseSecretKeyRingProtector(SolitaryPassphraseProvider(passphrase)) /** - * Use the provided passphrase to lock/unlock only the provided (sub-)key. - * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if + * Use the provided passphrase to lock/unlock only the provided (sub-)key. This protector + * will only return a non-null encryptor/decryptor based on the provided passphrase if * [getEncryptor]/[getDecryptor] is getting called with the key-id of the provided key. * * Otherwise, this protector will always return null. @@ -106,11 +112,11 @@ interface SecretKeyRingProtector { */ @JvmStatic fun unlockSingleKeyWith(passphrase: Passphrase, key: PGPSecretKey): SecretKeyRingProtector = - PasswordBasedSecretKeyRingProtector.forKey(key, passphrase) + PasswordBasedSecretKeyRingProtector.forKey(key, passphrase) /** - * Use the provided passphrase to lock/unlock only the provided (sub-)key. - * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if + * Use the provided passphrase to lock/unlock only the provided (sub-)key. This protector + * will only return a non-null encryptor/decryptor based on the provided passphrase if * [getEncryptor]/[getDecryptor] is getting called with the key-id of the provided key. * * Otherwise, this protector will always return null. @@ -121,21 +127,20 @@ interface SecretKeyRingProtector { */ @JvmStatic fun unlockSingleKeyWith(passphrase: Passphrase, keyId: Long): SecretKeyRingProtector = - PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase) + PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase) /** - * Protector for unprotected keys. - * This protector returns null for all [getEncryptor]/[getDecryptor] calls, - * no matter what the key-id is. + * Protector for unprotected keys. This protector returns null for all + * [getEncryptor]/[getDecryptor] calls, no matter what the key-id is. * - * As a consequence, this protector can only "unlock" keys which are not protected using a passphrase, and it will - * leave keys unprotected, should it be used to "protect" a key - * (e.g. in [org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor.changePassphraseFromOldPassphrase]). + * As a consequence, this protector can only "unlock" keys which are not protected using a + * passphrase, and it will leave keys unprotected, should it be used to "protect" a key + * (e.g. in + * [org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor.changePassphraseFromOldPassphrase]). * * @return protector */ - @JvmStatic - fun unprotectedKeys() = UnprotectedKeysProtector() + @JvmStatic fun unprotectedKeys() = UnprotectedKeysProtector() /** * Use the provided map of key-ids and passphrases to unlock keys. @@ -145,6 +150,7 @@ interface SecretKeyRingProtector { */ @JvmStatic fun fromPassphraseMap(passphraseMap: Map): SecretKeyRingProtector = - CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null) + CachingSecretKeyRingProtector( + passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt index 84d7d0d7..f9fe3e1d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt @@ -5,6 +5,7 @@ package org.pgpainless.key.protection +import kotlin.jvm.Throws import openpgp.openPgpKeyId import org.bouncycastle.extensions.isEncrypted import org.bouncycastle.openpgp.PGPException @@ -16,7 +17,6 @@ import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.exception.WrongPassphraseException import org.pgpainless.key.util.PublicKeyParameterValidationUtil import org.pgpainless.util.Passphrase -import kotlin.jvm.Throws class UnlockSecretKey { @@ -24,7 +24,10 @@ class UnlockSecretKey { @JvmStatic @Throws(PGPException::class, KeyIntegrityException::class) - fun unlockSecretKey(secretKey: PGPSecretKey, protector: SecretKeyRingProtector): PGPPrivateKey { + fun unlockSecretKey( + secretKey: PGPSecretKey, + protector: SecretKeyRingProtector + ): PGPPrivateKey { return if (secretKey.isEncrypted()) { unlockSecretKey(secretKey, protector.getDecryptor(secretKey.keyID)) } else { @@ -34,23 +37,29 @@ class UnlockSecretKey { @JvmStatic @Throws(PGPException::class) - fun unlockSecretKey(secretKey: PGPSecretKey, decryptor: PBESecretKeyDecryptor?): PGPPrivateKey { - val privateKey = try { - secretKey.extractPrivateKey(decryptor) - } catch (e : PGPException) { - throw WrongPassphraseException(secretKey.keyID, e) - } + fun unlockSecretKey( + secretKey: PGPSecretKey, + decryptor: PBESecretKeyDecryptor? + ): PGPPrivateKey { + val privateKey = + try { + secretKey.extractPrivateKey(decryptor) + } catch (e: PGPException) { + throw WrongPassphraseException(secretKey.keyID, e) + } if (privateKey == null) { if (secretKey.s2K.type in 100..110) { - throw PGPException("Cannot decrypt secret key ${secretKey.keyID.openPgpKeyId()}: \n" + + throw PGPException( + "Cannot decrypt secret key ${secretKey.keyID.openPgpKeyId()}: \n" + "Unsupported private S2K type ${secretKey.s2K.type}") } throw PGPException("Cannot decrypt secret key.") } if (PGPainless.getPolicy().isEnableKeyParameterValidation()) { - PublicKeyParameterValidationUtil.verifyPublicKeyParameterIntegrity(privateKey, secretKey.publicKey) + PublicKeyParameterValidationUtil.verifyPublicKeyParameterIntegrity( + privateKey, secretKey.publicKey) } return privateKey @@ -61,8 +70,9 @@ class UnlockSecretKey { return if (passphrase == null) { unlockSecretKey(secretKey, SecretKeyRingProtector.unprotectedKeys()) } else { - unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey)) + unlockSecretKey( + secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey)) } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt index f87c6d3d..a25bb31a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt @@ -4,7 +4,8 @@ package org.pgpainless.key.protection /** - * Implementation of the [SecretKeyRingProtector] which assumes that all handled keys are not password protected. + * Implementation of the [SecretKeyRingProtector] which assumes that all handled keys are not + * password protected. */ class UnprotectedKeysProtector : SecretKeyRingProtector { override fun hasPassphraseFor(keyId: Long) = true @@ -12,4 +13,4 @@ class UnprotectedKeysProtector : SecretKeyRingProtector { override fun getDecryptor(keyId: Long) = null override fun getEncryptor(keyId: Long) = null -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt index c02486ac..43cca885 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/fixes/S2KUsageFix.kt @@ -14,12 +14,14 @@ import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.protection.SecretKeyRingProtector /** - * Repair class to fix keys which use S2K usage of value [SecretKeyPacket.USAGE_CHECKSUM]. - * The method [replaceUsageChecksumWithUsageSha1] ensures that such keys are encrypted using - * S2K usage [SecretKeyPacket.USAGE_SHA1] instead. + * Repair class to fix keys which use S2K usage of value [SecretKeyPacket.USAGE_CHECKSUM]. The + * method [replaceUsageChecksumWithUsageSha1] ensures that such keys are encrypted using S2K usage + * [SecretKeyPacket.USAGE_SHA1] instead. * - * @see Related PGPainless Bug Report - * @see Related PGPainless Feature Request + * @see Related PGPainless Bug + * Report + * @see Related PGPainless Feature + * Request * @see Related upstream BC bug report */ class S2KUsageFix { @@ -27,23 +29,26 @@ class S2KUsageFix { companion object { /** - * Repair method for keys which use S2K usage
USAGE_CHECKSUM
which is deemed insecure. - * This method fixes the private keys by changing them to
USAGE_SHA1
instead. + * Repair method for keys which use S2K usage
USAGE_CHECKSUM
which is deemed + * insecure. This method fixes the private keys by changing them to
USAGE_SHA1
+ * instead. * * @param keys keys * @param protector protector to unlock and re-lock affected private keys - * @param skipKeysWithMissingPassphrase if set to true, missing subkey passphrases will cause the subkey to stay unaffected. + * @param skipKeysWithMissingPassphrase if set to true, missing subkey passphrases will + * cause the subkey to stay unaffected. * @return fixed key ring * @throws PGPException in case of a PGP error. */ @JvmStatic @JvmOverloads fun replaceUsageChecksumWithUsageSha1( - keys: PGPSecretKeyRing, - protector: SecretKeyRingProtector, - skipKeysWithMissingPassphrase: Boolean = false + keys: PGPSecretKeyRing, + protector: SecretKeyRingProtector, + skipKeysWithMissingPassphrase: Boolean = false ): PGPSecretKeyRing { - val digestCalculator = ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1) + val digestCalculator = + ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1) val keyList = mutableListOf() for (key in keys) { // CHECKSUM is not recommended @@ -59,21 +64,22 @@ class S2KUsageFix { keyList.add(key) continue } - throw WrongPassphraseException("Missing passphrase for key with ID " + java.lang.Long.toHexString(keyId)) + throw WrongPassphraseException( + "Missing passphrase for key with ID " + java.lang.Long.toHexString(keyId)) } val privateKey = key.unlock(protector) // This constructor makes use of USAGE_SHA1 by default - val fixedKey = PGPSecretKey( + val fixedKey = + PGPSecretKey( privateKey, key.publicKey, digestCalculator, key.isMasterKey, - protector.getEncryptor(keyId) - ) + protector.getEncryptor(keyId)) keyList.add(fixedKey) } return PGPSecretKeyRing(keyList) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.kt index 22260126..ffa3619a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.kt @@ -7,10 +7,11 @@ package org.pgpainless.key.protection.passphrase_provider import org.pgpainless.util.Passphrase /** - * Implementation of the [SecretKeyPassphraseProvider] that holds a map of key-IDs and respective [Passphrase]. - * It will return the right passphrase depending on the key-id. + * Implementation of the [SecretKeyPassphraseProvider] that holds a map of key-IDs and respective + * [Passphrase]. It will return the right passphrase depending on the key-id. * * Note: This provider might return null! + * * TODO: Make this null-safe and throw an exception instead? */ class MapBasedPassphraseProvider(val map: Map) : SecretKeyPassphraseProvider { @@ -18,4 +19,4 @@ class MapBasedPassphraseProvider(val map: Map) : SecretKeyPass override fun getPassphraseFor(keyId: Long): Passphrase? = map[keyId] override fun hasPassphrase(keyId: Long): Boolean = map.containsKey(keyId) -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.kt index aaf24b70..d872da51 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.kt @@ -7,16 +7,13 @@ package org.pgpainless.key.protection.passphrase_provider import org.bouncycastle.openpgp.PGPSecretKey import org.pgpainless.util.Passphrase -/** - * Interface to allow the user to provide a [Passphrase] for an encrypted OpenPGP secret key. - */ +/** Interface to allow the user to provide a [Passphrase] for an encrypted OpenPGP secret key. */ interface SecretKeyPassphraseProvider { /** - * Return a passphrase for the given secret key. - * If no record is found, return null. - * Note: In case of an unprotected secret key, this method must may not return null, but a [Passphrase] with - * a content of null. + * Return a passphrase for the given secret key. If no record is found, return null. Note: In + * case of an unprotected secret key, this method must may not return null, but a [Passphrase] + * with a content of null. * * @param secretKey secret key * @return passphrase or null, if no passphrase record is found. @@ -26,9 +23,9 @@ interface SecretKeyPassphraseProvider { } /** - * Return a passphrase for the given key. If no record has been found, return null. - * Note: In case of an unprotected secret key, this method must may not return null, but a [Passphrase] with - * a content of null. + * Return a passphrase for the given key. If no record has been found, return null. Note: In + * case of an unprotected secret key, this method must may not return null, but a [Passphrase] + * with a content of null. * * @param keyId if of the secret key * @return passphrase or null, if no passphrase record has been found. @@ -36,4 +33,4 @@ interface SecretKeyPassphraseProvider { fun getPassphraseFor(keyId: Long): Passphrase? fun hasPassphrase(keyId: Long): Boolean -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.kt index 46f77342..a8a1735d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.kt @@ -6,12 +6,10 @@ package org.pgpainless.key.protection.passphrase_provider import org.pgpainless.util.Passphrase -/** - * Implementation of the [SecretKeyPassphraseProvider] that holds a single [Passphrase]. - */ +/** Implementation of the [SecretKeyPassphraseProvider] that holds a single [Passphrase]. */ class SolitaryPassphraseProvider(val passphrase: Passphrase?) : SecretKeyPassphraseProvider { override fun getPassphraseFor(keyId: Long): Passphrase? = passphrase override fun hasPassphrase(keyId: Long): Boolean = true -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt index 63b65672..b26d84dc 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyIdUtil.kt @@ -12,25 +12,24 @@ class KeyIdUtil { companion object { /** - * Convert a long key-id into a key-id. - * A long key-id is a 16 digit hex string. + * Convert a long key-id into a key-id. A long key-id is a 16 digit hex string. * * @param longKeyId 16-digit hexadecimal string * @return key-id converted to {@link Long}. */ @JvmStatic - @Deprecated("Superseded by Long extension method.", - ReplaceWith("Long.fromHexKeyId(longKeyId)")) + @Deprecated( + "Superseded by Long extension method.", ReplaceWith("Long.fromHexKeyId(longKeyId)")) fun fromLongKeyId(longKeyId: String) = Long.fromOpenPgpKeyId(longKeyId) /** * Format a long key-ID as upper-case hex string. + * * @param keyId keyId * @return hex encoded key ID */ @JvmStatic - @Deprecated("Superseded by Long extension method.", - ReplaceWith("keyId.hexKeyId()")) + @Deprecated("Superseded by Long extension method.", ReplaceWith("keyId.hexKeyId()")) fun formatKeyId(keyId: Long) = keyId.openPgpKeyId() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt index 79215408..29f343c1 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/KeyRingUtils.kt @@ -4,6 +4,8 @@ package org.pgpainless.key.util +import java.io.ByteArrayOutputStream +import kotlin.jvm.Throws import openpgp.openPgpKeyId import org.bouncycastle.bcpg.S2K import org.bouncycastle.bcpg.SecretKeyPacket @@ -17,34 +19,32 @@ import org.pgpainless.key.protection.SecretKeyRingProtector import org.pgpainless.key.protection.fixes.S2KUsageFix import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.ByteArrayOutputStream -import kotlin.jvm.Throws class KeyRingUtils { companion object { - @JvmStatic - private val LOGGER: Logger = LoggerFactory.getLogger(KeyRingUtils::class.java) + @JvmStatic private val LOGGER: Logger = LoggerFactory.getLogger(KeyRingUtils::class.java) /** - * Return the primary {@link PGPSecretKey} from the provided {@link PGPSecretKeyRing}. - * If it has no primary secret key, throw a {@link NoSuchElementException}. + * Return the primary {@link PGPSecretKey} from the provided {@link PGPSecretKeyRing}. If it + * has no primary secret key, throw a {@link NoSuchElementException}. * * @param secretKeys secret keys * @return primary secret key */ @JvmStatic - @Deprecated("Deprecated in favor of PGPSecretKeyRing extension function.", - ReplaceWith("secretKeys.requireSecretKey(keyId)")) + @Deprecated( + "Deprecated in favor of PGPSecretKeyRing extension function.", + ReplaceWith("secretKeys.requireSecretKey(keyId)")) fun requirePrimarySecretKeyFrom(secretKeys: PGPSecretKeyRing): PGPSecretKey { return secretKeys.requireSecretKey(secretKeys.publicKey.keyID) } /** - * Return the primary secret key from the given secret key ring. - * If the key ring only contains subkeys (e.g. if the primary secret key is stripped), - * this method may return null. + * Return the primary secret key from the given secret key ring. If the key ring only + * contains subkeys (e.g. if the primary secret key is stripped), this method may return + * null. * * @param secretKeys secret key ring * @return primary secret key @@ -61,8 +61,8 @@ class KeyRingUtils { } /** - * Return the primary {@link PGPPublicKey} from the provided key ring. - * Throws a {@link NoSuchElementException} if the key ring has no primary public key. + * Return the primary {@link PGPPublicKey} from the provided key ring. Throws a {@link + * NoSuchElementException} if the key ring has no primary public key. * * @param keyRing key ring * @return primary public key @@ -70,11 +70,12 @@ class KeyRingUtils { @JvmStatic fun requirePrimaryPublicKeyFrom(keyRing: PGPKeyRing): PGPPublicKey { return getPrimaryPublicKey(keyRing) - ?: throw NoSuchElementException("Provided PGPKeyRing has no primary public key.") + ?: throw NoSuchElementException("Provided PGPKeyRing has no primary public key.") } /** - * Return the primary {@link PGPPublicKey} from the provided key ring or null if it has none. + * Return the primary {@link PGPPublicKey} from the provided key ring or null if it has + * none. * * @param keyRing key ring * @return primary public key @@ -91,8 +92,8 @@ class KeyRingUtils { } /** - * Require the public key with the given subKeyId from the keyRing. - * If no such subkey exists, throw an {@link NoSuchElementException}. + * Require the public key with the given subKeyId from the keyRing. If no such subkey + * exists, throw an {@link NoSuchElementException}. * * @param keyRing key ring * @param subKeyId subkey id @@ -101,12 +102,13 @@ class KeyRingUtils { @JvmStatic fun requirePublicKeyFrom(keyRing: PGPKeyRing, subKeyId: Long): PGPPublicKey { return keyRing.getPublicKey(subKeyId) - ?: throw NoSuchElementException("KeyRing does not contain public key with keyId ${subKeyId.openPgpKeyId()}.") + ?: throw NoSuchElementException( + "KeyRing does not contain public key with keyId ${subKeyId.openPgpKeyId()}.") } /** - * Require the secret key with the given secret subKeyId from the secret keyRing. - * If no such subkey exists, throw an {@link NoSuchElementException}. + * Require the secret key with the given secret subKeyId from the secret keyRing. If no such + * subkey exists, throw an {@link NoSuchElementException}. * * @param keyRing secret key ring * @param subKeyId subkey id @@ -115,7 +117,8 @@ class KeyRingUtils { @JvmStatic fun requireSecretKeyFrom(keyRing: PGPSecretKeyRing, subKeyId: Long): PGPSecretKey { return keyRing.getSecretKey(subKeyId) - ?: throw NoSuchElementException("KeyRing does not contain secret key with keyID ${subKeyId.openPgpKeyId()}.") + ?: throw NoSuchElementException( + "KeyRing does not contain secret key with keyID ${subKeyId.openPgpKeyId()}.") } @JvmStatic @@ -128,57 +131,67 @@ class KeyRingUtils { } /** - * Extract a {@link PGPPublicKeyRing} containing all public keys from the provided {@link PGPSecretKeyRing}. + * Extract a {@link PGPPublicKeyRing} containing all public keys from the provided {@link + * PGPSecretKeyRing}. * * @param secretKeys secret key ring * @return public key ring */ @JvmStatic - @Deprecated("Deprecated in favor of PGPSecretKeyRing extension method.", - ReplaceWith("secretKeys.certificate", "org.bouncycastle.extensions.certificate")) + @Deprecated( + "Deprecated in favor of PGPSecretKeyRing extension method.", + ReplaceWith("secretKeys.certificate", "org.bouncycastle.extensions.certificate")) fun publicKeyRingFrom(secretKeys: PGPSecretKeyRing): PGPPublicKeyRing { return secretKeys.certificate } /** - * Extract {@link PGPPublicKeyRing PGPPublicKeyRings} from all {@link PGPSecretKeyRing PGPSecretKeyRings} in - * the given {@link PGPSecretKeyRingCollection} and return them as a {@link PGPPublicKeyRingCollection}. + * Extract {@link PGPPublicKeyRing PGPPublicKeyRings} from all {@link PGPSecretKeyRing + * PGPSecretKeyRings} in the given {@link PGPSecretKeyRingCollection} and return them as a + * {@link PGPPublicKeyRingCollection}. * * @param secretKeyRings secret key ring collection * @return public key ring collection */ @JvmStatic - fun publicKeyRingCollectionFrom(secretKeyRings: PGPSecretKeyRingCollection): PGPPublicKeyRingCollection { + fun publicKeyRingCollectionFrom( + secretKeyRings: PGPSecretKeyRingCollection + ): PGPPublicKeyRingCollection { return PGPPublicKeyRingCollection( - secretKeyRings.keyRings.asSequence() - .map { it.certificate } - .toList()) + secretKeyRings.keyRings.asSequence().map { it.certificate }.toList()) } /** - * Create a new {@link PGPPublicKeyRingCollection} from an array of {@link PGPPublicKeyRing PGPPublicKeyRings}. + * Create a new {@link PGPPublicKeyRingCollection} from an array of {@link PGPPublicKeyRing + * PGPPublicKeyRings}. * * @param certificates array of public key rings * @return key ring collection */ @JvmStatic - fun keyRingsToKeyRingCollection(vararg certificates: PGPPublicKeyRing): PGPPublicKeyRingCollection { + fun keyRingsToKeyRingCollection( + vararg certificates: PGPPublicKeyRing + ): PGPPublicKeyRingCollection { return PGPPublicKeyRingCollection(certificates.toList()) } /** - * Create a new {@link PGPSecretKeyRingCollection} from an array of {@link PGPSecretKeyRing PGPSecretKeyRings}. + * Create a new {@link PGPSecretKeyRingCollection} from an array of {@link PGPSecretKeyRing + * PGPSecretKeyRings}. * * @param secretKeys array of secret key rings * @return secret key ring collection */ @JvmStatic - fun keyRingsToKeyRingCollection(vararg secretKeys: PGPSecretKeyRing): PGPSecretKeyRingCollection { + fun keyRingsToKeyRingCollection( + vararg secretKeys: PGPSecretKeyRing + ): PGPSecretKeyRingCollection { return PGPSecretKeyRingCollection(secretKeys.toList()) } /** - * Return true, if the given {@link PGPPublicKeyRing} contains a {@link PGPPublicKey} for the given key id. + * Return true, if the given {@link PGPPublicKeyRing} contains a {@link PGPPublicKey} for + * the given key id. * * @param certificate public key ring * @param keyId id of the key in question @@ -194,8 +207,8 @@ class KeyRingUtils { * * @param keyRing key ring * @param certification key signature - * @return key ring with injected signature * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} + * @return key ring with injected signature */ @JvmStatic fun injectCertification(keyRing: T, certification: PGPSignature): T { @@ -210,27 +223,35 @@ class KeyRingUtils { * @param certification key signature * @param either {@link PGPPublicKeyRing} or {@link PGPSecretKeyRing} * @return key ring with injected signature - * * @throws NoSuchElementException in case that the signed key is not part of the key ring */ @JvmStatic - fun injectCertification(keyRing: T, certifiedKey: PGPPublicKey, certification: PGPSignature): T { + fun injectCertification( + keyRing: T, + certifiedKey: PGPPublicKey, + certification: PGPSignature + ): T { val secretAndPublicKeys = secretAndPublicKeys(keyRing) val secretKeys: PGPSecretKeyRing? = secretAndPublicKeys.first var certificate: PGPPublicKeyRing = secretAndPublicKeys.second if (!keyRingContainsKeyWithId(certificate, certifiedKey.keyID)) { - throw NoSuchElementException("Cannot find public key with id ${certifiedKey.keyID.openPgpKeyId()} in the provided key ring.") + throw NoSuchElementException( + "Cannot find public key with id ${certifiedKey.keyID.openPgpKeyId()} in the provided key ring.") } - certificate = PGPPublicKeyRing( - certificate.publicKeys.asSequence().map { - if (it.keyID == certifiedKey.keyID) { - PGPPublicKey.addCertification(it, certification) - } else { - it + certificate = + PGPPublicKeyRing( + certificate.publicKeys + .asSequence() + .map { + if (it.keyID == certifiedKey.keyID) { + PGPPublicKey.addCertification(it, certification) + } else { + it + } } - }.toList()) + .toList()) return if (secretKeys == null) { certificate as T } else { @@ -248,16 +269,23 @@ class KeyRingUtils { * @return key ring with injected certification */ @JvmStatic - fun injectCertification(keyRing: T, userId: CharSequence, certification: PGPSignature): T { + fun injectCertification( + keyRing: T, + userId: CharSequence, + certification: PGPSignature + ): T { val secretAndPublicKeys = secretAndPublicKeys(keyRing) val secretKeys: PGPSecretKeyRing? = secretAndPublicKeys.first var certificate: PGPPublicKeyRing = secretAndPublicKeys.second - certificate = PGPPublicKeyRing( + certificate = + PGPPublicKeyRing( listOf( - PGPPublicKey.addCertification(requirePrimaryPublicKeyFrom(certificate), - userId.toString(), certification) - ).plus(certificate.publicKeys.asSequence().drop(1))) + PGPPublicKey.addCertification( + requirePrimaryPublicKeyFrom(certificate), + userId.toString(), + certification)) + .plus(certificate.publicKeys.asSequence().drop(1))) return if (secretKeys == null) { certificate as T @@ -276,16 +304,23 @@ class KeyRingUtils { * @return key ring with injected user-attribute certification */ @JvmStatic - fun injectCertification(keyRing: T, userAttributes: PGPUserAttributeSubpacketVector, certification: PGPSignature): T { + fun injectCertification( + keyRing: T, + userAttributes: PGPUserAttributeSubpacketVector, + certification: PGPSignature + ): T { val secretAndPublicKeys = secretAndPublicKeys(keyRing) val secretKeys: PGPSecretKeyRing? = secretAndPublicKeys.first var certificate: PGPPublicKeyRing = secretAndPublicKeys.second - certificate = PGPPublicKeyRing( + certificate = + PGPPublicKeyRing( listOf( - PGPPublicKey.addCertification(requirePrimaryPublicKeyFrom(certificate), - userAttributes, certification) - ).plus(certificate.publicKeys.asSequence().drop(1))) + PGPPublicKey.addCertification( + requirePrimaryPublicKeyFrom(certificate), + userAttributes, + certification)) + .plus(certificate.publicKeys.asSequence().drop(1))) return if (secretKeys == null) { certificate as T @@ -316,7 +351,9 @@ class KeyRingUtils { } @JvmStatic - private fun secretAndPublicKeys(keyRing: PGPKeyRing): Pair { + private fun secretAndPublicKeys( + keyRing: PGPKeyRing + ): Pair { var secretKeys: PGPSecretKeyRing? = null val certificate: PGPPublicKeyRing when (keyRing) { @@ -327,7 +364,9 @@ class KeyRingUtils { is PGPPublicKeyRing -> { certificate = keyRing } - else -> throw IllegalArgumentException("keyRing is an unknown PGPKeyRing subclass: ${keyRing.javaClass.name}") + else -> + throw IllegalArgumentException( + "keyRing is an unknown PGPKeyRing subclass: ${keyRing.javaClass.name}") } return secretKeys to certificate } @@ -340,12 +379,16 @@ class KeyRingUtils { * @return secret key ring with injected secret key */ @JvmStatic - fun keysPlusSecretKey(secretKeys: PGPSecretKeyRing, secretKey: PGPSecretKey): PGPSecretKeyRing { + fun keysPlusSecretKey( + secretKeys: PGPSecretKeyRing, + secretKey: PGPSecretKey + ): PGPSecretKeyRing { return PGPSecretKeyRing.insertSecretKey(secretKeys, secretKey) } /** * Inject the given signature into the public part of the given secret key. + * * @param secretKey secret key * @param signature signature * @return secret key with the signature injected in its public key @@ -358,15 +401,16 @@ class KeyRingUtils { } /** - * Remove the secret key of the subkey identified by the given secret key id from the key ring. - * The public part stays attached to the key ring, so that it can still be used for encryption / verification of signatures. + * Remove the secret key of the subkey identified by the given secret key id from the key + * ring. The public part stays attached to the key ring, so that it can still be used for + * encryption / verification of signatures. * - * This method is intended to be used to remove secret primary keys from live keys when those are kept in offline storage. + * This method is intended to be used to remove secret primary keys from live keys when + * those are kept in offline storage. * * @param secretKeys secret key ring * @param keyId id of the secret key to remove * @return secret key ring with removed secret key - * * @throws IOException in case of an error during serialization / deserialization of the key * @throws PGPException in case of a broken key */ @@ -376,7 +420,8 @@ class KeyRingUtils { "Bouncy Castle currently cannot deal with stripped primary secret keys." } if (secretKeys.getSecretKey(keyId) == null) { - throw NoSuchElementException("PGPSecretKeyRing does not contain secret key ${keyId.openPgpKeyId()}.") + throw NoSuchElementException( + "PGPSecretKeyRing does not contain secret key ${keyId.openPgpKeyId()}.") } val out = ByteArrayOutputStream() @@ -389,10 +434,9 @@ class KeyRingUtils { it.encode(out) } } - secretKeys.extraPublicKeys.forEach { - it.encode(out) - } - return PGPSecretKeyRing(out.toByteArray(), ImplementationFactory.getInstance().keyFingerprintCalculator) + secretKeys.extraPublicKeys.forEach { it.encode(out) } + return PGPSecretKeyRing( + out.toByteArray(), ImplementationFactory.getInstance().keyFingerprintCalculator) } /** @@ -404,7 +448,9 @@ class KeyRingUtils { */ @JvmStatic fun getStrippedDownPublicKey(bloatedKey: PGPPublicKey): PGPPublicKey { - return PGPPublicKey(bloatedKey.publicKeyPacket, ImplementationFactory.getInstance().keyFingerprintCalculator) + return PGPPublicKey( + bloatedKey.publicKeyPacket, + ImplementationFactory.getInstance().keyFingerprintCalculator) } @JvmStatic @@ -413,7 +459,7 @@ class KeyRingUtils { key.rawUserIDs.forEach { try { add(Strings.fromUTF8ByteArray(it)) - } catch (e : IllegalArgumentException) { + } catch (e: IllegalArgumentException) { LOGGER.warn("Invalid UTF-8 user-ID encountered: ${String(it)}") } } @@ -422,50 +468,62 @@ class KeyRingUtils { @JvmStatic @Throws(MissingPassphraseException::class, PGPException::class) - fun changePassphrase(keyId: Long?, - secretKeys: PGPSecretKeyRing, - oldProtector: SecretKeyRingProtector, - newProtector: SecretKeyRingProtector): PGPSecretKeyRing { + fun changePassphrase( + keyId: Long?, + secretKeys: PGPSecretKeyRing, + oldProtector: SecretKeyRingProtector, + newProtector: SecretKeyRingProtector + ): PGPSecretKeyRing { return if (keyId == null) { - PGPSecretKeyRing( - secretKeys.secretKeys.asSequence().map { - reencryptPrivateKey(it, oldProtector, newProtector) - }.toList() - ) - } else { - PGPSecretKeyRing( - secretKeys.secretKeys.asSequence().map { - if (it.keyID == keyId) { - reencryptPrivateKey(it, oldProtector, newProtector) - } else { - it + PGPSecretKeyRing( + secretKeys.secretKeys + .asSequence() + .map { reencryptPrivateKey(it, oldProtector, newProtector) } + .toList()) + } else { + PGPSecretKeyRing( + secretKeys.secretKeys + .asSequence() + .map { + if (it.keyID == keyId) { + reencryptPrivateKey(it, oldProtector, newProtector) + } else { + it + } } - }.toList() - ) - }.let { s2kUsageFixIfNecessary(it, newProtector) } + .toList()) + } + .let { s2kUsageFixIfNecessary(it, newProtector) } } @JvmStatic - fun reencryptPrivateKey(secretKey: PGPSecretKey, - oldProtector: SecretKeyRingProtector, - newProtector: SecretKeyRingProtector): PGPSecretKey { + fun reencryptPrivateKey( + secretKey: PGPSecretKey, + oldProtector: SecretKeyRingProtector, + newProtector: SecretKeyRingProtector + ): PGPSecretKey { if (secretKey.s2K != null && secretKey.s2K.type == S2K.GNU_DUMMY_S2K) { // If the key uses GNU_DUMMY_S2K we leave it as is return secretKey } - return PGPSecretKey.copyWithNewPassword(secretKey, - oldProtector.getDecryptor(secretKey.keyID), - newProtector.getEncryptor(secretKey.keyID)) + return PGPSecretKey.copyWithNewPassword( + secretKey, + oldProtector.getDecryptor(secretKey.keyID), + newProtector.getEncryptor(secretKey.keyID)) } @JvmStatic - fun s2kUsageFixIfNecessary(secretKeys: PGPSecretKeyRing, protector: SecretKeyRingProtector): PGPSecretKeyRing { - if (secretKeys.secretKeys.asSequence().any { it.s2KUsage == SecretKeyPacket.USAGE_CHECKSUM }) { + fun s2kUsageFixIfNecessary( + secretKeys: PGPSecretKeyRing, + protector: SecretKeyRingProtector + ): PGPSecretKeyRing { + if (secretKeys.secretKeys.asSequence().any { + it.s2KUsage == SecretKeyPacket.USAGE_CHECKSUM + }) { return S2KUsageFix.replaceUsageChecksumWithUsageSha1(secretKeys, protector, true) } return secretKeys } - } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/PublicKeyParameterValidationUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/PublicKeyParameterValidationUtil.kt index b5576928..57f0d423 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/PublicKeyParameterValidationUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/PublicKeyParameterValidationUtil.kt @@ -4,6 +4,10 @@ package org.pgpainless.key.util +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.math.BigInteger +import java.security.SecureRandom import org.bouncycastle.bcpg.* import org.bouncycastle.extensions.publicKeyAlgorithm import org.bouncycastle.openpgp.* @@ -15,16 +19,12 @@ import org.pgpainless.algorithm.SignatureType import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.implementation.ImplementationFactory.Companion.getInstance -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.math.BigInteger -import java.security.SecureRandom /** - * Utility class to verify keys against Key Overwriting (KO) attacks. - * This class of attacks is only possible if the attacker has access to the (encrypted) secret key material. - * To execute the attack, they would modify the unauthenticated parameters of the users public key. - * Using the modified public key in combination with the unmodified secret key material can then lead to the + * Utility class to verify keys against Key Overwriting (KO) attacks. This class of attacks is only + * possible if the attacker has access to the (encrypted) secret key material. To execute the + * attack, they would modify the unauthenticated parameters of the users public key. Using the + * modified public key in combination with the unmodified secret key material can then lead to the * extraction of secret key parameters via weakly crafted messages. * * @see Key Overwriting (KO) Attacks against OpenPGP @@ -41,19 +41,32 @@ class PublicKeyParameterValidationUtil { val key = privateKey.privateKeyDataPacket when (privateKey.privateKeyDataPacket) { is RSASecretBCPGKey -> - valid = verifyRSAKeyIntegrity(key as RSASecretBCPGKey, publicKey.publicKeyPacket.key as RSAPublicBCPGKey) + valid = + verifyRSAKeyIntegrity( + key as RSASecretBCPGKey, + publicKey.publicKeyPacket.key as RSAPublicBCPGKey) is EdSecretBCPGKey -> - valid = verifyEdDsaKeyIntegrity(key as EdSecretBCPGKey, publicKey.publicKeyPacket.key as EdDSAPublicBCPGKey) + valid = + verifyEdDsaKeyIntegrity( + key as EdSecretBCPGKey, + publicKey.publicKeyPacket.key as EdDSAPublicBCPGKey) is DSASecretBCPGKey -> - valid = verifyDsaKeyIntegrity(key as DSASecretBCPGKey, publicKey.publicKeyPacket.key as DSAPublicBCPGKey) + valid = + verifyDsaKeyIntegrity( + key as DSASecretBCPGKey, + publicKey.publicKeyPacket.key as DSAPublicBCPGKey) is ElGamalSecretBCPGKey -> - valid = verifyElGamalKeyIntegrity(key as ElGamalSecretBCPGKey, publicKey.publicKeyPacket.key as ElGamalPublicBCPGKey) + valid = + verifyElGamalKeyIntegrity( + key as ElGamalSecretBCPGKey, + publicKey.publicKeyPacket.key as ElGamalPublicBCPGKey) } if (!valid) throw KeyIntegrityException() // Additional to the algorithm-specific tests further above, we also perform - // generic functionality tests with the key, such as whether it is able to decrypt encrypted data + // generic functionality tests with the key, such as whether it is able to decrypt + // encrypted data // or verify signatures. // These tests should be more or less constant time. if (algorithm.isSigningCapable()) { @@ -68,21 +81,30 @@ class PublicKeyParameterValidationUtil { @JvmStatic @Throws(KeyIntegrityException::class) - private fun verifyRSAKeyIntegrity(secretKey: RSASecretBCPGKey, publicKey: RSAPublicBCPGKey): Boolean { + private fun verifyRSAKeyIntegrity( + secretKey: RSASecretBCPGKey, + publicKey: RSAPublicBCPGKey + ): Boolean { // Verify that the public keys N is equal to private keys p*q return publicKey.modulus.equals(secretKey.primeP.multiply(secretKey.primeQ)) } @JvmStatic @Throws(KeyIntegrityException::class) - private fun verifyEdDsaKeyIntegrity(secretKey: EdSecretBCPGKey, publicKey: EdDSAPublicBCPGKey): Boolean { + private fun verifyEdDsaKeyIntegrity( + secretKey: EdSecretBCPGKey, + publicKey: EdDSAPublicBCPGKey + ): Boolean { // TODO: Implement return true } @JvmStatic @Throws(KeyIntegrityException::class) - private fun verifyDsaKeyIntegrity(privateKey: DSASecretBCPGKey, publicKey: DSAPublicBCPGKey): Boolean { + private fun verifyDsaKeyIntegrity( + privateKey: DSASecretBCPGKey, + publicKey: DSAPublicBCPGKey + ): Boolean { // Not sure what value to put here in order to have a "robust" primality check // I went with 40, since that's what SO recommends: // https://stackoverflow.com/a/6330138 @@ -134,15 +156,20 @@ class PublicKeyParameterValidationUtil { /** * Validate ElGamal public key parameters. * - * Original implementation by the openpgpjs authors: - * OpenPGP.js + * source + * * @param secretKey secret key * @param publicKey public key * @return true if supposedly valid, false if invalid */ @JvmStatic @Throws(KeyIntegrityException::class) - private fun verifyElGamalKeyIntegrity(secretKey: ElGamalSecretBCPGKey, publicKey: ElGamalPublicBCPGKey): Boolean { + private fun verifyElGamalKeyIntegrity( + secretKey: ElGamalSecretBCPGKey, + publicKey: ElGamalPublicBCPGKey + ): Boolean { val p = publicKey.p val g = publicKey.g val y = publicKey.y @@ -185,7 +212,9 @@ class PublicKeyParameterValidationUtil { } /** - * Verify that the public key can be used to successfully verify a signature made by the private key. + * Verify that the public key can be used to successfully verify a signature made by the + * private key. + * * @param privateKey private key * @param publicKey public key * @return false if signature verification fails @@ -193,16 +222,23 @@ class PublicKeyParameterValidationUtil { @JvmStatic private fun verifyCanSign(privateKey: PGPPrivateKey, publicKey: PGPPublicKey): Boolean { val data = ByteArray(512).also { SecureRandom().nextBytes(it) } - val signatureGenerator = PGPSignatureGenerator( - getInstance().getPGPContentSignerBuilder(requireFromId(publicKey.algorithm), HashAlgorithm.SHA256)) + val signatureGenerator = + PGPSignatureGenerator( + getInstance() + .getPGPContentSignerBuilder( + requireFromId(publicKey.algorithm), HashAlgorithm.SHA256)) return try { - signatureGenerator.apply { - init(SignatureType.TIMESTAMP.code, privateKey) - update(data) - }.generate().apply { - init(getInstance().pgpContentVerifierBuilderProvider, publicKey) - update(data) - }.verify() + signatureGenerator + .apply { + init(SignatureType.TIMESTAMP.code, privateKey) + update(data) + } + .generate() + .apply { + init(getInstance().pgpContentVerifierBuilderProvider, publicKey) + update(data) + } + .verify() } catch (e: PGPException) { false } @@ -211,6 +247,7 @@ class PublicKeyParameterValidationUtil { /** * Verify that the public key can be used to encrypt a message which can successfully be * decrypted using the private key. + * * @param privateKey private key * @param publicKey public key * @return false if decryption of a message encrypted with the public key fails @@ -218,10 +255,12 @@ class PublicKeyParameterValidationUtil { @JvmStatic private fun verifyCanDecrypt(privateKey: PGPPrivateKey, publicKey: PGPPublicKey): Boolean { val data = ByteArray(1024).also { SecureRandom().nextBytes(it) } - val encryptedDataGenerator = PGPEncryptedDataGenerator( - getInstance().getPGPDataEncryptorBuilder(SymmetricKeyAlgorithm.AES_256)).apply { - addMethod(getInstance().getPublicKeyKeyEncryptionMethodGenerator(publicKey)) - } + val encryptedDataGenerator = + PGPEncryptedDataGenerator( + getInstance().getPGPDataEncryptorBuilder(SymmetricKeyAlgorithm.AES_256)) + .apply { + addMethod(getInstance().getPublicKeyKeyEncryptionMethodGenerator(publicKey)) + } var out = ByteArrayOutputStream() try { @@ -230,7 +269,8 @@ class PublicKeyParameterValidationUtil { encryptedDataGenerator.close() val encryptedDataList = PGPEncryptedDataList(out.toByteArray()) val decryptorFactory = getInstance().getPublicKeyDataDecryptorFactory(privateKey) - val encryptedData = encryptedDataList.encryptedDataObjects.next() as PGPPublicKeyEncryptedData + val encryptedData = + encryptedDataList.encryptedDataObjects.next() as PGPPublicKeyEncryptedData val decrypted = encryptedData.getDataStream(decryptorFactory) out = ByteArrayOutputStream() Streams.pipeAll(decrypted, out) @@ -243,4 +283,4 @@ class PublicKeyParameterValidationUtil { return Arrays.constantTimeAreEqual(data, out.toByteArray()) } } -} \ No newline at end of file +} 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 index 39f69fbe..2300325a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/RevocationAttributes.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/RevocationAttributes.kt @@ -4,50 +4,43 @@ package org.pgpainless.key.util -class RevocationAttributes( - val reason: Reason, - val description: String) { +class RevocationAttributes(val reason: Reason, val description: String) { /** - * Reason for revocation. - * There are two kinds of reasons: hard and soft reason. + * 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. + * 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. + * 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. + * 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. + * 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. + * 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. + * 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. - */ + /** The user-id is no longer valid. This is a SOFT revocation reason and can be undone. */ USER_ID_NO_LONGER_VALID(32), ; @@ -59,8 +52,7 @@ class RevocationAttributes( companion object { - @JvmStatic - private val MAP = values().associateBy { it.code } + @JvmStatic private val MAP = values().associateBy { it.code } /** * Decode a machine-readable reason code. @@ -69,13 +61,13 @@ class RevocationAttributes( * @return reason */ @JvmStatic - fun fromCode(code: Byte) = MAP[code] ?: throw IllegalArgumentException("Invalid revocation reason: $code") + 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. + * 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 @@ -84,21 +76,25 @@ class RevocationAttributes( 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. + * 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 - } + 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 */ @@ -107,14 +103,16 @@ class RevocationAttributes( /** * 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 - } + fun isKeyRevocation(reason: Reason) = + when (reason) { + USER_ID_NO_LONGER_VALID -> false + else -> true + } } } @@ -124,11 +122,9 @@ class RevocationAttributes( } companion object { - @JvmStatic - fun createKeyRevocation() = WithReason(RevocationType.KEY_REVOCATION) + @JvmStatic fun createKeyRevocation() = WithReason(RevocationType.KEY_REVOCATION) - @JvmStatic - fun createCertificateRevocation() = WithReason(RevocationType.CERT_REVOCATION) + @JvmStatic fun createCertificateRevocation() = WithReason(RevocationType.CERT_REVOCATION) } class WithReason(val type: RevocationType) { @@ -143,13 +139,16 @@ class RevocationAttributes( 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 + 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 withDescription(description: String): RevocationAttributes = + RevocationAttributes(reason, description) + fun withoutDescription() = RevocationAttributes(reason, "") } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/UserId.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/UserId.kt index b461f6b4..c2b81700 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/UserId.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/UserId.kt @@ -4,11 +4,7 @@ package org.pgpainless.key.util -class UserId internal constructor( - name: String?, - comment: String?, - email: String? -) : CharSequence { +class UserId internal constructor(name: String?, comment: String?, email: String?) : CharSequence { private val _name: String? val comment: String? @@ -60,9 +56,9 @@ class UserId internal constructor( if (other !is UserId) { return false } - return isComponentEqual(_name, other._name, false) - && isComponentEqual(comment, other.comment, false) - && isComponentEqual(email, other.email, true) + return isComponentEqual(_name, other._name, false) && + isComponentEqual(comment, other.comment, false) && + isComponentEqual(email, other.email, true) } override fun get(index: Int): Char { @@ -81,59 +77,73 @@ class UserId internal constructor( return full } - private fun isComponentEqual(value: String?, otherValue: String?, ignoreCase: Boolean): Boolean = value.equals(otherValue, ignoreCase) + private fun isComponentEqual( + value: String?, + otherValue: String?, + ignoreCase: Boolean + ): Boolean = value.equals(otherValue, ignoreCase) - fun toBuilder() = builder().also { builder -> - if (this._name != null) builder.withName(_name) - if (this.comment != null) builder.withComment(comment) - if (this.email != null) builder.withEmail(email) - } + fun toBuilder() = + builder().also { builder -> + if (this._name != null) builder.withName(_name) + if (this.comment != null) builder.withComment(comment) + if (this.email != null) builder.withEmail(email) + } companion object { // Email regex: https://emailregex.com/ - // switched "a-z0-9" to "\p{L}\u0900-\u097F0-9" for better support for international characters + // switched "a-z0-9" to "\p{L}\u0900-\u097F0-9" for better support for international + // characters // \\p{L} = Unicode Letters // \u0900-\u097F = Hindi Letters @JvmStatic - private val emailPattern = ("(?:[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+(?:\\.[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-" + - "\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9" + - "-]*[\\p{L}\\u0900-\\u097F0-9])?\\.)+[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + - "\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[$\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f" + - "\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])").toPattern() + private val emailPattern = + ("(?:[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+(?:\\.[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-" + + "\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9" + + "-]*[\\p{L}\\u0900-\\u097F0-9])?\\.)+[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[$\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f" + + "\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])") + .toPattern() // User-ID Regex // "Firstname Lastname (Comment) " // All groups are optional // https://www.rfc-editor.org/rfc/rfc5322#page-16 @JvmStatic - private val nameAddrPattern = "^((?.+?)\\s)?(\\((?.+?)\\)\\s)?(<(?.+?)>)?$".toPattern() + private val nameAddrPattern = + "^((?.+?)\\s)?(\\((?.+?)\\)\\s)?(<(?.+?)>)?$".toPattern() /** - * Parse a [UserId] from free-form text,
name-addr
or
mailbox
string and split it - * up into its components. - * Example inputs for this method: + * Parse a [UserId] from free-form text,
name-addr
or
mailbox
string + * and split it up into its components. Example inputs for this method: *
    - *
  • john@pgpainless.org
  • - *
  • <john@pgpainless.org>
  • - *
  • John Doe
  • - *
  • John Doe <john@pgpainless.org>
  • - *
  • John Doe (work email) <john@pgpainless.org>
  • + *
  • john@pgpainless.org
  • + *
  • <john@pgpainless.org>
  • + *
  • John Doe
  • + *
  • John Doe <john@pgpainless.org>
  • + *
  • John Doe (work email) <john@pgpainless.org>
  • *
- * In these cases, this method will detect email addresses, names and comments and expose those - * via the respective getters. - * This method does not support parsing mail addresses of the following formats: - *
    - *
  • Local domains without TLDs (
    user@localdomain1
    )
  • - *
  • " "@example.org
    (spaces between the quotes)
  • - *
  • "very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com
  • - *
- * Note: This method does not guarantee that
string.equals(UserId.parse(string).toString())
is true. - * For example,
UserId.parse("alice@pgpainless.org").toString()
wraps the mail address in angled brackets. * - * @see RFC5322 §3.4. Address Specification + * In these cases, this method will detect email addresses, names and comments and expose + * those via the respective getters. This method does not support parsing mail addresses of + * the following formats: + *
    + *
  • Local domains without TLDs (
    user@localdomain1
    )
  • + *
  • " "@example.org
    (spaces between the quotes)
  • + *
  • "very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com
  • + *
+ * + * Note: This method does not guarantee that + *
string.equals(UserId.parse(string).toString())
is true. For example, + *
UserId.parse("alice@pgpainless.org").toString()
wraps the mail address in + * + * angled brackets. + * * @param string user-id * @return parsed UserId object + * @see RFC5322 §3.4. Address + * Specification */ @JvmStatic fun parse(string: String): UserId { @@ -152,21 +162,19 @@ class UserId internal constructor( } } - @JvmStatic - fun onlyEmail(email: String) = UserId(null, null, email) + @JvmStatic fun onlyEmail(email: String) = UserId(null, null, email) + + @JvmStatic fun nameAndEmail(name: String, email: String) = UserId(name, null, email) @JvmStatic - fun nameAndEmail(name: String, email: String) = UserId(name, null, email) - - @JvmStatic - fun compare(u1: UserId?, u2: UserId?, comparator: Comparator) = comparator.compare(u1, u2) + fun compare(u1: UserId?, u2: UserId?, comparator: Comparator) = + comparator.compare(u1, u2) @JvmStatic @Deprecated("Deprecated in favor of builde() method.", ReplaceWith("builder()")) fun newBuilder() = builder() - @JvmStatic - fun builder() = Builder() + @JvmStatic fun builder() = Builder() } class Builder internal constructor() { @@ -175,11 +183,15 @@ class UserId internal constructor( var email: String? = null fun withName(name: String) = apply { this.name = name } - fun withComment(comment: String) = apply { this.comment = comment} + + fun withComment(comment: String) = apply { this.comment = comment } + fun withEmail(email: String) = apply { this.email = email } fun noName() = apply { this.name = null } + fun noComment() = apply { this.comment = null } + fun noEmail() = apply { this.email = null } fun build() = UserId(name, comment, email) @@ -188,19 +200,18 @@ class UserId internal constructor( class DefaultComparator : Comparator { override fun compare(o1: UserId?, o2: UserId?): Int { return compareBy { it?._name } - .thenBy { it?.comment } - .thenBy { it?.email } - .compare(o1, o2) + .thenBy { it?.comment } + .thenBy { it?.email } + .compare(o1, o2) } } class DefaultIgnoreCaseComparator : Comparator { override fun compare(p0: UserId?, p1: UserId?): Int { return compareBy { it?._name?.lowercase() } - .thenBy { it?.comment?.lowercase() } - .thenBy { it?.email?.lowercase() } - .compare(p0, p1) + .thenBy { it?.comment?.lowercase() } + .thenBy { it?.email?.lowercase() } + .compare(p0, p1) } - } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt index b7c9f39e..22f42cd9 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt @@ -4,21 +4,23 @@ package org.pgpainless.policy +import java.util.* import org.pgpainless.algorithm.* import org.pgpainless.util.DateUtil import org.pgpainless.util.NotationRegistry -import java.util.* class Policy( - var signatureHashAlgorithmPolicy: HashAlgorithmPolicy, - var revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, - var symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, - var symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, - var compressionAlgorithmPolicy: CompressionAlgorithmPolicy, - var publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy, - var notationRegistry: NotationRegistry) { + var signatureHashAlgorithmPolicy: HashAlgorithmPolicy, + var revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + var symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, + var symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, + var compressionAlgorithmPolicy: CompressionAlgorithmPolicy, + var publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy, + var notationRegistry: NotationRegistry +) { - constructor(): this( + constructor() : + this( HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(), HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(), SymmetricKeyAlgorithmPolicy.symmetricKeyEncryptionPolicy2022(), @@ -33,29 +35,32 @@ class Policy( fun isEnableKeyParameterValidation() = enableKeyParameterValidation - /** - * Create a HashAlgorithmPolicy which accepts all [HashAlgorithms][HashAlgorithm] from the - * given map, if the queried usage date is BEFORE the respective termination date. - * A termination date value of
null
means no termination, resulting in the algorithm being - * acceptable, regardless of usage date. + * Create a HashAlgorithmPolicy which accepts all [HashAlgorithms][HashAlgorithm] from the given + * map, if the queried usage date is BEFORE the respective termination date. A termination date + * value of
null
means no termination, resulting in the algorithm being acceptable, + * regardless of usage date. * * @param defaultHashAlgorithm default hash algorithm * @param algorithmTerminationDates map of acceptable algorithms and their termination dates */ class HashAlgorithmPolicy( - val defaultHashAlgorithm: HashAlgorithm, - val acceptableHashAlgorithmsAndTerminationDates: Map) { + val defaultHashAlgorithm: HashAlgorithm, + val acceptableHashAlgorithmsAndTerminationDates: Map + ) { /** - * Create a [HashAlgorithmPolicy] which accepts all [HashAlgorithms][HashAlgorithm] listed in - * the given list, regardless of usage date. + * Create a [HashAlgorithmPolicy] which accepts all [HashAlgorithms][HashAlgorithm] listed + * in the given list, regardless of usage date. * - * @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation fails) + * @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation + * fails) * @param acceptableHashAlgorithms list of acceptable hash algorithms */ - constructor(defaultHashAlgorithm: HashAlgorithm, acceptableHashAlgorithms: List) : - this(defaultHashAlgorithm, acceptableHashAlgorithms.associateWith { null }) + constructor( + defaultHashAlgorithm: HashAlgorithm, + acceptableHashAlgorithms: List + ) : this(defaultHashAlgorithm, acceptableHashAlgorithms.associateWith { null }) fun isAcceptable(hashAlgorithm: HashAlgorithm) = isAcceptable(hashAlgorithm, Date()) @@ -64,13 +69,13 @@ class Policy( * * @param hashAlgorithm algorithm * @param referenceTime usage date (e.g. signature creation time) - * * @return acceptance */ fun isAcceptable(hashAlgorithm: HashAlgorithm, referenceTime: Date): Boolean { if (!acceptableHashAlgorithmsAndTerminationDates.containsKey(hashAlgorithm)) return false - val terminationDate = acceptableHashAlgorithmsAndTerminationDates[hashAlgorithm] ?: return true + val terminationDate = + acceptableHashAlgorithmsAndTerminationDates[hashAlgorithm] ?: return true return terminationDate > referenceTime } @@ -85,66 +90,73 @@ class Policy( companion object { @JvmStatic - fun smartSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( - HashAlgorithm.SHA512, buildMap { - put(HashAlgorithm.SHA3_512, null) - put(HashAlgorithm.SHA3_256, null) - put(HashAlgorithm.SHA512, null) - put(HashAlgorithm.SHA384, null) - put(HashAlgorithm.SHA256, null) - put(HashAlgorithm.SHA224, null) - put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) - put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) - put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")) - }) + fun smartSignatureHashAlgorithmPolicy() = + HashAlgorithmPolicy( + HashAlgorithm.SHA512, + buildMap { + put(HashAlgorithm.SHA3_512, null) + put(HashAlgorithm.SHA3_256, null) + put(HashAlgorithm.SHA512, null) + put(HashAlgorithm.SHA384, null) + put(HashAlgorithm.SHA256, null) + put(HashAlgorithm.SHA224, null) + put( + HashAlgorithm.RIPEMD160, + DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) + put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) + put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")) + }) /** - * [HashAlgorithmPolicy] which only accepts signatures made using algorithms which are acceptable - * according to 2022 standards. + * [HashAlgorithmPolicy] which only accepts signatures made using algorithms which are + * acceptable according to 2022 standards. * * Particularly this policy only accepts algorithms from the SHA2 and SHA3 families. * * @return static signature algorithm policy */ @JvmStatic - fun static2022SignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( + fun static2022SignatureHashAlgorithmPolicy() = + HashAlgorithmPolicy( HashAlgorithm.SHA512, listOf( - HashAlgorithm.SHA3_512, - HashAlgorithm.SHA3_256, - HashAlgorithm.SHA512, - HashAlgorithm.SHA384, - HashAlgorithm.SHA256, - HashAlgorithm.SHA224) - ) + HashAlgorithm.SHA3_512, + HashAlgorithm.SHA3_256, + HashAlgorithm.SHA512, + HashAlgorithm.SHA384, + HashAlgorithm.SHA256, + HashAlgorithm.SHA224)) /** - * Hash algorithm policy for revocation signatures, which accepts SHA1 and SHA2 algorithms, as well as RIPEMD160. + * Hash algorithm policy for revocation signatures, which accepts SHA1 and SHA2 + * algorithms, as well as RIPEMD160. * * @return static revocation signature hash algorithm policy */ @JvmStatic - fun static2022RevocationSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( + fun static2022RevocationSignatureHashAlgorithmPolicy() = + HashAlgorithmPolicy( HashAlgorithm.SHA512, listOf( - HashAlgorithm.SHA3_512, - HashAlgorithm.SHA3_256, - HashAlgorithm.SHA512, - HashAlgorithm.SHA384, - HashAlgorithm.SHA256, - HashAlgorithm.SHA224, - HashAlgorithm.SHA1, - HashAlgorithm.RIPEMD160 - ) - ) + HashAlgorithm.SHA3_512, + HashAlgorithm.SHA3_256, + HashAlgorithm.SHA512, + HashAlgorithm.SHA384, + HashAlgorithm.SHA256, + HashAlgorithm.SHA224, + HashAlgorithm.SHA1, + HashAlgorithm.RIPEMD160)) } } class SymmetricKeyAlgorithmPolicy( - val defaultSymmetricKeyAlgorithm: SymmetricKeyAlgorithm, - val acceptableSymmetricKeyAlgorithms: List) { + val defaultSymmetricKeyAlgorithm: SymmetricKeyAlgorithm, + val acceptableSymmetricKeyAlgorithms: List + ) { + + fun isAcceptable(algorithm: SymmetricKeyAlgorithm) = + acceptableSymmetricKeyAlgorithms.contains(algorithm) - fun isAcceptable(algorithm: SymmetricKeyAlgorithm) = acceptableSymmetricKeyAlgorithms.contains(algorithm) fun isAcceptable(algorithmId: Int): Boolean { val algorithm = SymmetricKeyAlgorithm.fromId(algorithmId) ?: return false return isAcceptable(algorithm) @@ -169,29 +181,29 @@ class Policy( */ @JvmStatic @Deprecated( - "Not expressive - will be removed in a future release", - ReplaceWith("symmetricKeyEncryptionPolicy2022")) + "Not expressive - will be removed in a future release", + ReplaceWith("symmetricKeyEncryptionPolicy2022")) fun defaultSymmetricKeyEncryptionAlgorithmPolicy() = symmetricKeyEncryptionPolicy2022() /** - * Policy for symmetric encryption algorithms in the context of message production (encryption). - * This suite contains algorithms that are deemed safe to use in 2022. + * Policy for symmetric encryption algorithms in the context of message production + * (encryption). This suite contains algorithms that are deemed safe to use in 2022. * * @return 2022 symmetric key encryption algorithm policy */ @JvmStatic - fun symmetricKeyEncryptionPolicy2022() = SymmetricKeyAlgorithmPolicy( + fun symmetricKeyEncryptionPolicy2022() = + SymmetricKeyAlgorithmPolicy( SymmetricKeyAlgorithm.AES_128, // Reject: Unencrypted, IDEA, TripleDES, CAST5, Blowfish listOf( - SymmetricKeyAlgorithm.AES_256, - SymmetricKeyAlgorithm.AES_192, - SymmetricKeyAlgorithm.AES_128, - SymmetricKeyAlgorithm.TWOFISH, - SymmetricKeyAlgorithm.CAMELLIA_256, - SymmetricKeyAlgorithm.CAMELLIA_192, - SymmetricKeyAlgorithm.CAMELLIA_128 - )) + SymmetricKeyAlgorithm.AES_256, + SymmetricKeyAlgorithm.AES_192, + SymmetricKeyAlgorithm.AES_128, + SymmetricKeyAlgorithm.TWOFISH, + SymmetricKeyAlgorithm.CAMELLIA_256, + SymmetricKeyAlgorithm.CAMELLIA_192, + SymmetricKeyAlgorithm.CAMELLIA_128)) /** * The default symmetric decryption algorithm policy of PGPainless. @@ -200,38 +212,42 @@ class Policy( * @deprecated not expressive - will be removed in a future update */ @JvmStatic - @Deprecated("not expressive - will be removed in a future update", - ReplaceWith("symmetricKeyDecryptionPolicy2022()")) + @Deprecated( + "not expressive - will be removed in a future update", + ReplaceWith("symmetricKeyDecryptionPolicy2022()")) fun defaultSymmetricKeyDecryptionAlgorithmPolicy() = symmetricKeyDecryptionPolicy2022() /** - * Policy for symmetric key encryption algorithms in the context of message consumption (decryption). - * This suite contains algorithms that are deemed safe to use in 2022. + * Policy for symmetric key encryption algorithms in the context of message consumption + * (decryption). This suite contains algorithms that are deemed safe to use in 2022. * * @return 2022 symmetric key decryption algorithm policy */ @JvmStatic - fun symmetricKeyDecryptionPolicy2022() = SymmetricKeyAlgorithmPolicy( + fun symmetricKeyDecryptionPolicy2022() = + SymmetricKeyAlgorithmPolicy( SymmetricKeyAlgorithm.AES_128, // Reject: Unencrypted, IDEA, TripleDES, Blowfish listOf( - SymmetricKeyAlgorithm.AES_256, - SymmetricKeyAlgorithm.AES_192, - SymmetricKeyAlgorithm.AES_128, - SymmetricKeyAlgorithm.TWOFISH, - SymmetricKeyAlgorithm.CAMELLIA_256, - SymmetricKeyAlgorithm.CAMELLIA_192, - SymmetricKeyAlgorithm.CAMELLIA_128, - SymmetricKeyAlgorithm.CAST5 - )) + SymmetricKeyAlgorithm.AES_256, + SymmetricKeyAlgorithm.AES_192, + SymmetricKeyAlgorithm.AES_128, + SymmetricKeyAlgorithm.TWOFISH, + SymmetricKeyAlgorithm.CAMELLIA_256, + SymmetricKeyAlgorithm.CAMELLIA_192, + SymmetricKeyAlgorithm.CAMELLIA_128, + SymmetricKeyAlgorithm.CAST5)) } } class CompressionAlgorithmPolicy( - val defaultCompressionAlgorithm: CompressionAlgorithm, - val acceptableCompressionAlgorithms: List) { + val defaultCompressionAlgorithm: CompressionAlgorithm, + val acceptableCompressionAlgorithms: List + ) { + + fun isAcceptable(algorithm: CompressionAlgorithm) = + acceptableCompressionAlgorithms.contains(algorithm) - fun isAcceptable(algorithm: CompressionAlgorithm) = acceptableCompressionAlgorithms.contains(algorithm) fun isAcceptable(algorithmId: Int): Boolean { val algorithm = CompressionAlgorithm.fromId(algorithmId) ?: return false return isAcceptable(algorithm) @@ -242,30 +258,33 @@ class Policy( companion object { /** - * Default {@link CompressionAlgorithmPolicy} of PGPainless. - * The default compression algorithm policy accepts any compression algorithm. + * Default {@link CompressionAlgorithmPolicy} of PGPainless. The default compression + * algorithm policy accepts any compression algorithm. * * @return default algorithm policy * @deprecated not expressive - might be removed in a future release */ @JvmStatic - @Deprecated("not expressive - might be removed in a future release", - ReplaceWith("anyCompressionAlgorithmPolicy()")) + @Deprecated( + "not expressive - might be removed in a future release", + ReplaceWith("anyCompressionAlgorithmPolicy()")) fun defaultCompressionAlgorithmPolicy() = anyCompressionAlgorithmPolicy() /** - * Policy that accepts any known compression algorithm and offers [CompressionAlgorithm.ZIP] as - * default algorithm. + * Policy that accepts any known compression algorithm and offers + * [CompressionAlgorithm.ZIP] as default algorithm. * * @return compression algorithm policy */ @JvmStatic - fun anyCompressionAlgorithmPolicy() = CompressionAlgorithmPolicy( + fun anyCompressionAlgorithmPolicy() = + CompressionAlgorithmPolicy( CompressionAlgorithm.ZIP, - listOf(CompressionAlgorithm.UNCOMPRESSED, - CompressionAlgorithm.ZIP, - CompressionAlgorithm.BZIP2, - CompressionAlgorithm.ZLIB)) + listOf( + CompressionAlgorithm.UNCOMPRESSED, + CompressionAlgorithm.ZIP, + CompressionAlgorithm.BZIP2, + CompressionAlgorithm.ZLIB)) } } @@ -283,77 +302,84 @@ class Policy( companion object { /** - * Return PGPainless' default public key algorithm policy. - * This policy is based upon recommendations made by the German Federal Office for Information Security (BSI). + * Return PGPainless' default public key algorithm policy. This policy is based upon + * recommendations made by the German Federal Office for Information Security (BSI). * * @return default algorithm policy * @deprecated not expressive - might be removed in a future release */ @JvmStatic - @Deprecated("not expressive - might be removed in a future release", - ReplaceWith("bsi2021PublicKeyAlgorithmPolicy()")) + @Deprecated( + "not expressive - might be removed in a future release", + ReplaceWith("bsi2021PublicKeyAlgorithmPolicy()")) fun defaultPublicKeyAlgorithmPolicy() = bsi2021PublicKeyAlgorithmPolicy() /** - * This policy is based upon recommendations made by the German Federal Office for Information Security (BSI). + * This policy is based upon recommendations made by the German Federal Office for + * Information Security (BSI). * - * Basically this policy requires keys based on elliptic curves to have a bit strength of at least 250, - * and keys based on prime number factorization / discrete logarithm problems to have a strength of at least 2000 bits. - * - * @see BSI - Technical Guideline - Cryptographic Mechanisms: Recommendations and Key Lengths (2021-01) - * @see BlueKrypt | Cryptographic Key Length Recommendation + * Basically this policy requires keys based on elliptic curves to have a bit strength + * of at least 250, and keys based on prime number factorization / discrete logarithm + * problems to have a strength of at least 2000 bits. * * @return default algorithm policy + * @see BSI - + * Technical Guideline - Cryptographic Mechanisms: Recommendations and Key Lengths + * (2021-01) + * @see BlueKrypt | Cryptographic Key Length + * Recommendation */ @JvmStatic - fun bsi2021PublicKeyAlgorithmPolicy() = PublicKeyAlgorithmPolicy(buildMap { - // §5.4.1 - put(PublicKeyAlgorithm.RSA_GENERAL, 2000) - put(PublicKeyAlgorithm.RSA_SIGN, 2000) - put(PublicKeyAlgorithm.RSA_ENCRYPT, 2000) - // Note: ElGamal is not mentioned in the BSI document. - // We assume that the requirements are similar to other DH algorithms - put(PublicKeyAlgorithm.ELGAMAL_ENCRYPT, 2000) - put(PublicKeyAlgorithm.ELGAMAL_GENERAL, 2000) - // §5.4.2 - put(PublicKeyAlgorithm.DSA, 2000) - // §5.4.3 - put(PublicKeyAlgorithm.ECDSA, 250) - // Note: EdDSA is not mentioned in the BSI document. - // We assume that the requirements are similar to other EC algorithms. - put(PublicKeyAlgorithm.EDDSA, 250) - // §7.2.1 - put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000) - // §7.2.2 - put(PublicKeyAlgorithm.ECDH, 250) - }) + fun bsi2021PublicKeyAlgorithmPolicy() = + PublicKeyAlgorithmPolicy( + buildMap { + // §5.4.1 + put(PublicKeyAlgorithm.RSA_GENERAL, 2000) + put(PublicKeyAlgorithm.RSA_SIGN, 2000) + put(PublicKeyAlgorithm.RSA_ENCRYPT, 2000) + // Note: ElGamal is not mentioned in the BSI document. + // We assume that the requirements are similar to other DH algorithms + put(PublicKeyAlgorithm.ELGAMAL_ENCRYPT, 2000) + put(PublicKeyAlgorithm.ELGAMAL_GENERAL, 2000) + // §5.4.2 + put(PublicKeyAlgorithm.DSA, 2000) + // §5.4.3 + put(PublicKeyAlgorithm.ECDSA, 250) + // Note: EdDSA is not mentioned in the BSI document. + // We assume that the requirements are similar to other EC algorithms. + put(PublicKeyAlgorithm.EDDSA, 250) + // §7.2.1 + put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000) + // §7.2.2 + put(PublicKeyAlgorithm.ECDH, 250) + }) } } enum class SignerUserIdValidationLevel { /** - * PGPainless will verify {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets in signatures strictly. - * This means, that signatures with Signer's User-ID subpackets containing a value that does not match the signer key's - * user-id exactly, will be rejected. - * E.g. Signer's user-id "alice@pgpainless.org", User-ID: "Alice <alice@pgpainless.org>" does not - * match exactly and is therefore rejected. + * PGPainless will verify {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets in + * signatures strictly. This means, that signatures with Signer's User-ID subpackets + * containing a value that does not match the signer key's user-id exactly, will be + * rejected. E.g. Signer's user-id "alice@pgpainless.org", User-ID: "Alice + * <alice@pgpainless.org>" does not match exactly and is therefore rejected. */ STRICT, /** - * PGPainless will ignore {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets on signature. + * PGPainless will ignore {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets on + * signature. */ DISABLED } companion object { - @Volatile - private var INSTANCE: Policy? = null + @Volatile private var INSTANCE: Policy? = null @JvmStatic - fun getInstance() = INSTANCE ?: synchronized(this) { - INSTANCE ?: Policy().also { INSTANCE = it } - } + fun getInstance() = + INSTANCE ?: synchronized(this) { INSTANCE ?: Policy().also { INSTANCE = it } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/provider/BouncyCastleProviderFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/provider/BouncyCastleProviderFactory.kt index 4654f529..27192953 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/provider/BouncyCastleProviderFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/provider/BouncyCastleProviderFactory.kt @@ -4,9 +4,9 @@ package org.pgpainless.provider -import org.bouncycastle.jce.provider.BouncyCastleProvider import java.security.Provider +import org.bouncycastle.jce.provider.BouncyCastleProvider class BouncyCastleProviderFactory : ProviderFactory() { override val securityProvider: Provider = BouncyCastleProvider() -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/provider/ProviderFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/provider/ProviderFactory.kt index 3fe127f2..531ae54b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/provider/ProviderFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/provider/ProviderFactory.kt @@ -7,11 +7,10 @@ package org.pgpainless.provider import java.security.Provider /** - * Allow the use of different [Provider] implementations to provide cryptographic primitives by setting - * a [ProviderFactory] singleton. - * By default, the class is initialized with a [BouncyCastleProviderFactory]. - * To make use of your own custom [Provider], call [setFactory], passing your - * own custom [ProviderFactory] instance. + * Allow the use of different [Provider] implementations to provide cryptographic primitives by + * setting a [ProviderFactory] singleton. By default, the class is initialized with a + * [BouncyCastleProviderFactory]. To make use of your own custom [Provider], call [setFactory], + * passing your own custom [ProviderFactory] instance. */ abstract class ProviderFactory { @@ -21,16 +20,14 @@ abstract class ProviderFactory { companion object { // singleton instance - @JvmStatic - var factory: ProviderFactory = BouncyCastleProviderFactory() + @JvmStatic var factory: ProviderFactory = BouncyCastleProviderFactory() @JvmStatic val provider: Provider - @JvmName("getProvider") - get() = factory.securityProvider + @JvmName("getProvider") get() = factory.securityProvider @JvmStatic val providerName: String get() = factory.securityProviderName } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt index 492c4fe4..e84ed0d3 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/SignatureUtils.kt @@ -4,6 +4,9 @@ package org.pgpainless.signature +import java.io.IOException +import java.io.InputStream +import java.util.* import openpgp.plusSeconds import org.bouncycastle.bcpg.sig.KeyExpirationTime import org.bouncycastle.extensions.* @@ -14,9 +17,6 @@ import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.util.RevocationAttributes.Reason import org.pgpainless.util.ArmorUtils -import java.io.IOException -import java.io.InputStream -import java.util.* const val MAX_ITERATIONS = 10000 @@ -24,89 +24,103 @@ class SignatureUtils { companion object { /** - * Extract and return the key expiration date value from the given signature. - * If the signature does not carry a [KeyExpirationTime] subpacket, return null. + * Extract and return the key expiration date value from the given signature. If the + * signature does not carry a [KeyExpirationTime] subpacket, return null. * * @param keyCreationDate creation date of the key * @param signature signature * @return key expiration date as given by the signature */ @JvmStatic - @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.getKeyExpirationDate(keyCreationDate)", "org.bouncycastle.extensions.getKeyExpirationDate")) + @Deprecated( + "Deprecated in favor of PGPSignature extension method.", + ReplaceWith( + "signature.getKeyExpirationDate(keyCreationDate)", + "org.bouncycastle.extensions.getKeyExpirationDate")) fun getKeyExpirationDate(keyCreationDate: Date, signature: PGPSignature): Date? { return signature.getKeyExpirationDate(keyCreationDate) } /** - * Return the expiration date of the signature. - * If the signature has no expiration date, this will return null. + * Return the expiration date of the signature. If the signature has no expiration date, + * this will return null. * * @param signature signature * @return expiration date of the signature, or null if it does not expire. */ @JvmStatic - @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.signatureExpirationDate", "org.bouncycastle.extensions.signatureExpirationDate")) - fun getSignatureExpirationDate(signature: PGPSignature): Date? = signature.signatureExpirationDate + @Deprecated( + "Deprecated in favor of PGPSignature extension method.", + ReplaceWith( + "signature.signatureExpirationDate", + "org.bouncycastle.extensions.signatureExpirationDate")) + fun getSignatureExpirationDate(signature: PGPSignature): Date? = + signature.signatureExpirationDate /** * Return a new date which represents the given date plus the given amount of seconds added. * - * Since '0' is a special date value in the OpenPGP specification - * (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0. + * Since '0' is a special date value in the OpenPGP specification (e.g. '0' means no + * expiration for expiration dates), this method will return 'null' if seconds is 0. * * @param date date * @param seconds number of seconds to be added * @return date plus seconds or null if seconds is '0' */ @JvmStatic - @Deprecated("Deprecated in favor of Date extension method.", - ReplaceWith("date.plusSeconds(seconds)", "openpgp.plusSeconds")) + @Deprecated( + "Deprecated in favor of Date extension method.", + ReplaceWith("date.plusSeconds(seconds)", "openpgp.plusSeconds")) fun datePlusSeconds(date: Date, seconds: Long): Date? { return date.plusSeconds(seconds) } /** - * Return true, if the expiration date of the [PGPSignature] lays in the past. - * If no expiration date is present in the signature, it is considered non-expired. + * Return true, if the expiration date of the [PGPSignature] lays in the past. If no + * expiration date is present in the signature, it is considered non-expired. * * @param signature signature * @return true if expired, false otherwise */ @JvmStatic - @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.isExpired()", "org.bouncycastle.extensions.isExpired")) + @Deprecated( + "Deprecated in favor of PGPSignature extension method.", + ReplaceWith("signature.isExpired()", "org.bouncycastle.extensions.isExpired")) fun isSignatureExpired(signature: PGPSignature): Boolean { return signature.isExpired() } /** - * Return true, if the expiration date of the given [PGPSignature] is past the given comparison [Date]. - * If no expiration date is present in the signature, it is considered non-expiring. + * Return true, if the expiration date of the given [PGPSignature] is past the given + * comparison [Date]. If no expiration date is present in the signature, it is considered + * non-expiring. * * @param signature signature * @param referenceTime reference date * @return true if sig is expired at reference date, false otherwise */ @JvmStatic - @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.isExpired(referenceTime)", "org.bouncycastle.extensions.isExpired")) + @Deprecated( + "Deprecated in favor of PGPSignature extension method.", + ReplaceWith( + "signature.isExpired(referenceTime)", "org.bouncycastle.extensions.isExpired")) fun isSignatureExpired(signature: PGPSignature, referenceTime: Date): Boolean { return signature.isExpired(referenceTime) } /** - * Return true if the provided signature is a hard revocation. - * Hard revocations are revocation signatures which either carry a revocation reason of - * [Reason.KEY_COMPROMISED] or [Reason.NO_REASON], or no reason at all. + * Return true if the provided signature is a hard revocation. Hard revocations are + * revocation signatures which either carry a revocation reason of [Reason.KEY_COMPROMISED] + * or [Reason.NO_REASON], or no reason at all. * * @param signature signature * @return true if signature is a hard revocation */ @JvmStatic - @Deprecated("Deprecated in favor of PGPSignature extension function.", - ReplaceWith("signature.isHardRevocation", "org.bouncycastle.extensions.isHardRevocation")) + @Deprecated( + "Deprecated in favor of PGPSignature extension function.", + ReplaceWith( + "signature.isHardRevocation", "org.bouncycastle.extensions.isHardRevocation")) fun isHardRevocation(signature: PGPSignature): Boolean { return signature.isHardRevocation } @@ -128,8 +142,8 @@ class SignatureUtils { } /** - * Read and return [PGPSignatures][PGPSignature]. - * This method can deal with signatures that may be binary, armored and may contain marker packets. + * Read and return [PGPSignatures][PGPSignature]. This method can deal with signatures that + * may be binary, armored and may contain marker packets. * * @param inputStream input stream * @param maxIterations number of loop iterations until reading is aborted @@ -143,13 +157,18 @@ class SignatureUtils { var i = 0 var nextObject: Any? = null - while (i++ < maxIterations && objectFactory.nextObject().also { nextObject = it } != null) { - // Since signatures are indistinguishable from randomness, there is no point in having them compressed, - // except for an attacker who is trying to exploit flaws in the decompression algorithm. + while (i++ < maxIterations && + objectFactory.nextObject().also { nextObject = it } != null) { + // Since signatures are indistinguishable from randomness, there is no point in + // having them compressed, + // except for an attacker who is trying to exploit flaws in the decompression + // algorithm. // Therefore, we ignore compressed data packets without attempting decompression. if (nextObject is PGPCompressedData) { // getInputStream() does not do decompression, contrary to getDataStream(). - Streams.drain((nextObject as PGPCompressedData).inputStream) // Skip packet without decompressing + Streams.drain( + (nextObject as PGPCompressedData) + .inputStream) // Skip packet without decompressing } if (nextObject is PGPSignatureList) { @@ -166,17 +185,20 @@ class SignatureUtils { } /** - * Determine the issuer key-id of a [PGPSignature]. - * This method first inspects the [org.bouncycastle.bcpg.sig.IssuerKeyID] subpacket of the signature and returns the key-id if present. - * If not, it inspects the [org.bouncycastle.bcpg.sig.IssuerFingerprint] packet and retrieves the key-id from the fingerprint. + * Determine the issuer key-id of a [PGPSignature]. This method first inspects the + * [org.bouncycastle.bcpg.sig.IssuerKeyID] subpacket of the signature and returns the key-id + * if present. If not, it inspects the [org.bouncycastle.bcpg.sig.IssuerFingerprint] packet + * and retrieves the key-id from the fingerprint. * * Otherwise, it returns 0. + * * @param signature signature * @return signatures issuing key id */ @JvmStatic - @Deprecated("Deprecated in favor of PGPSignature extension method.", - ReplaceWith("signature.issuerKeyId", "org.bouncycastle.extensions.issuerKeyId")) + @Deprecated( + "Deprecated in favor of PGPSignature extension method.", + ReplaceWith("signature.issuerKeyId", "org.bouncycastle.extensions.issuerKeyId")) fun determineIssuerKeyId(signature: PGPSignature): Long { return signature.issuerKeyId } @@ -193,22 +215,26 @@ class SignatureUtils { } @JvmStatic - @Deprecated("Deprecated in favor of PGPSignature extension method", - ReplaceWith("signature.wasIssuedBy(fingerprint)", "org.bouncycastle.extensions.wasIssuedBy")) + @Deprecated( + "Deprecated in favor of PGPSignature extension method", + ReplaceWith( + "signature.wasIssuedBy(fingerprint)", "org.bouncycastle.extensions.wasIssuedBy")) fun wasIssuedBy(fingerprint: ByteArray, signature: PGPSignature): Boolean { return signature.wasIssuedBy(fingerprint) } @JvmStatic - @Deprecated("Deprecated in favor of PGPSignature extension method", - ReplaceWith("signature.wasIssuedBy(fingerprint)", "org.bouncycastle.extensions.wasIssuedBy")) + @Deprecated( + "Deprecated in favor of PGPSignature extension method", + ReplaceWith( + "signature.wasIssuedBy(fingerprint)", "org.bouncycastle.extensions.wasIssuedBy")) fun wasIssuedBy(fingerprint: OpenPgpFingerprint, signature: PGPSignature): Boolean { return signature.wasIssuedBy(fingerprint) } /** - * Extract all signatures from the given
key
which were issued by
issuerKeyId
- * over
userId
. + * Extract all signatures from the given
key
which were issued by + *
issuerKeyId
over
userId
. * * @param key public key * @param userId user-id @@ -216,28 +242,33 @@ class SignatureUtils { * @return (potentially empty) list of signatures */ @JvmStatic - fun getSignaturesOverUserIdBy(key: PGPPublicKey, userId: String, issuer: Long): List { + fun getSignaturesOverUserIdBy( + key: PGPPublicKey, + userId: String, + issuer: Long + ): List { val signatures = key.getSignaturesForID(userId) ?: return listOf() - return signatures - .asSequence() - .filter { it.keyID == issuer } - .toList() + return signatures.asSequence().filter { it.keyID == issuer }.toList() } @JvmStatic fun getDelegations(key: PGPPublicKeyRing): List { return key.publicKey.keySignatures - .asSequence() - .filter { key.getPublicKey(it.keyID) == null } // Filter out back-sigs from subkeys - .toList() + .asSequence() + .filter { key.getPublicKey(it.keyID) == null } // Filter out back-sigs from subkeys + .toList() } @JvmStatic - fun get3rdPartyCertificationsFor(key: PGPPublicKeyRing, userId: String): List { - return key.publicKey.getSignaturesForID(userId) - .asSequence() - .filter { it.keyID != key.publicKey.keyID } // Filter out self-sigs - .toList() + fun get3rdPartyCertificationsFor( + key: PGPPublicKeyRing, + userId: String + ): List { + return key.publicKey + .getSignaturesForID(userId) + .asSequence() + .filter { it.keyID != key.publicKey.keyID } // Filter out self-sigs + .toList() } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt index 126803d4..43c92959 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt @@ -4,6 +4,8 @@ package org.pgpainless.signature.consumer +import java.io.InputStream +import java.util.* import openpgp.openPgpKeyId import org.bouncycastle.extensions.issuerKeyId import org.bouncycastle.openpgp.PGPPublicKey @@ -17,18 +19,16 @@ import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.policy.Policy import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.slf4j.LoggerFactory -import java.io.InputStream -import java.util.* /** - * A collection of static methods that validate signing certificates (public keys) and verify signature correctness. + * A collection of static methods that validate signing certificates (public keys) and verify + * signature correctness. */ class CertificateValidator { companion object { - @JvmStatic - private val LOGGER = LoggerFactory.getLogger(CertificateValidator::class.java) + @JvmStatic private val LOGGER = LoggerFactory.getLogger(CertificateValidator::class.java) /** * Check if the signing key was eligible to create the provided signature. @@ -47,43 +47,56 @@ class CertificateValidator { */ @JvmStatic @Throws(SignatureValidationException::class) - fun validateCertificate(signature: PGPSignature, - signingKeyRing: PGPPublicKeyRing, - policy: Policy = PGPainless.getPolicy()): Boolean { - val signingSubkey: PGPPublicKey = signingKeyRing.getPublicKey(signature.issuerKeyId) - ?: throw SignatureValidationException("Provided key ring does not contain a subkey with id ${signature.issuerKeyId.openPgpKeyId()}.") + fun validateCertificate( + signature: PGPSignature, + signingKeyRing: PGPPublicKeyRing, + policy: Policy = PGPainless.getPolicy() + ): Boolean { + val signingSubkey: PGPPublicKey = + signingKeyRing.getPublicKey(signature.issuerKeyId) + ?: throw SignatureValidationException( + "Provided key ring does not contain a subkey with id ${signature.issuerKeyId.openPgpKeyId()}.") val primaryKey = signingKeyRing.publicKey!! val directKeyAndRevSigs = mutableListOf() val rejections = mutableMapOf() // revocations - primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.code).asSequence() - .filter { it.issuerKeyId == primaryKey.keyID } // We do not support external rev keys - .forEach { - try { - if (SignatureVerifier.verifyKeyRevocationSignature(it, primaryKey, policy, signature.creationTime)) { - directKeyAndRevSigs.add(it) - } - } catch (e: SignatureValidationException) { - rejections[it] = e - LOGGER.debug("Rejecting key revocation signature: ${e.message}", e) + primaryKey + .getSignaturesOfType(SignatureType.KEY_REVOCATION.code) + .asSequence() + .filter { + it.issuerKeyId == primaryKey.keyID + } // We do not support external rev keys + .forEach { + try { + if (SignatureVerifier.verifyKeyRevocationSignature( + it, primaryKey, policy, signature.creationTime)) { + directKeyAndRevSigs.add(it) } + } catch (e: SignatureValidationException) { + rejections[it] = e + LOGGER.debug("Rejecting key revocation signature: ${e.message}", e) } + } // direct-key sigs - primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.code).asSequence() - .filter { it.issuerKeyId == primaryKey.keyID } - .forEach { - try { - if (SignatureVerifier.verifyDirectKeySignature(it, primaryKey, policy, signature.creationTime)) { - directKeyAndRevSigs.add(it) - } - } catch (e: SignatureValidationException) { - rejections[it] = e - LOGGER.debug("Rejecting key signature: ${e.message}, e") + primaryKey + .getSignaturesOfType(SignatureType.DIRECT_KEY.code) + .asSequence() + .filter { it.issuerKeyId == primaryKey.keyID } + .forEach { + try { + if (SignatureVerifier.verifyDirectKeySignature( + it, primaryKey, policy, signature.creationTime)) { + directKeyAndRevSigs.add(it) } + } catch (e: SignatureValidationException) { + rejections[it] = e + LOGGER.debug("Rejecting key signature: ${e.message}, e") } + } - directKeyAndRevSigs.sortWith(SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)) + directKeyAndRevSigs.sortWith( + SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)) if (directKeyAndRevSigs.isNotEmpty()) { if (directKeyAndRevSigs[0].signatureType == SignatureType.KEY_REVOCATION.code) { throw SignatureValidationException("Primary key has been revoked.") @@ -94,12 +107,18 @@ class CertificateValidator { val userIdSignatures = mutableMapOf>() KeyRingUtils.getUserIdsIgnoringInvalidUTF8(primaryKey).forEach { userId -> buildList { - primaryKey.getSignaturesForID(userId) + primaryKey + .getSignaturesForID(userId) .asSequence() .filter { it.issuerKeyId == primaryKey.keyID } .forEach { uidSig -> try { - if (SignatureVerifier.verifySignatureOverUserId(userId, uidSig, primaryKey, policy, signature.creationTime)) { + if (SignatureVerifier.verifySignatureOverUserId( + userId, + uidSig, + primaryKey, + policy, + signature.creationTime)) { add(uidSig) } } catch (e: SignatureValidationException) { @@ -107,14 +126,19 @@ class CertificateValidator { LOGGER.debug("Rejecting user-id signature: ${e.message}", e) } } - }.sortedWith(SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)) - .let { userIdSignatures[userId] = it } + } + .sortedWith( + SignatureValidityComparator( + SignatureCreationDateComparator.Order.NEW_TO_OLD)) + .let { userIdSignatures[userId] = it } } val hasAnyUserIds = userIdSignatures.isNotEmpty() - val isAnyUserIdValid = userIdSignatures.any { entry -> - entry.value.isNotEmpty() && entry.value[0].signatureType != SignatureType.CERTIFICATION_REVOCATION.code - } + val isAnyUserIdValid = + userIdSignatures.any { entry -> + entry.value.isNotEmpty() && + entry.value[0].signatureType != SignatureType.CERTIFICATION_REVOCATION.code + } if (hasAnyUserIds && !isAnyUserIdValid) { throw SignatureValidationException("No valid user-id found.", rejections) @@ -124,12 +148,15 @@ class CertificateValidator { if (policy.signerUserIdValidationLevel == Policy.SignerUserIdValidationLevel.STRICT) { SignatureSubpacketsUtil.getSignerUserID(signature)?.let { if (userIdSignatures[it.id] == null || userIdSignatures[it.id]!!.isEmpty()) { - throw SignatureValidationException("Signature was allegedly made by user-id '${it.id}'," + + throw SignatureValidationException( + "Signature was allegedly made by user-id '${it.id}'," + " but we have no valid signatures for that on the certificate.") } - if (userIdSignatures[it.id]!![0].signatureType == SignatureType.CERTIFICATION_REVOCATION.code) { - throw SignatureValidationException("Signature was made with user-id '${it.id}' which is revoked.") + if (userIdSignatures[it.id]!![0].signatureType == + SignatureType.CERTIFICATION_REVOCATION.code) { + throw SignatureValidationException( + "Signature was made with user-id '${it.id}' which is revoked.") } } } @@ -144,32 +171,39 @@ class CertificateValidator { } } else { // signing key is subkey val subkeySigs = mutableListOf() - signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.code).asSequence() - .filter { it.issuerKeyId == primaryKey.keyID } - .forEach { - try { - if (SignatureVerifier.verifySubkeyBindingRevocation(it, primaryKey, signingSubkey, policy, signature.creationTime)) { - subkeySigs.add(it) - } - } catch (e : SignatureValidationException) { - rejections[it] = e - LOGGER.debug("REjecting subkey revocation signature: ${e.message}", e) + signingSubkey + .getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.code) + .asSequence() + .filter { it.issuerKeyId == primaryKey.keyID } + .forEach { + try { + if (SignatureVerifier.verifySubkeyBindingRevocation( + it, primaryKey, signingSubkey, policy, signature.creationTime)) { + subkeySigs.add(it) } + } catch (e: SignatureValidationException) { + rejections[it] = e + LOGGER.debug("REjecting subkey revocation signature: ${e.message}", e) } + } - signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.code).asSequence() - .forEach { - try { - if (SignatureVerifier.verifySubkeyBindingSignature(it, primaryKey, signingSubkey, policy, signature.creationTime)) { - subkeySigs.add(it) - } - } catch (e : SignatureValidationException) { - rejections[it] = e - LOGGER.debug("Rejecting subkey binding signature: ${e.message}", e) + signingSubkey + .getSignaturesOfType(SignatureType.SUBKEY_BINDING.code) + .asSequence() + .forEach { + try { + if (SignatureVerifier.verifySubkeyBindingSignature( + it, primaryKey, signingSubkey, policy, signature.creationTime)) { + subkeySigs.add(it) } + } catch (e: SignatureValidationException) { + rejections[it] = e + LOGGER.debug("Rejecting subkey binding signature: ${e.message}", e) } + } - subkeySigs.sortWith(SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)) + subkeySigs.sortWith( + SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)) if (subkeySigs.isEmpty()) { throw SignatureValidationException("Subkey is not bound.", rejections) } @@ -180,15 +214,17 @@ class CertificateValidator { val keyFlags = SignatureSubpacketsUtil.getKeyFlags(subkeySigs[0]) if (keyFlags == null || !KeyFlag.hasKeyFlag(keyFlags.flags, KeyFlag.SIGN_DATA)) { - throw SignatureValidationException("Signature was made by key which is not capable of signing (no keyflag).") + throw SignatureValidationException( + "Signature was made by key which is not capable of signing (no keyflag).") } } return true } /** - * Validate the given signing key and then verify the given signature while parsing out the signed data. - * Uninitialized means that no signed data has been read and the hash generators state has not yet been updated. + * Validate the given signing key and then verify the given signature while parsing out the + * signed data. Uninitialized means that no signed data has been read and the hash + * generators state has not yet been updated. * * @param signature uninitialized signature * @param signedData input stream containing signed data @@ -200,18 +236,25 @@ class CertificateValidator { */ @JvmStatic @Throws(SignatureValidationException::class) - fun validateCertificateAndVerifyUninitializedSignature(signature: PGPSignature, - signedData: InputStream, - signingKeyRing: PGPPublicKeyRing, - policy: Policy, - referenceTime: Date = signature.creationTime): Boolean { - return validateCertificate(signature, signingKeyRing, policy) - && SignatureVerifier.verifyUninitializedSignature(signature, signedData, signingKeyRing.getPublicKey(signature.issuerKeyId)!!, policy, referenceTime) + fun validateCertificateAndVerifyUninitializedSignature( + signature: PGPSignature, + signedData: InputStream, + signingKeyRing: PGPPublicKeyRing, + policy: Policy, + referenceTime: Date = signature.creationTime + ): Boolean { + return validateCertificate(signature, signingKeyRing, policy) && + SignatureVerifier.verifyUninitializedSignature( + signature, + signedData, + signingKeyRing.getPublicKey(signature.issuerKeyId)!!, + policy, + referenceTime) } /** - * Validate the signing key and the given initialized signature. - * Initialized means that the signatures hash generator has already been updated by reading the signed data completely. + * Validate the signing key and the given initialized signature. Initialized means that the + * signatures hash generator has already been updated by reading the signed data completely. * * @param signature initialized signature * @param verificationKeys key ring containing the verification key @@ -221,11 +264,17 @@ class CertificateValidator { */ @JvmStatic @Throws(SignatureValidationException::class) - fun validateCertificateAndVerifyInitializedSignature(signature: PGPSignature, - verificationKeys: PGPPublicKeyRing, - policy: Policy): Boolean { + fun validateCertificateAndVerifyInitializedSignature( + signature: PGPSignature, + verificationKeys: PGPPublicKeyRing, + policy: Policy + ): Boolean { return validateCertificate(signature, verificationKeys, policy) && - SignatureVerifier.verifyInitializedSignature(signature, verificationKeys.getPublicKey(signature.issuerKeyId), policy, signature.creationTime) + SignatureVerifier.verifyInitializedSignature( + signature, + verificationKeys.getPublicKey(signature.issuerKeyId), + policy, + signature.creationTime) } /** @@ -238,12 +287,18 @@ class CertificateValidator { */ @JvmStatic @Throws(SignatureValidationException::class) - fun validateCertificateAndVerifyOnePassSignature(onePassSignature: OnePassSignatureCheck, - policy: Policy): Boolean { - return validateCertificate(onePassSignature.signature!!, onePassSignature.verificationKeys, policy) && - SignatureVerifier.verifyOnePassSignature(onePassSignature.signature!!, - onePassSignature.verificationKeys.getPublicKey(onePassSignature.signature!!.issuerKeyId), - onePassSignature, policy) + fun validateCertificateAndVerifyOnePassSignature( + onePassSignature: OnePassSignatureCheck, + policy: Policy + ): Boolean { + return validateCertificate( + onePassSignature.signature!!, onePassSignature.verificationKeys, policy) && + SignatureVerifier.verifyOnePassSignature( + onePassSignature.signature!!, + onePassSignature.verificationKeys.getPublicKey( + onePassSignature.signature!!.issuerKeyId), + onePassSignature, + policy) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt index a315f577..4a89e0b2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt @@ -10,18 +10,19 @@ import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.key.SubkeyIdentifier /** - * Tuple-class that bundles together a [PGPOnePassSignature] object, a [PGPPublicKeyRing] - * destined to verify the signature, the [PGPSignature] itself and a record of whether the signature - * was verified. + * Tuple-class that bundles together a [PGPOnePassSignature] object, a [PGPPublicKeyRing] destined + * to verify the signature, the [PGPSignature] itself and a record of whether the signature was + * verified. * * @param onePassSignature the one-pass-signature packet * @param verificationKeys certificate containing the signing subkey * @param signature the signature packet */ data class OnePassSignatureCheck( - val onePassSignature: PGPOnePassSignature, - val verificationKeys: PGPPublicKeyRing, - var signature: PGPSignature? = null) { + val onePassSignature: PGPOnePassSignature, + val verificationKeys: PGPPublicKeyRing, + var signature: PGPSignature? = null +) { /** * Return an identifier for the signing key. @@ -30,4 +31,4 @@ data class OnePassSignatureCheck( */ val signingKey: SubkeyIdentifier get() = SubkeyIdentifier(verificationKeys, onePassSignature.keyID) -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCheck.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCheck.kt index 48a3aa96..15564773 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCheck.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCheck.kt @@ -9,15 +9,17 @@ import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.key.SubkeyIdentifier /** - * Tuple-class which bundles together a signature, the signing key that created the signature, - * an identifier of the signing key and a record of whether the signature was verified. + * Tuple-class which bundles together a signature, the signing key that created the signature, an + * identifier of the signing key and a record of whether the signature was verified. * * @param signature OpenPGP signature - * @param signingKeyIdentifier identifier pointing to the exact signing key which was used to create the signature - * @param signingKeyRing certificate or key ring that contains the signing key that created the signature + * @param signingKeyIdentifier identifier pointing to the exact signing key which was used to create + * the signature + * @param signingKeyRing certificate or key ring that contains the signing key that created the + * signature */ data class SignatureCheck( - val signature: PGPSignature, - val signingKeyRing: PGPKeyRing, - val signingKeyIdentifier: SubkeyIdentifier) { -} \ No newline at end of file + val signature: PGPSignature, + val signingKeyRing: PGPKeyRing, + val signingKeyIdentifier: SubkeyIdentifier +) {} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCreationDateComparator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCreationDateComparator.kt index 75cd274c..a913bf32 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCreationDateComparator.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureCreationDateComparator.kt @@ -8,27 +8,23 @@ import org.bouncycastle.openpgp.PGPSignature /** * Create a new comparator which sorts signatures according to the passed ordering. + * * @param order ordering */ -class SignatureCreationDateComparator( - private val order: Order = Order.OLD_TO_NEW -) : Comparator { +class SignatureCreationDateComparator(private val order: Order = Order.OLD_TO_NEW) : + Comparator { enum class Order { - /** - * Oldest signatures first. - */ + /** Oldest signatures first. */ OLD_TO_NEW, - /** - * Newest signatures first. - */ + /** Newest signatures first. */ NEW_TO_OLD } override fun compare(one: PGPSignature, two: PGPSignature): Int { - return when(order) { + return when (order) { Order.OLD_TO_NEW -> one.creationTime.compareTo(two.creationTime) Order.NEW_TO_OLD -> two.creationTime.compareTo(one.creationTime) } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt index 9c5b9a8d..56db7ee0 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt @@ -4,8 +4,8 @@ package org.pgpainless.signature.consumer +import java.util.Date import org.bouncycastle.extensions.getPublicKeyFor -import org.bouncycastle.extensions.hasPublicKey import org.bouncycastle.extensions.isExpired import org.bouncycastle.extensions.wasIssuedBy import org.bouncycastle.openpgp.PGPKeyRing @@ -14,30 +14,24 @@ import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.algorithm.SignatureType import org.pgpainless.exception.SignatureValidationException import org.pgpainless.policy.Policy -import java.util.Date -import kotlin.math.sign /** * Pick signatures from keys. * * The format of a V4 OpenPGP key is: * - * Primary-Key - * [Revocation Self Signature] - * [Direct Key Signature...] - * User ID [Signature ...] - * [User ID [Signature ...] ...] - * [User Attribute [Signature ...] ...] - * [[Subkey [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...] + * Primary-Key [Revocation Self Signature] [Direct Key Signature...] User ID [Signature ...] [User + * ID [Signature ...] ...] [User Attribute [Signature ...] ...] [[Subkey + * [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...] */ class SignaturePicker { companion object { /** - * Pick the at validation date most recent valid key revocation signature. - * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after - * validationDate or if it is already expired. + * Pick the at validation date most recent valid key revocation signature. If there are hard + * revocation signatures, the latest hard revocation sig is picked, even if it was created + * after validationDate or if it is already expired. * * @param keyRing key ring * @param policy policy @@ -45,21 +39,26 @@ class SignaturePicker { * @return most recent, valid key revocation signature */ @JvmStatic - fun pickCurrentRevocationSelfSignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickCurrentRevocationSelfSignature( + keyRing: PGPKeyRing, + policy: Policy, + referenceTime: Date + ): PGPSignature? { val primaryKey = keyRing.publicKey return getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION).lastOrNull { return@lastOrNull try { - SignatureVerifier.verifyKeyRevocationSignature(it, primaryKey, policy, referenceTime) + SignatureVerifier.verifyKeyRevocationSignature( + it, primaryKey, policy, referenceTime) true // valid - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { false // not valid } } } /** - * Pick the at validationDate most recent, valid direct key signature. - * This method might return null, if there is no direct key self-signature which is valid at validationDate. + * Pick the at validationDate most recent, valid direct key signature. This method might + * return null, if there is no direct key self-signature which is valid at validationDate. * * @param keyRing key ring * @param policy policy @@ -67,28 +66,38 @@ class SignaturePicker { * @return direct-key self-signature */ @JvmStatic - fun pickCurrentDirectKeySelfSignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickCurrentDirectKeySelfSignature( + keyRing: PGPKeyRing, + policy: Policy, + referenceTime: Date + ): PGPSignature? { val primaryKey = keyRing.publicKey return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, referenceTime) } @JvmStatic - fun pickCurrentDirectKeySignature(signingKey: PGPPublicKey, signedKey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickCurrentDirectKeySignature( + signingKey: PGPPublicKey, + signedKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): PGPSignature? { return getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY).lastOrNull { return@lastOrNull try { - SignatureVerifier.verifyDirectKeySignature(it, signingKey, signedKey, policy, referenceTime) + SignatureVerifier.verifyDirectKeySignature( + it, signingKey, signedKey, policy, referenceTime) true - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { false } } } /** - * Pick the at validationDate latest direct key signature. - * This method might return an expired signature. - * If there are more than one direct-key signature, and some of those are not expired, the latest non-expired - * yet already effective direct-key signature will be returned. + * Pick the at validationDate latest direct key signature. This method might return an + * expired signature. If there are more than one direct-key signature, and some of those are + * not expired, the latest non-expired yet already effective direct-key signature will be + * returned. * * @param keyRing key ring * @param policy policy @@ -96,15 +105,20 @@ class SignaturePicker { * @return latest direct key signature */ @JvmStatic - fun pickLatestDirectKeySignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { - return pickLatestDirectKeySignature(keyRing.publicKey, keyRing.publicKey, policy, referenceTime) + fun pickLatestDirectKeySignature( + keyRing: PGPKeyRing, + policy: Policy, + referenceTime: Date + ): PGPSignature? { + return pickLatestDirectKeySignature( + keyRing.publicKey, keyRing.publicKey, policy, referenceTime) } /** * Pick the at validationDate latest direct key signature made by signingKey on signedKey. - * This method might return an expired signature. - * If a non-expired direct-key signature exists, the latest non-expired yet already effective direct-key - * signature will be returned. + * This method might return an expired signature. If a non-expired direct-key signature + * exists, the latest non-expired yet already effective direct-key signature will be + * returned. * * @param signingKey signing key (key that made the sig) * @param signedKey signed key (key that carries the sig) @@ -113,7 +127,12 @@ class SignaturePicker { * @return latest direct key sig */ @JvmStatic - fun pickLatestDirectKeySignature(signingKey: PGPPublicKey, signedKey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickLatestDirectKeySignature( + signingKey: PGPPublicKey, + signedKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): PGPSignature? { var latest: PGPSignature? = null return getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY).lastOrNull { try { @@ -126,16 +145,16 @@ class SignaturePicker { SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(it) latest = it true - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { false } } } /** - * Pick the at validationDate most recent, valid user-id revocation signature. - * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after - * validationDate or if it is already expired. + * Pick the at validationDate most recent, valid user-id revocation signature. If there are + * hard revocation signatures, the latest hard revocation sig is picked, even if it was + * created after validationDate or if it is already expired. * * @param keyRing key ring * @param userId user-Id that gets revoked @@ -144,23 +163,31 @@ class SignaturePicker { * @return revocation signature */ @JvmStatic - fun pickCurrentUserIdRevocationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickCurrentUserIdRevocationSignature( + keyRing: PGPKeyRing, + userId: CharSequence, + policy: Policy, + referenceTime: Date + ): PGPSignature? { val primaryKey = keyRing.publicKey - return getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION).lastOrNull { - keyRing.getPublicKeyFor(it) ?: return@lastOrNull false // signature made by external key. skip. - return@lastOrNull try { - SignatureVerifier.verifyUserIdRevocation(userId.toString(), it, primaryKey, policy, referenceTime) - true - } catch (e : SignatureValidationException) { - false // signature not valid + return getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION) + .lastOrNull { + keyRing.getPublicKeyFor(it) + ?: return@lastOrNull false // signature made by external key. skip. + return@lastOrNull try { + SignatureVerifier.verifyUserIdRevocation( + userId.toString(), it, primaryKey, policy, referenceTime) + true + } catch (e: SignatureValidationException) { + false // signature not valid + } } - } } /** - * Pick the at validationDate latest, valid certification self-signature for the given user-id. - * This method might return null, if there is no certification self signature for that user-id which is valid - * at validationDate. + * Pick the at validationDate latest, valid certification self-signature for the given + * user-id. This method might return null, if there is no certification self signature for + * that user-id which is valid at validationDate. * * @param keyRing keyring * @param userId userid @@ -169,25 +196,34 @@ class SignaturePicker { * @return user-id certification */ @JvmStatic - fun pickCurrentUserIdCertificationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickCurrentUserIdCertificationSignature( + keyRing: PGPKeyRing, + userId: CharSequence, + policy: Policy, + referenceTime: Date + ): PGPSignature? { val primaryKey = keyRing.publicKey - return primaryKey.getSignaturesForID(userId.toString()).asSequence() - .sortedWith(SignatureCreationDateComparator()) - .lastOrNull { - return@lastOrNull it.wasIssuedBy(primaryKey) && try { - SignatureVerifier.verifyUserIdCertification(userId.toString(), it, primaryKey, policy, referenceTime) + return primaryKey + .getSignaturesForID(userId.toString()) + .asSequence() + .sortedWith(SignatureCreationDateComparator()) + .lastOrNull { + return@lastOrNull it.wasIssuedBy(primaryKey) && + try { + SignatureVerifier.verifyUserIdCertification( + userId.toString(), it, primaryKey, policy, referenceTime) true - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { false } - } + } } /** * Pick the at validationDate latest certification self-signature for the given user-id. - * This method might return an expired signature. - * If a non-expired user-id certification signature exists, the latest non-expired yet already effective - * user-id certification signature for the given user-id will be returned. + * This method might return an expired signature. If a non-expired user-id certification + * signature exists, the latest non-expired yet already effective user-id certification + * signature for the given user-id will be returned. * * @param keyRing keyring * @param userId userid @@ -196,28 +232,38 @@ class SignaturePicker { * @return user-id certification */ @JvmStatic - fun pickLatestUserIdCertificationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickLatestUserIdCertificationSignature( + keyRing: PGPKeyRing, + userId: CharSequence, + policy: Policy, + referenceTime: Date + ): PGPSignature? { val primaryKey = keyRing.publicKey - return primaryKey.getSignaturesForID(userId.toString()).asSequence() - .sortedWith(SignatureCreationDateComparator()) - .lastOrNull { - return@lastOrNull try { - SignatureValidator.wasPossiblyMadeByKey(primaryKey).verify(it) - SignatureValidator.signatureIsCertification().verify(it) - SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(it) - SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) - SignatureValidator.correctSignatureOverUserId(userId.toString(), primaryKey, primaryKey).verify(it) - true - } catch (e : SignatureValidationException) { - false - } + return primaryKey + .getSignaturesForID(userId.toString()) + .asSequence() + .sortedWith(SignatureCreationDateComparator()) + .lastOrNull { + return@lastOrNull try { + SignatureValidator.wasPossiblyMadeByKey(primaryKey).verify(it) + SignatureValidator.signatureIsCertification().verify(it) + SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy) + .verify(it) + SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) + SignatureValidator.correctSignatureOverUserId( + userId.toString(), primaryKey, primaryKey) + .verify(it) + true + } catch (e: SignatureValidationException) { + false } + } } /** - * Pick the at validationDate most recent, valid subkey revocation signature. - * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after - * validationDate or if it is already expired. + * Pick the at validationDate most recent, valid subkey revocation signature. If there are + * hard revocation signatures, the latest hard revocation sig is picked, even if it was + * created after validationDate or if it is already expired. * * @param keyRing keyring * @param subkey subkey @@ -226,14 +272,22 @@ class SignaturePicker { * @return subkey revocation signature */ @JvmStatic - fun pickCurrentSubkeyBindingRevocationSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickCurrentSubkeyBindingRevocationSignature( + keyRing: PGPKeyRing, + subkey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): PGPSignature? { val primaryKey = keyRing.publicKey - require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding revocations." } + require(primaryKey.keyID != subkey.keyID) { + "Primary key cannot have subkey binding revocations." + } return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_REVOCATION).lastOrNull { return@lastOrNull try { - SignatureVerifier.verifySubkeyBindingRevocation(it, primaryKey, subkey, policy, referenceTime) + SignatureVerifier.verifySubkeyBindingRevocation( + it, primaryKey, subkey, policy, referenceTime) true - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { false } } @@ -241,8 +295,8 @@ class SignaturePicker { /** * Pick the at validationDate latest, valid subkey binding signature for the given subkey. - * This method might return null, if there is no subkey binding signature which is valid - * at validationDate. + * This method might return null, if there is no subkey binding signature which is valid at + * validationDate. * * @param keyRing key ring * @param subkey subkey @@ -251,24 +305,32 @@ class SignaturePicker { * @return most recent valid subkey binding signature */ @JvmStatic - fun pickCurrentSubkeyBindingSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickCurrentSubkeyBindingSignature( + keyRing: PGPKeyRing, + subkey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): PGPSignature? { val primaryKey = keyRing.publicKey - require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding signatures." } + require(primaryKey.keyID != subkey.keyID) { + "Primary key cannot have subkey binding signatures." + } return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING).lastOrNull { return@lastOrNull try { - SignatureVerifier.verifySubkeyBindingSignature(it, primaryKey, subkey, policy, referenceTime) + SignatureVerifier.verifySubkeyBindingSignature( + it, primaryKey, subkey, policy, referenceTime) true - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { false } } } /** - * Pick the at validationDate latest subkey binding signature for the given subkey. - * This method might return an expired signature. - * If a non-expired subkey binding signature exists, the latest non-expired yet already effective - * subkey binding signature for the given subkey will be returned. + * Pick the at validationDate latest subkey binding signature for the given subkey. This + * method might return an expired signature. If a non-expired subkey binding signature + * exists, the latest non-expired yet already effective subkey binding signature for the + * given subkey will be returned. * * @param keyRing key ring * @param subkey subkey @@ -277,9 +339,16 @@ class SignaturePicker { * @return subkey binding signature */ @JvmStatic - fun pickLatestSubkeyBindingSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + fun pickLatestSubkeyBindingSignature( + keyRing: PGPKeyRing, + subkey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): PGPSignature? { val primaryKey = keyRing.publicKey - require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding signatures." } + require(primaryKey.keyID != subkey.keyID) { + "Primary key cannot have subkey binding signatures." + } var latest: PGPSignature? = null return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING).lastOrNull { return@lastOrNull try { @@ -287,24 +356,28 @@ class SignaturePicker { SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(it) SignatureValidator.signatureDoesNotPredateSignee(subkey).verify(it) SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) - // if the currently latest signature is not yet expired, check if the next candidate is not yet expired + // if the currently latest signature is not yet expired, check if the next + // candidate is not yet expired if (latest != null && !latest!!.isExpired(referenceTime)) { SignatureValidator.signatureIsNotYetExpired(referenceTime).verify(it) } SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(it) latest = it true - } catch (e : SignatureValidationException) { + } catch (e: SignatureValidationException) { false } } } @JvmStatic - private fun getSortedSignaturesOfType(key: PGPPublicKey, type: SignatureType): List = - key.getSignaturesOfType(type.code).asSequence() - .sortedWith(SignatureCreationDateComparator()) - .toList() + private fun getSortedSignaturesOfType( + key: PGPPublicKey, + type: SignatureType + ): List = + key.getSignaturesOfType(type.code) + .asSequence() + .sortedWith(SignatureCreationDateComparator()) + .toList() } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidityComparator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidityComparator.kt index f2b586ae..38c409b7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidityComparator.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidityComparator.kt @@ -10,15 +10,17 @@ import org.bouncycastle.openpgp.PGPSignature /** * Comparator which sorts signatures based on an ordering and on revocation hardness. * - * If a list of signatures gets ordered using this comparator, hard revocations will always - * come first. - * Further, signatures are ordered by date according to the [SignatureCreationDateComparator.Order]. + * If a list of signatures gets ordered using this comparator, hard revocations will always come + * first. Further, signatures are ordered by date according to the + * [SignatureCreationDateComparator.Order]. */ class SignatureValidityComparator( - order: SignatureCreationDateComparator.Order = SignatureCreationDateComparator.Order.OLD_TO_NEW + order: SignatureCreationDateComparator.Order = SignatureCreationDateComparator.Order.OLD_TO_NEW ) : Comparator { - private val creationDateComparator: SignatureCreationDateComparator = SignatureCreationDateComparator(order) + private val creationDateComparator: SignatureCreationDateComparator = + SignatureCreationDateComparator(order) + override fun compare(one: PGPSignature, two: PGPSignature): Int { return if (one.isHardRevocation == two.isHardRevocation) { // Both have the same hardness, so compare creation time @@ -27,5 +29,4 @@ class SignatureValidityComparator( // else favor the "harder" signature else if (one.isHardRevocation) -1 else 1 } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt index 66618b23..3586eecf 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt @@ -4,14 +4,14 @@ package org.pgpainless.signature.subpackets +import java.io.IOException +import java.net.URL +import java.util.* import org.bouncycastle.bcpg.sig.* import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm -import java.io.IOException -import java.net.URL -import java.util.* interface BaseSignatureSubpackets { @@ -22,9 +22,8 @@ interface BaseSignatureSubpackets { * * @param key key * @return this - * - * @deprecated this method MUST NOT be used for OpenPGP v6, since v6 signatures MUST NOT contain any - * [IssuerKeyID] packets. + * @deprecated this method MUST NOT be used for OpenPGP v6, since v6 signatures MUST NOT contain + * any [IssuerKeyID] packets. */ fun setIssuerFingerprintAndKeyId(key: PGPPublicKey): BaseSignatureSubpackets @@ -46,13 +45,22 @@ interface BaseSignatureSubpackets { fun setSignatureCreationTime(creationTime: SignatureCreationTime?): BaseSignatureSubpackets - fun setSignatureExpirationTime(creationTime: Date, expirationTime: Date?): BaseSignatureSubpackets + fun setSignatureExpirationTime( + creationTime: Date, + expirationTime: Date? + ): BaseSignatureSubpackets - fun setSignatureExpirationTime(isCritical: Boolean, creationTime: Date, expirationTime: Date?): BaseSignatureSubpackets + fun setSignatureExpirationTime( + isCritical: Boolean, + creationTime: Date, + expirationTime: Date? + ): BaseSignatureSubpackets fun setSignatureExpirationTime(isCritical: Boolean, seconds: Long): BaseSignatureSubpackets - fun setSignatureExpirationTime(expirationTime: SignatureExpirationTime?): BaseSignatureSubpackets + fun setSignatureExpirationTime( + expirationTime: SignatureExpirationTime? + ): BaseSignatureSubpackets fun setSignerUserId(userId: CharSequence): BaseSignatureSubpackets @@ -60,9 +68,18 @@ interface BaseSignatureSubpackets { fun setSignerUserId(signerUserID: SignerUserID?): BaseSignatureSubpackets - fun addNotationData(isCritical: Boolean, notationName: String, notationValue: String): BaseSignatureSubpackets + fun addNotationData( + isCritical: Boolean, + notationName: String, + notationValue: String + ): BaseSignatureSubpackets - fun addNotationData(isCritical: Boolean, isHumanReadable: Boolean, notationName: String, notationValue: String): BaseSignatureSubpackets + fun addNotationData( + isCritical: Boolean, + isHumanReadable: Boolean, + notationName: String, + notationValue: String + ): BaseSignatureSubpackets fun addNotationData(notationData: NotationData): BaseSignatureSubpackets @@ -70,9 +87,14 @@ interface BaseSignatureSubpackets { fun addIntendedRecipientFingerprint(recipientKey: PGPPublicKey): BaseSignatureSubpackets - fun addIntendedRecipientFingerprint(isCritical: Boolean, recipientKey: PGPPublicKey): BaseSignatureSubpackets + fun addIntendedRecipientFingerprint( + isCritical: Boolean, + recipientKey: PGPPublicKey + ): BaseSignatureSubpackets - fun addIntendedRecipientFingerprint(intendedRecipient: IntendedRecipientFingerprint): BaseSignatureSubpackets + fun addIntendedRecipientFingerprint( + intendedRecipient: IntendedRecipientFingerprint + ): BaseSignatureSubpackets fun clearIntendedRecipientFingerprints(): BaseSignatureSubpackets @@ -104,9 +126,18 @@ interface BaseSignatureSubpackets { fun setRevocable(revocable: Revocable?): BaseSignatureSubpackets - fun setSignatureTarget(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm, hashData: ByteArray): BaseSignatureSubpackets + fun setSignatureTarget( + keyAlgorithm: PublicKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + hashData: ByteArray + ): BaseSignatureSubpackets - fun setSignatureTarget(isCritical: Boolean, keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm, hashData: ByteArray): BaseSignatureSubpackets + fun setSignatureTarget( + isCritical: Boolean, + keyAlgorithm: PublicKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + hashData: ByteArray + ): BaseSignatureSubpackets fun setSignatureTarget(signatureTarget: SignatureTarget?): BaseSignatureSubpackets @@ -125,4 +156,4 @@ interface BaseSignatureSubpackets { fun addEmbeddedSignature(embeddedSignature: EmbeddedSignature): BaseSignatureSubpackets fun clearEmbeddedSignatures(): BaseSignatureSubpackets -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/CertificationSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/CertificationSubpackets.kt index 423edd8e..c3edf2c4 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/CertificationSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/CertificationSubpackets.kt @@ -7,5 +7,4 @@ package org.pgpainless.signature.subpackets interface CertificationSubpackets : BaseSignatureSubpackets { interface Callback : SignatureSubpacketCallback - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.kt index 327ed4a9..2e152f9e 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/RevocationSignatureSubpackets.kt @@ -11,11 +11,20 @@ interface RevocationSignatureSubpackets : BaseSignatureSubpackets { interface Callback : SignatureSubpacketCallback - fun setRevocationReason(revocationAttributes: RevocationAttributes): RevocationSignatureSubpackets + fun setRevocationReason( + revocationAttributes: RevocationAttributes + ): RevocationSignatureSubpackets - fun setRevocationReason(isCritical: Boolean, revocationAttributes: RevocationAttributes): RevocationSignatureSubpackets + fun setRevocationReason( + isCritical: Boolean, + revocationAttributes: RevocationAttributes + ): RevocationSignatureSubpackets - fun setRevocationReason(isCritical: Boolean, reason: RevocationAttributes.Reason, description: CharSequence): RevocationSignatureSubpackets + fun setRevocationReason( + isCritical: Boolean, + reason: RevocationAttributes.Reason, + description: CharSequence + ): RevocationSignatureSubpackets fun setRevocationReason(reason: RevocationReason?): RevocationSignatureSubpackets -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt index ce8e09a9..d1b2b428 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt @@ -4,6 +4,7 @@ package org.pgpainless.signature.subpackets +import java.util.* import org.bouncycastle.bcpg.sig.Features import org.bouncycastle.bcpg.sig.KeyExpirationTime import org.bouncycastle.bcpg.sig.KeyFlags @@ -12,7 +13,6 @@ import org.bouncycastle.bcpg.sig.PrimaryUserID import org.bouncycastle.bcpg.sig.RevocationKey import org.bouncycastle.openpgp.PGPPublicKey import org.pgpainless.algorithm.* -import java.util.* interface SelfSignatureSubpackets : BaseSignatureSubpackets { @@ -34,35 +34,66 @@ interface SelfSignatureSubpackets : BaseSignatureSubpackets { fun setKeyExpirationTime(key: PGPPublicKey, keyExpirationTime: Date?): SelfSignatureSubpackets - fun setKeyExpirationTime(keyCreationTime: Date, keyExpirationTime: Date?): SelfSignatureSubpackets + fun setKeyExpirationTime( + keyCreationTime: Date, + keyExpirationTime: Date? + ): SelfSignatureSubpackets - fun setKeyExpirationTime(isCritical: Boolean, keyCreationTime: Date, keyExpirationTime: Date?): SelfSignatureSubpackets + fun setKeyExpirationTime( + isCritical: Boolean, + keyCreationTime: Date, + keyExpirationTime: Date? + ): SelfSignatureSubpackets - fun setKeyExpirationTime(isCritical: Boolean, secondsFromCreationToExpiration: Long): SelfSignatureSubpackets + fun setKeyExpirationTime( + isCritical: Boolean, + secondsFromCreationToExpiration: Long + ): SelfSignatureSubpackets fun setKeyExpirationTime(keyExpirationTime: KeyExpirationTime?): SelfSignatureSubpackets - fun setPreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): SelfSignatureSubpackets + fun setPreferredCompressionAlgorithms( + vararg algorithms: CompressionAlgorithm + ): SelfSignatureSubpackets - fun setPreferredCompressionAlgorithms(algorithms: Collection): SelfSignatureSubpackets + fun setPreferredCompressionAlgorithms( + algorithms: Collection + ): SelfSignatureSubpackets - fun setPreferredCompressionAlgorithms(isCritical: Boolean, algorithms: Collection): SelfSignatureSubpackets + fun setPreferredCompressionAlgorithms( + isCritical: Boolean, + algorithms: Collection + ): SelfSignatureSubpackets - fun setPreferredCompressionAlgorithms(preferredAlgorithms: PreferredAlgorithms?): SelfSignatureSubpackets + fun setPreferredCompressionAlgorithms( + preferredAlgorithms: PreferredAlgorithms? + ): SelfSignatureSubpackets - fun setPreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): SelfSignatureSubpackets + fun setPreferredSymmetricKeyAlgorithms( + vararg algorithms: SymmetricKeyAlgorithm + ): SelfSignatureSubpackets - fun setPreferredSymmetricKeyAlgorithms(algorithms: Collection): SelfSignatureSubpackets + fun setPreferredSymmetricKeyAlgorithms( + algorithms: Collection + ): SelfSignatureSubpackets - fun setPreferredSymmetricKeyAlgorithms(isCritical: Boolean, algorithms: Collection): SelfSignatureSubpackets + fun setPreferredSymmetricKeyAlgorithms( + isCritical: Boolean, + algorithms: Collection + ): SelfSignatureSubpackets - fun setPreferredSymmetricKeyAlgorithms(algorithms: PreferredAlgorithms?): SelfSignatureSubpackets + fun setPreferredSymmetricKeyAlgorithms( + algorithms: PreferredAlgorithms? + ): SelfSignatureSubpackets fun setPreferredHashAlgorithms(vararg algorithms: HashAlgorithm): SelfSignatureSubpackets fun setPreferredHashAlgorithms(algorithms: Collection): SelfSignatureSubpackets - fun setPreferredHashAlgorithms(isCritical: Boolean, algorithms: Collection): SelfSignatureSubpackets + fun setPreferredHashAlgorithms( + isCritical: Boolean, + algorithms: Collection + ): SelfSignatureSubpackets fun setPreferredHashAlgorithms(algorithms: PreferredAlgorithms?): SelfSignatureSubpackets @@ -70,7 +101,11 @@ interface SelfSignatureSubpackets : BaseSignatureSubpackets { fun addRevocationKey(isCritical: Boolean, revocationKey: PGPPublicKey): SelfSignatureSubpackets - fun addRevocationKey(isCritical: Boolean, isSensitive: Boolean, revocationKey: PGPPublicKey): SelfSignatureSubpackets + fun addRevocationKey( + isCritical: Boolean, + isSensitive: Boolean, + revocationKey: PGPPublicKey + ): SelfSignatureSubpackets fun addRevocationKey(revocationKey: RevocationKey): SelfSignatureSubpackets @@ -81,4 +116,4 @@ interface SelfSignatureSubpackets : BaseSignatureSubpackets { fun setFeatures(isCritical: Boolean, vararg features: Feature): SelfSignatureSubpackets fun setFeatures(features: Features?): SelfSignatureSubpackets -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.kt index d147dc97..fbc56035 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketCallback.kt @@ -23,4 +23,4 @@ interface SignatureSubpacketCallback { fun modifyUnhashedSubpackets(unhashedSubpackets: S) { // Empty default implementation to allow for cleaner overriding } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt index a88e4e8a..e8fe2d94 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt @@ -4,6 +4,10 @@ package org.pgpainless.signature.subpackets +import java.lang.IllegalArgumentException +import java.net.URL +import java.util.* +import kotlin.experimental.or import openpgp.secondsTill import openpgp.toSecondsPrecision import org.bouncycastle.bcpg.SignatureSubpacket @@ -14,13 +18,12 @@ import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.PGPSignatureSubpacketVector import org.pgpainless.algorithm.* import org.pgpainless.key.util.RevocationAttributes -import java.lang.IllegalArgumentException -import java.net.URL -import java.util.* -import kotlin.experimental.or -class SignatureSubpackets - : BaseSignatureSubpackets, SelfSignatureSubpackets, CertificationSubpackets, RevocationSignatureSubpackets { +class SignatureSubpackets : + BaseSignatureSubpackets, + SelfSignatureSubpackets, + CertificationSubpackets, + RevocationSignatureSubpackets { interface Callback : SignatureSubpacketCallback @@ -52,7 +55,10 @@ class SignatureSubpackets companion object { @JvmStatic - fun refreshHashedSubpackets(issuer: PGPPublicKey, oldSignature: PGPSignature): SignatureSubpackets { + fun refreshHashedSubpackets( + issuer: PGPPublicKey, + oldSignature: PGPSignature + ): SignatureSubpackets { return createHashedSubpacketsFrom(issuer, oldSignature.hashedSubPackets) } @@ -62,10 +68,11 @@ class SignatureSubpackets } @JvmStatic - fun createHashedSubpacketsFrom(issuer: PGPPublicKey, base: PGPSignatureSubpacketVector): SignatureSubpackets { - return createSubpacketsFrom(base).apply { - setIssuerFingerprintAndKeyId(issuer) - } + fun createHashedSubpacketsFrom( + issuer: PGPPublicKey, + base: PGPSignatureSubpacketVector + ): SignatureSubpackets { + return createSubpacketsFrom(base).apply { setIssuerFingerprintAndKeyId(issuer) } } @JvmStatic @@ -84,15 +91,23 @@ class SignatureSubpackets } } - override fun setRevocationReason(revocationAttributes: RevocationAttributes): SignatureSubpackets = apply { - setRevocationReason(false, revocationAttributes) + override fun setRevocationReason( + revocationAttributes: RevocationAttributes + ): SignatureSubpackets = apply { setRevocationReason(false, revocationAttributes) } + + override fun setRevocationReason( + isCritical: Boolean, + revocationAttributes: RevocationAttributes + ): SignatureSubpackets = apply { + setRevocationReason( + isCritical, revocationAttributes.reason, revocationAttributes.description) } - override fun setRevocationReason(isCritical: Boolean, revocationAttributes: RevocationAttributes): SignatureSubpackets = apply { - setRevocationReason(isCritical, revocationAttributes.reason, revocationAttributes.description) - } - - override fun setRevocationReason(isCritical: Boolean, reason: RevocationAttributes.Reason, description: CharSequence): SignatureSubpackets = apply { + override fun setRevocationReason( + isCritical: Boolean, + reason: RevocationAttributes.Reason, + description: CharSequence + ): SignatureSubpackets = apply { setRevocationReason(RevocationReason(isCritical, reason.code, description.toString())) } @@ -108,17 +123,16 @@ class SignatureSubpackets setKeyFlags(true, *keyFlags.toTypedArray()) } - override fun setKeyFlags(isCritical: Boolean, vararg keyFlags: KeyFlag): SignatureSubpackets = apply { - setKeyFlags(KeyFlags(isCritical, KeyFlag.toBitmask(*keyFlags))) - } + override fun setKeyFlags(isCritical: Boolean, vararg keyFlags: KeyFlag): SignatureSubpackets = + apply { + setKeyFlags(KeyFlags(isCritical, KeyFlag.toBitmask(*keyFlags))) + } override fun setKeyFlags(keyFlags: KeyFlags?): SignatureSubpackets = apply { this.keyFlagsSubpacket = keyFlags } - override fun setPrimaryUserId(): SignatureSubpackets = apply { - setPrimaryUserId(true) - } + override fun setPrimaryUserId(): SignatureSubpackets = apply { setPrimaryUserId(true) } override fun setPrimaryUserId(isCritical: Boolean): SignatureSubpackets = apply { setPrimaryUserId(PrimaryUserID(isCritical, true)) @@ -128,15 +142,23 @@ class SignatureSubpackets this.primaryUserIdSubpacket = primaryUserID } - override fun setKeyExpirationTime(key: PGPPublicKey, keyExpirationTime: Date?): SignatureSubpackets = apply { - setKeyExpirationTime(key.creationTime, keyExpirationTime) - } + override fun setKeyExpirationTime( + key: PGPPublicKey, + keyExpirationTime: Date? + ): SignatureSubpackets = apply { setKeyExpirationTime(key.creationTime, keyExpirationTime) } - override fun setKeyExpirationTime(keyCreationTime: Date, keyExpirationTime: Date?): SignatureSubpackets = apply { + override fun setKeyExpirationTime( + keyCreationTime: Date, + keyExpirationTime: Date? + ): SignatureSubpackets = apply { setKeyExpirationTime(true, keyCreationTime, keyExpirationTime) } - override fun setKeyExpirationTime(isCritical: Boolean, keyCreationTime: Date, keyExpirationTime: Date?): SignatureSubpackets = apply { + override fun setKeyExpirationTime( + isCritical: Boolean, + keyCreationTime: Date, + keyExpirationTime: Date? + ): SignatureSubpackets = apply { if (keyExpirationTime == null) { setKeyExpirationTime(isCritical, 0) } else { @@ -144,94 +166,124 @@ class SignatureSubpackets } } - override fun setKeyExpirationTime(isCritical: Boolean, secondsFromCreationToExpiration: Long): SignatureSubpackets = apply { + override fun setKeyExpirationTime( + isCritical: Boolean, + secondsFromCreationToExpiration: Long + ): SignatureSubpackets = apply { enforceExpirationBounds(secondsFromCreationToExpiration) setKeyExpirationTime(KeyExpirationTime(isCritical, secondsFromCreationToExpiration)) } - override fun setKeyExpirationTime(keyExpirationTime: KeyExpirationTime?): SignatureSubpackets = apply { - this.keyExpirationTimeSubpacket = keyExpirationTime - } + override fun setKeyExpirationTime(keyExpirationTime: KeyExpirationTime?): SignatureSubpackets = + apply { + this.keyExpirationTimeSubpacket = keyExpirationTime + } - override fun setPreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): SignatureSubpackets = apply { - setPreferredCompressionAlgorithms(setOf(*algorithms)) - } + override fun setPreferredCompressionAlgorithms( + vararg algorithms: CompressionAlgorithm + ): SignatureSubpackets = apply { setPreferredCompressionAlgorithms(setOf(*algorithms)) } - override fun setPreferredCompressionAlgorithms(algorithms: Collection): SignatureSubpackets = apply { - setPreferredCompressionAlgorithms(false, algorithms) - } + override fun setPreferredCompressionAlgorithms( + algorithms: Collection + ): SignatureSubpackets = apply { setPreferredCompressionAlgorithms(false, algorithms) } - override fun setPreferredCompressionAlgorithms(isCritical: Boolean, algorithms: Collection): SignatureSubpackets = apply { - setPreferredCompressionAlgorithms(PreferredAlgorithms( + override fun setPreferredCompressionAlgorithms( + isCritical: Boolean, + algorithms: Collection + ): SignatureSubpackets = apply { + setPreferredCompressionAlgorithms( + PreferredAlgorithms( SignatureSubpacketTags.PREFERRED_COMP_ALGS, isCritical, algorithms.map { it.algorithmId }.toIntArray())) } - override fun setPreferredCompressionAlgorithms(algorithms: PreferredAlgorithms?): SignatureSubpackets = apply { - require(algorithms == null || algorithms.type == SignatureSubpacketTags.PREFERRED_COMP_ALGS) { - "Invalid preferred compression algorithms type." - } + override fun setPreferredCompressionAlgorithms( + algorithms: PreferredAlgorithms? + ): SignatureSubpackets = apply { + require( + algorithms == null || algorithms.type == SignatureSubpacketTags.PREFERRED_COMP_ALGS) { + "Invalid preferred compression algorithms type." + } this.preferredCompressionAlgorithmsSubpacket = algorithms } - override fun setPreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): SignatureSubpackets = apply { - setPreferredSymmetricKeyAlgorithms(setOf(*algorithms)) - } + override fun setPreferredSymmetricKeyAlgorithms( + vararg algorithms: SymmetricKeyAlgorithm + ): SignatureSubpackets = apply { setPreferredSymmetricKeyAlgorithms(setOf(*algorithms)) } - override fun setPreferredSymmetricKeyAlgorithms(algorithms: Collection): SignatureSubpackets = apply { - setPreferredSymmetricKeyAlgorithms(false, algorithms) - } + override fun setPreferredSymmetricKeyAlgorithms( + algorithms: Collection + ): SignatureSubpackets = apply { setPreferredSymmetricKeyAlgorithms(false, algorithms) } - override fun setPreferredSymmetricKeyAlgorithms(isCritical: Boolean, algorithms: Collection): SignatureSubpackets = apply { - setPreferredSymmetricKeyAlgorithms(PreferredAlgorithms( + override fun setPreferredSymmetricKeyAlgorithms( + isCritical: Boolean, + algorithms: Collection + ): SignatureSubpackets = apply { + setPreferredSymmetricKeyAlgorithms( + PreferredAlgorithms( SignatureSubpacketTags.PREFERRED_SYM_ALGS, isCritical, - algorithms.map { it.algorithmId }.toIntArray() - )) + algorithms.map { it.algorithmId }.toIntArray())) } - override fun setPreferredSymmetricKeyAlgorithms(algorithms: PreferredAlgorithms?): SignatureSubpackets = apply { - require(algorithms == null || algorithms.type == SignatureSubpacketTags.PREFERRED_SYM_ALGS) { - "Invalid preferred symmetric algorithms type." - } + override fun setPreferredSymmetricKeyAlgorithms( + algorithms: PreferredAlgorithms? + ): SignatureSubpackets = apply { + require( + algorithms == null || algorithms.type == SignatureSubpacketTags.PREFERRED_SYM_ALGS) { + "Invalid preferred symmetric algorithms type." + } this.preferredSymmetricKeyAlgorithmsSubpacket = algorithms } - override fun setPreferredHashAlgorithms(vararg algorithms: HashAlgorithm): SignatureSubpackets = apply { - setPreferredHashAlgorithms(setOf(*algorithms)) - } + override fun setPreferredHashAlgorithms(vararg algorithms: HashAlgorithm): SignatureSubpackets = + apply { + setPreferredHashAlgorithms(setOf(*algorithms)) + } - override fun setPreferredHashAlgorithms(algorithms: Collection): SignatureSubpackets = apply { - setPreferredHashAlgorithms(false, algorithms) - } + override fun setPreferredHashAlgorithms( + algorithms: Collection + ): SignatureSubpackets = apply { setPreferredHashAlgorithms(false, algorithms) } - override fun setPreferredHashAlgorithms(isCritical: Boolean, algorithms: Collection): SignatureSubpackets = apply { - setPreferredHashAlgorithms(PreferredAlgorithms( + override fun setPreferredHashAlgorithms( + isCritical: Boolean, + algorithms: Collection + ): SignatureSubpackets = apply { + setPreferredHashAlgorithms( + PreferredAlgorithms( SignatureSubpacketTags.PREFERRED_HASH_ALGS, isCritical, - algorithms.map { it.algorithmId }.toIntArray() - )) + algorithms.map { it.algorithmId }.toIntArray())) } - override fun setPreferredHashAlgorithms(algorithms: PreferredAlgorithms?): SignatureSubpackets = apply { - require(algorithms == null || algorithms.type == SignatureSubpacketTags.PREFERRED_HASH_ALGS) { - "Invalid preferred hash algorithms type." + override fun setPreferredHashAlgorithms(algorithms: PreferredAlgorithms?): SignatureSubpackets = + apply { + require( + algorithms == null || + algorithms.type == SignatureSubpacketTags.PREFERRED_HASH_ALGS) { + "Invalid preferred hash algorithms type." + } + this.preferredHashAlgorithmsSubpacket = algorithms } - this.preferredHashAlgorithmsSubpacket = algorithms - } override fun addRevocationKey(revocationKey: PGPPublicKey): SignatureSubpackets = apply { addRevocationKey(true, revocationKey) } - override fun addRevocationKey(isCritical: Boolean, revocationKey: PGPPublicKey): SignatureSubpackets = apply { - addRevocationKey(isCritical, false, revocationKey) - } + override fun addRevocationKey( + isCritical: Boolean, + revocationKey: PGPPublicKey + ): SignatureSubpackets = apply { addRevocationKey(isCritical, false, revocationKey) } - override fun addRevocationKey(isCritical: Boolean, isSensitive: Boolean, revocationKey: PGPPublicKey): SignatureSubpackets = apply { + override fun addRevocationKey( + isCritical: Boolean, + isSensitive: Boolean, + revocationKey: PGPPublicKey + ): SignatureSubpackets = apply { val clazz = 0x80.toByte() or if (isSensitive) 0x40.toByte() else 0x00.toByte() - addRevocationKey(RevocationKey(isCritical, clazz, revocationKey.algorithm, revocationKey.fingerprint)) + addRevocationKey( + RevocationKey(isCritical, clazz, revocationKey.algorithm, revocationKey.fingerprint)) } override fun addRevocationKey(revocationKey: RevocationKey): SignatureSubpackets = apply { @@ -246,9 +298,10 @@ class SignatureSubpackets setFeatures(true, *features) } - override fun setFeatures(isCritical: Boolean, vararg features: Feature): SignatureSubpackets = apply { - setFeatures(Features(isCritical, Feature.toBitmask(*features))) - } + override fun setFeatures(isCritical: Boolean, vararg features: Feature): SignatureSubpackets = + apply { + setFeatures(Features(isCritical, Feature.toBitmask(*features))) + } override fun setFeatures(features: Features?): SignatureSubpackets = apply { this.featuresSubpacket = features @@ -271,7 +324,10 @@ class SignatureSubpackets this.issuerKeyIdSubpacket = issuerKeyID } - override fun setIssuerFingerprint(isCritical: Boolean, issuer: PGPPublicKey): SignatureSubpackets = apply { + override fun setIssuerFingerprint( + isCritical: Boolean, + issuer: PGPPublicKey + ): SignatureSubpackets = apply { setIssuerFingerprint(IssuerFingerprint(isCritical, issuer.version, issuer.fingerprint)) } @@ -279,44 +335,60 @@ class SignatureSubpackets setIssuerFingerprint(false, issuer) } - override fun setIssuerFingerprint(fingerprint: IssuerFingerprint?): SignatureSubpackets = apply { - this.issuerFingerprintSubpacket = fingerprint - } + override fun setIssuerFingerprint(fingerprint: IssuerFingerprint?): SignatureSubpackets = + apply { + this.issuerFingerprintSubpacket = fingerprint + } override fun setSignatureCreationTime(creationTime: Date): SignatureSubpackets = apply { setSignatureCreationTime(true, creationTime) } - override fun setSignatureCreationTime(isCritical: Boolean, creationTime: Date): SignatureSubpackets = apply { + override fun setSignatureCreationTime( + isCritical: Boolean, + creationTime: Date + ): SignatureSubpackets = apply { setSignatureCreationTime(SignatureCreationTime(isCritical, creationTime)) } - override fun setSignatureCreationTime(creationTime: SignatureCreationTime?): SignatureSubpackets = apply { - this.signatureCreationTimeSubpacket = creationTime - } - override fun setSignatureExpirationTime(creationTime: Date, expirationTime: Date?): SignatureSubpackets = apply { + override fun setSignatureCreationTime( + creationTime: SignatureCreationTime? + ): SignatureSubpackets = apply { this.signatureCreationTimeSubpacket = creationTime } + + override fun setSignatureExpirationTime( + creationTime: Date, + expirationTime: Date? + ): SignatureSubpackets = apply { setSignatureExpirationTime(true, creationTime, expirationTime) } - override fun setSignatureExpirationTime(isCritical: Boolean, creationTime: Date, expirationTime: Date?): SignatureSubpackets = apply { + override fun setSignatureExpirationTime( + isCritical: Boolean, + creationTime: Date, + expirationTime: Date? + ): SignatureSubpackets = apply { if (expirationTime != null) { require(creationTime.toSecondsPrecision() < expirationTime.toSecondsPrecision()) { "Expiration time MUST NOT be less or equal the creation time." } - setSignatureExpirationTime(SignatureExpirationTime(isCritical, creationTime.secondsTill(expirationTime))) + setSignatureExpirationTime( + SignatureExpirationTime(isCritical, creationTime.secondsTill(expirationTime))) } else { setSignatureExpirationTime(SignatureExpirationTime(isCritical, 0)) } } - override fun setSignatureExpirationTime(isCritical: Boolean, seconds: Long): SignatureSubpackets = apply { + override fun setSignatureExpirationTime( + isCritical: Boolean, + seconds: Long + ): SignatureSubpackets = apply { enforceExpirationBounds(seconds) setSignatureExpirationTime(SignatureExpirationTime(isCritical, seconds)) } /** - * Enforce that
seconds
is within bounds of an unsigned 32bit number. - * Values less than 0 are illegal, as well as values greater 0xffffffff. + * Enforce that
seconds
is within bounds of an unsigned 32bit number. Values less + * than 0 are illegal, as well as values greater 0xffffffff. * * @param seconds number to check * @throws IllegalArgumentException in case of an under- or overflow @@ -325,32 +397,40 @@ class SignatureSubpackets require(seconds <= 0xffffffffL) { "Integer overflow. Seconds from creation to expiration (${seconds}) cannot be larger than ${0xffffffffL}." } - require(seconds >= 0) { - "Seconds from creation to expiration cannot be less than 0." - } + require(seconds >= 0) { "Seconds from creation to expiration cannot be less than 0." } } - override fun setSignatureExpirationTime(expirationTime: SignatureExpirationTime?): SignatureSubpackets = apply { - this.signatureExpirationTimeSubpacket = expirationTime - } + override fun setSignatureExpirationTime( + expirationTime: SignatureExpirationTime? + ): SignatureSubpackets = apply { this.signatureExpirationTimeSubpacket = expirationTime } override fun setSignerUserId(userId: CharSequence): SignatureSubpackets = apply { setSignerUserId(false, userId) } - override fun setSignerUserId(isCritical: Boolean, userId: CharSequence): SignatureSubpackets = apply { - setSignerUserId(SignerUserID(isCritical, userId.toString())) - } + override fun setSignerUserId(isCritical: Boolean, userId: CharSequence): SignatureSubpackets = + apply { + setSignerUserId(SignerUserID(isCritical, userId.toString())) + } override fun setSignerUserId(signerUserID: SignerUserID?): SignatureSubpackets = apply { this.signerUserIdSubpacket = signerUserID } - override fun addNotationData(isCritical: Boolean, notationName: String, notationValue: String): SignatureSubpackets = apply { + override fun addNotationData( + isCritical: Boolean, + notationName: String, + notationValue: String + ): SignatureSubpackets = apply { addNotationData(isCritical, true, notationName, notationValue) } - override fun addNotationData(isCritical: Boolean, isHumanReadable: Boolean, notationName: String, notationValue: String): SignatureSubpackets = apply { + override fun addNotationData( + isCritical: Boolean, + isHumanReadable: Boolean, + notationName: String, + notationValue: String + ): SignatureSubpackets = apply { addNotationData(NotationData(isCritical, isHumanReadable, notationName, notationValue)) } @@ -362,15 +442,23 @@ class SignatureSubpackets (this.notationDataSubpackets as MutableList).clear() } - override fun addIntendedRecipientFingerprint(recipientKey: PGPPublicKey): SignatureSubpackets = apply { - addIntendedRecipientFingerprint(false, recipientKey) + override fun addIntendedRecipientFingerprint(recipientKey: PGPPublicKey): SignatureSubpackets = + apply { + addIntendedRecipientFingerprint(false, recipientKey) + } + + override fun addIntendedRecipientFingerprint( + isCritical: Boolean, + recipientKey: PGPPublicKey + ): SignatureSubpackets = apply { + addIntendedRecipientFingerprint( + IntendedRecipientFingerprint( + isCritical, recipientKey.version, recipientKey.fingerprint)) } - override fun addIntendedRecipientFingerprint(isCritical: Boolean, recipientKey: PGPPublicKey): SignatureSubpackets = apply { - addIntendedRecipientFingerprint(IntendedRecipientFingerprint(isCritical, recipientKey.version, recipientKey.fingerprint)) - } - - override fun addIntendedRecipientFingerprint(intendedRecipient: IntendedRecipientFingerprint): SignatureSubpackets = apply { + override fun addIntendedRecipientFingerprint( + intendedRecipient: IntendedRecipientFingerprint + ): SignatureSubpackets = apply { (this.intendedRecipientFingerprintSubpackets as MutableList).add(intendedRecipient) } @@ -378,17 +466,16 @@ class SignatureSubpackets (this.intendedRecipientFingerprintSubpackets as MutableList).clear() } - override fun setExportable(): SignatureSubpackets = apply { - setExportable(true) - } + override fun setExportable(): SignatureSubpackets = apply { setExportable(true) } override fun setExportable(isExportable: Boolean): SignatureSubpackets = apply { setExportable(true, isExportable) } - override fun setExportable(isCritical: Boolean, isExportable: Boolean): SignatureSubpackets = apply { - setExportable(Exportable(isCritical, isExportable)) - } + override fun setExportable(isCritical: Boolean, isExportable: Boolean): SignatureSubpackets = + apply { + setExportable(Exportable(isCritical, isExportable)) + } override fun setExportable(exportable: Exportable?): SignatureSubpackets = apply { this.exportableSubpacket = exportable @@ -410,7 +497,10 @@ class SignatureSubpackets setRegularExpression(false, regex) } - override fun setRegularExpression(isCritical: Boolean, regex: CharSequence): SignatureSubpackets = apply { + override fun setRegularExpression( + isCritical: Boolean, + regex: CharSequence + ): SignatureSubpackets = apply { setRegularExpression(RegularExpression(isCritical, regex.toString())) } @@ -418,41 +508,53 @@ class SignatureSubpackets this.regularExpressionSubpacket = regex } - override fun setRevocable(): SignatureSubpackets = apply { - setRevocable(true) - } + override fun setRevocable(): SignatureSubpackets = apply { setRevocable(true) } override fun setRevocable(isRevocable: Boolean): SignatureSubpackets = apply { setRevocable(true, isRevocable) } - override fun setRevocable(isCritical: Boolean, isRevocable: Boolean): SignatureSubpackets = apply { - setRevocable(Revocable(isCritical, isRevocable)) - } + override fun setRevocable(isCritical: Boolean, isRevocable: Boolean): SignatureSubpackets = + apply { + setRevocable(Revocable(isCritical, isRevocable)) + } override fun setRevocable(revocable: Revocable?): SignatureSubpackets = apply { this.revocableSubpacket = revocable } - override fun setSignatureTarget(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm, hashData: ByteArray): SignatureSubpackets = apply { + override fun setSignatureTarget( + keyAlgorithm: PublicKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + hashData: ByteArray + ): SignatureSubpackets = apply { setSignatureTarget(true, keyAlgorithm, hashAlgorithm, hashData) } - override fun setSignatureTarget(isCritical: Boolean, keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm, hashData: ByteArray): SignatureSubpackets = apply { - setSignatureTarget(SignatureTarget(isCritical, keyAlgorithm.algorithmId, hashAlgorithm.algorithmId, hashData)) + override fun setSignatureTarget( + isCritical: Boolean, + keyAlgorithm: PublicKeyAlgorithm, + hashAlgorithm: HashAlgorithm, + hashData: ByteArray + ): SignatureSubpackets = apply { + setSignatureTarget( + SignatureTarget( + isCritical, keyAlgorithm.algorithmId, hashAlgorithm.algorithmId, hashData)) } - override fun setSignatureTarget(signatureTarget: SignatureTarget?): SignatureSubpackets = apply { - this.signatureTargetSubpacket = signatureTarget - } + override fun setSignatureTarget(signatureTarget: SignatureTarget?): SignatureSubpackets = + apply { + this.signatureTargetSubpacket = signatureTarget + } override fun setTrust(depth: Int, amount: Int): SignatureSubpackets = apply { setTrust(true, depth, amount) } - override fun setTrust(isCritical: Boolean, depth: Int, amount: Int): SignatureSubpackets = apply { - setTrust(TrustSignature(isCritical, depth, amount)) - } + override fun setTrust(isCritical: Boolean, depth: Int, amount: Int): SignatureSubpackets = + apply { + setTrust(TrustSignature(isCritical, depth, amount)) + } override fun setTrust(trust: TrustSignature?): SignatureSubpackets = apply { this.trustSubpacket = trust @@ -462,26 +564,31 @@ class SignatureSubpackets addEmbeddedSignature(true, signature) } - override fun addEmbeddedSignature(isCritical: Boolean, signature: PGPSignature): SignatureSubpackets = apply { + override fun addEmbeddedSignature( + isCritical: Boolean, + signature: PGPSignature + ): SignatureSubpackets = apply { val sig = signature.encoded - val data = if (sig.size - 1 > 256) { - ByteArray(sig.size - 3) - } else { - ByteArray(sig.size - 2) - } + val data = + if (sig.size - 1 > 256) { + ByteArray(sig.size - 3) + } else { + ByteArray(sig.size - 2) + } System.arraycopy(sig, sig.size - data.size, data, 0, data.size) addEmbeddedSignature(EmbeddedSignature(isCritical, false, data)) } - override fun addEmbeddedSignature(embeddedSignature: EmbeddedSignature): SignatureSubpackets = apply { - (this.embeddedSignatureSubpackets as MutableList).add(embeddedSignature) - } + override fun addEmbeddedSignature(embeddedSignature: EmbeddedSignature): SignatureSubpackets = + apply { + (this.embeddedSignatureSubpackets as MutableList).add(embeddedSignature) + } override fun clearEmbeddedSignatures(): SignatureSubpackets = apply { (this.embeddedSignatureSubpackets as MutableList).clear() } - fun addResidualSubpacket(subpacket: org.bouncycastle.bcpg.SignatureSubpacket): SignatureSubpackets = apply { - (residualSubpackets as MutableList).add(subpacket) - } -} \ No newline at end of file + fun addResidualSubpacket( + subpacket: org.bouncycastle.bcpg.SignatureSubpacket + ): SignatureSubpackets = apply { (residualSubpackets as MutableList).add(subpacket) } +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.kt index 6767b0af..6c39432e 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsHelper.kt @@ -14,87 +14,135 @@ class SignatureSubpacketsHelper { companion object { @JvmStatic - fun applyFrom(vector: PGPSignatureSubpacketVector, subpackets: SignatureSubpackets) = subpackets.apply { - for (subpacket in vector.toArray()) { - val type = SignatureSubpacket.requireFromCode(subpacket.type) - when (type) { - SignatureSubpacket.signatureCreationTime, - SignatureSubpacket.issuerKeyId, - SignatureSubpacket.issuerFingerprint -> { /* ignore, we override this anyway */ } - SignatureSubpacket.signatureExpirationTime -> (subpacket as SignatureExpirationTime).let { - subpackets.setSignatureExpirationTime(it.isCritical, it.time) - } - SignatureSubpacket.exportableCertification -> (subpacket as Exportable).let { - subpackets.setExportable(it.isCritical, it.isExportable) - } - SignatureSubpacket.trustSignature -> (subpacket as TrustSignature).let { - subpackets.setTrust(it.isCritical, it.depth, it.trustAmount) - } - SignatureSubpacket.revocable -> (subpacket as Revocable).let { - subpackets.setRevocable(it.isCritical, it.isRevocable) - } - SignatureSubpacket.keyExpirationTime -> (subpacket as KeyExpirationTime).let { - subpackets.setKeyExpirationTime(it.isCritical, it.time) - } - SignatureSubpacket.preferredSymmetricAlgorithms -> (subpacket as PreferredAlgorithms).let { - subpackets.setPreferredSymmetricKeyAlgorithms(PreferredAlgorithms(it.type, it.isCritical, it.isLongLength, it.data)) - } - SignatureSubpacket.preferredHashAlgorithms -> (subpacket as PreferredAlgorithms).let { - subpackets.setPreferredHashAlgorithms(PreferredAlgorithms(it.type, it.isCritical, it.isLongLength, it.data)) - } - SignatureSubpacket.preferredCompressionAlgorithms -> (subpacket as PreferredAlgorithms).let { - subpackets.setPreferredCompressionAlgorithms(PreferredAlgorithms(it.type, it.isCritical, it.isLongLength, it.data)) - } - SignatureSubpacket.revocationKey -> (subpacket as RevocationKey).let { - subpackets.addRevocationKey(RevocationKey(it.isCritical, it.signatureClass, it.algorithm, it.fingerprint)) - } - SignatureSubpacket.notationData -> (subpacket as NotationData).let { - subpackets.addNotationData(it.isCritical, it.isHumanReadable, it.notationName, it.notationValue) - } - SignatureSubpacket.primaryUserId -> (subpacket as PrimaryUserID).let { - subpackets.setPrimaryUserId(PrimaryUserID(it.isCritical, it.isPrimaryUserID)) - } - SignatureSubpacket.keyFlags -> (subpacket as KeyFlags).let { - subpackets.setKeyFlags(it.isCritical, *(KeyFlag.fromBitmask(it.flags).toTypedArray())) - } - SignatureSubpacket.signerUserId -> (subpacket as SignerUserID).let { - subpackets.setSignerUserId(it.isCritical, it.id) - } - SignatureSubpacket.revocationReason -> (subpacket as RevocationReason).let { - subpackets.setRevocationReason(it.isCritical, RevocationAttributes.Reason.fromCode(it.revocationReason), it.revocationDescription) - } - SignatureSubpacket.features -> (subpacket as Features).let { - subpackets.setFeatures(it.isCritical, *(Feature.fromBitmask(it.features.toInt()).toTypedArray())) - } - SignatureSubpacket.signatureTarget -> (subpacket as SignatureTarget).let { - subpackets.setSignatureTarget(it.isCritical, - PublicKeyAlgorithm.requireFromId(it.publicKeyAlgorithm), - HashAlgorithm.requireFromId(it.hashAlgorithm), - it.hashData) - } - SignatureSubpacket.embeddedSignature -> (subpacket as EmbeddedSignature).let { - subpackets.addEmbeddedSignature(it) - } - SignatureSubpacket.intendedRecipientFingerprint -> (subpacket as IntendedRecipientFingerprint).let { - subpackets.addIntendedRecipientFingerprint(it) - } - SignatureSubpacket.policyUrl -> (subpacket as PolicyURI).let { - subpackets.setPolicyUrl(it) - } - SignatureSubpacket.regularExpression -> (subpacket as RegularExpression).let { - subpackets.setRegularExpression(it) - } - SignatureSubpacket.keyServerPreferences, + fun applyFrom(vector: PGPSignatureSubpacketVector, subpackets: SignatureSubpackets) = + subpackets.apply { + for (subpacket in vector.toArray()) { + val type = SignatureSubpacket.requireFromCode(subpacket.type) + when (type) { + SignatureSubpacket.signatureCreationTime, + SignatureSubpacket.issuerKeyId, + SignatureSubpacket.issuerFingerprint -> { + /* ignore, we override this anyway */ + } + SignatureSubpacket.signatureExpirationTime -> + (subpacket as SignatureExpirationTime).let { + subpackets.setSignatureExpirationTime(it.isCritical, it.time) + } + SignatureSubpacket.exportableCertification -> + (subpacket as Exportable).let { + subpackets.setExportable(it.isCritical, it.isExportable) + } + SignatureSubpacket.trustSignature -> + (subpacket as TrustSignature).let { + subpackets.setTrust(it.isCritical, it.depth, it.trustAmount) + } + SignatureSubpacket.revocable -> + (subpacket as Revocable).let { + subpackets.setRevocable(it.isCritical, it.isRevocable) + } + SignatureSubpacket.keyExpirationTime -> + (subpacket as KeyExpirationTime).let { + subpackets.setKeyExpirationTime(it.isCritical, it.time) + } + SignatureSubpacket.preferredSymmetricAlgorithms -> + (subpacket as PreferredAlgorithms).let { + subpackets.setPreferredSymmetricKeyAlgorithms( + PreferredAlgorithms( + it.type, it.isCritical, it.isLongLength, it.data)) + } + SignatureSubpacket.preferredHashAlgorithms -> + (subpacket as PreferredAlgorithms).let { + subpackets.setPreferredHashAlgorithms( + PreferredAlgorithms( + it.type, it.isCritical, it.isLongLength, it.data)) + } + SignatureSubpacket.preferredCompressionAlgorithms -> + (subpacket as PreferredAlgorithms).let { + subpackets.setPreferredCompressionAlgorithms( + PreferredAlgorithms( + it.type, it.isCritical, it.isLongLength, it.data)) + } + SignatureSubpacket.revocationKey -> + (subpacket as RevocationKey).let { + subpackets.addRevocationKey( + RevocationKey( + it.isCritical, + it.signatureClass, + it.algorithm, + it.fingerprint)) + } + SignatureSubpacket.notationData -> + (subpacket as NotationData).let { + subpackets.addNotationData( + it.isCritical, + it.isHumanReadable, + it.notationName, + it.notationValue) + } + SignatureSubpacket.primaryUserId -> + (subpacket as PrimaryUserID).let { + subpackets.setPrimaryUserId( + PrimaryUserID(it.isCritical, it.isPrimaryUserID)) + } + SignatureSubpacket.keyFlags -> + (subpacket as KeyFlags).let { + subpackets.setKeyFlags( + it.isCritical, *(KeyFlag.fromBitmask(it.flags).toTypedArray())) + } + SignatureSubpacket.signerUserId -> + (subpacket as SignerUserID).let { + subpackets.setSignerUserId(it.isCritical, it.id) + } + SignatureSubpacket.revocationReason -> + (subpacket as RevocationReason).let { + subpackets.setRevocationReason( + it.isCritical, + RevocationAttributes.Reason.fromCode(it.revocationReason), + it.revocationDescription) + } + SignatureSubpacket.features -> + (subpacket as Features).let { + subpackets.setFeatures( + it.isCritical, + *(Feature.fromBitmask(it.features.toInt()).toTypedArray())) + } + SignatureSubpacket.signatureTarget -> + (subpacket as SignatureTarget).let { + subpackets.setSignatureTarget( + it.isCritical, + PublicKeyAlgorithm.requireFromId(it.publicKeyAlgorithm), + HashAlgorithm.requireFromId(it.hashAlgorithm), + it.hashData) + } + SignatureSubpacket.embeddedSignature -> + (subpacket as EmbeddedSignature).let { + subpackets.addEmbeddedSignature(it) + } + SignatureSubpacket.intendedRecipientFingerprint -> + (subpacket as IntendedRecipientFingerprint).let { + subpackets.addIntendedRecipientFingerprint(it) + } + SignatureSubpacket.policyUrl -> + (subpacket as PolicyURI).let { subpackets.setPolicyUrl(it) } + SignatureSubpacket.regularExpression -> + (subpacket as RegularExpression).let { + subpackets.setRegularExpression(it) + } + SignatureSubpacket.keyServerPreferences, SignatureSubpacket.preferredKeyServers, SignatureSubpacket.placeholder, SignatureSubpacket.preferredAEADAlgorithms, - SignatureSubpacket.attestedCertification -> subpackets.addResidualSubpacket(subpacket) + SignatureSubpacket.attestedCertification -> + subpackets.addResidualSubpacket(subpacket) + } } } - } @JvmStatic - fun applyTo(subpackets: SignatureSubpackets, generator: PGPSignatureSubpacketGenerator): PGPSignatureSubpacketGenerator { + fun applyTo( + subpackets: SignatureSubpackets, + generator: PGPSignatureSubpacketGenerator + ): PGPSignatureSubpacketGenerator { return generator.apply { addSubpacket(subpackets.issuerKeyIdSubpacket) addSubpacket(subpackets.issuerFingerprintSubpacket) @@ -134,7 +182,9 @@ class SignatureSubpacketsHelper { } @JvmStatic - private fun PGPSignatureSubpacketGenerator.addSubpacket(subpacket: org.bouncycastle.bcpg.SignatureSubpacket?) { + private fun PGPSignatureSubpacketGenerator.addSubpacket( + subpacket: org.bouncycastle.bcpg.SignatureSubpacket? + ) { if (subpacket != null) { this.addCustomSubpacket(subpacket) } @@ -156,4 +206,4 @@ class SignatureSubpacketsHelper { } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt index 2cf79d72..31e1f53c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt @@ -4,6 +4,7 @@ package org.pgpainless.signature.subpackets +import java.util.* import openpgp.openPgpKeyId import openpgp.plusSeconds import org.bouncycastle.bcpg.sig.* @@ -19,27 +20,26 @@ import org.pgpainless.key.OpenPgpV4Fingerprint import org.pgpainless.key.OpenPgpV5Fingerprint import org.pgpainless.key.OpenPgpV6Fingerprint import org.pgpainless.key.generation.type.KeyType -import org.pgpainless.signature.SignatureUtils -import java.util.* class SignatureSubpacketsUtil { companion object { /** - * Return the issuer-fingerprint subpacket of the signature. - * Since this packet is self-authenticating, we expect it to be in the unhashed area, - * however as it cannot hurt we search for it in the hashed area first. + * Return the issuer-fingerprint subpacket of the signature. Since this packet is + * self-authenticating, we expect it to be in the unhashed area, however as it cannot hurt + * we search for it in the hashed area first. * * @param signature signature * @return issuer fingerprint or null */ @JvmStatic fun getIssuerFingerprint(signature: PGPSignature): IssuerFingerprint? = - hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint) + hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint) /** - * Return the [IssuerFingerprint] subpacket of the signature into a [org.pgpainless.key.OpenPgpFingerprint]. - * If no v4, v5 or v6 issuer fingerprint is present in the signature, return null. + * Return the [IssuerFingerprint] subpacket of the signature into a + * [org.pgpainless.key.OpenPgpFingerprint]. If no v4, v5 or v6 issuer fingerprint is present + * in the signature, return null. * * @param signature signature * @return fingerprint of the issuer, or null @@ -47,7 +47,7 @@ class SignatureSubpacketsUtil { @JvmStatic fun getIssuerFingerprintAsOpenPgpFingerprint(signature: PGPSignature): OpenPgpFingerprint? { val subpacket = getIssuerFingerprint(signature) ?: return null - return when(subpacket.keyVersion) { + return when (subpacket.keyVersion) { 4 -> OpenPgpV4Fingerprint(subpacket.fingerprint) 5 -> OpenPgpV5Fingerprint(subpacket.fingerprint) 6 -> OpenPgpV6Fingerprint(subpacket.fingerprint) @@ -57,85 +57,83 @@ class SignatureSubpacketsUtil { @JvmStatic fun getIssuerKeyId(signature: PGPSignature): IssuerKeyID? = - hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId) + hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId) /** - * Inspect the given signature's [IssuerKeyID] packet to determine the issuer key-id. - * If no such packet is present, return null. + * Inspect the given signature's [IssuerKeyID] packet to determine the issuer key-id. If no + * such packet is present, return null. * * @param signature signature * @return issuer key-id as {@link Long} */ @JvmStatic - fun getIssuerKeyIdAsLong(signature: PGPSignature): Long? = - getIssuerKeyId(signature)?.keyID + fun getIssuerKeyIdAsLong(signature: PGPSignature): Long? = getIssuerKeyId(signature)?.keyID /** - * Return the revocation reason subpacket of the signature. - * Since this packet is rather important for revocations, we only search for it in the - * hashed area of the signature. + * Return the revocation reason subpacket of the signature. Since this packet is rather + * important for revocations, we only search for it in the hashed area of the signature. * * @param signature signature * @return revocation reason */ @JvmStatic fun getRevocationReason(signature: PGPSignature): RevocationReason? = - hashed(signature, SignatureSubpacket.revocationReason) + hashed(signature, SignatureSubpacket.revocationReason) /** - * Return the signature creation time subpacket. - * Since this packet is rather important, we only search for it in the hashed area - * of the signature. + * Return the signature creation time subpacket. Since this packet is rather important, we + * only search for it in the hashed area of the signature. * * @param signature signature * @return signature creation time subpacket */ @JvmStatic fun getSignatureCreationTime(signature: PGPSignature): SignatureCreationTime? = - if (signature.version == 3) SignatureCreationTime(false, signature.creationTime) - else hashed(signature, SignatureSubpacket.signatureCreationTime) + if (signature.version == 3) SignatureCreationTime(false, signature.creationTime) + else hashed(signature, SignatureSubpacket.signatureCreationTime) /** - * Return the signature expiration time subpacket of the signature. - * Since this packet is rather important, we only search for it in the hashed area of the signature. + * Return the signature expiration time subpacket of the signature. Since this packet is + * rather important, we only search for it in the hashed area of the signature. * * @param signature signature * @return signature expiration time */ @JvmStatic fun getSignatureExpirationTime(signature: PGPSignature): SignatureExpirationTime? = - hashed(signature, SignatureSubpacket.signatureExpirationTime) + hashed(signature, SignatureSubpacket.signatureExpirationTime) /** - * Return the signatures' expiration time as a date. - * The expiration date is computed by adding the expiration time to the signature creation date. - * If the signature has no expiration time subpacket, or the expiration time is set to '0', this message returns null. + * Return the signatures' expiration time as a date. The expiration date is computed by + * adding the expiration time to the signature creation date. If the signature has no + * expiration time subpacket, or the expiration time is set to '0', this message returns + * null. * * @param signature signature * @return expiration time as date */ @JvmStatic fun getSignatureExpirationTimeAsDate(signature: PGPSignature): Date? = - getSignatureExpirationTime(signature)?.let { - signature.creationTime.plusSeconds(it.time) - } + getSignatureExpirationTime(signature)?.let { + signature.creationTime.plusSeconds(it.time) + } /** - * Return the key expiration time subpacket of this signature. - * We only look for it in the hashed area of the signature. + * Return the key expiration time subpacket of this signature. We only look for it in the + * hashed area of the signature. * * @param signature signature * @return key expiration time */ @JvmStatic fun getKeyExpirationTime(signature: PGPSignature): KeyExpirationTime? = - hashed(signature, SignatureSubpacket.keyExpirationTime) + hashed(signature, SignatureSubpacket.keyExpirationTime) /** - * Return the signatures key-expiration time as a date. - * The expiration date is computed by adding the signatures' key-expiration time to the signing keys - * creation date. - * If the signature does not have a key-expiration time subpacket, or its value is '0', this method returns null. + * Return the signatures key-expiration time as a date. The expiration date is computed by + * adding the signatures' key-expiration time to the signing keys creation date. If the + * signature does not have a key-expiration time subpacket, or its value is '0', this method + * returns null. * * @param signature self-signature carrying the key-expiration time subpacket * @param signingKey signature creation key @@ -143,9 +141,10 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getKeyExpirationTimeAsDate(signature: PGPSignature, signingKey: PGPPublicKey): Date? = - require(signature.keyID == signingKey.keyID) { + require(signature.keyID == signingKey.keyID) { "Provided key (${signingKey.keyID.openPgpKeyId()}) did not create the signature (${signature.keyID.openPgpKeyId()})" - }.run { + } + .run { getKeyExpirationTime(signature)?.let { signingKey.creationTime.plusSeconds(it.time) } @@ -160,25 +159,25 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getKeyLifetimeInSeconds(creationTime: Date, expirationTime: Date?): Long = - expirationTime?.let { - require(creationTime <= it) { + expirationTime?.let { + require(creationTime <= it) { "Key MUST NOT expire before being created.\n" + - "(creation: $creationTime, expiration: $it)" - }.run { - (it.time - creationTime.time) / 1000 + "(creation: $creationTime, expiration: $it)" } - } ?: 0 // 0 means "no expiration" + .run { (it.time - creationTime.time) / 1000 } + } + ?: 0 // 0 means "no expiration" /** - * Return the revocable subpacket of this signature. - * We only look for it in the hashed area of the signature. + * Return the revocable subpacket of this signature. We only look for it in the hashed area + * of the signature. * * @param signature signature * @return revocable subpacket */ @JvmStatic fun getRevocable(signature: PGPSignature): Revocable? = - hashed(signature, SignatureSubpacket.revocable) + hashed(signature, SignatureSubpacket.revocable) /** * Return the symmetric algorithm preferences from the signatures hashed area. @@ -188,23 +187,28 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getPreferredSymmetricAlgorithms(signature: PGPSignature): PreferredAlgorithms? = - hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms) + hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms) /** - * Return the preferred [SymmetricKeyAlgorithms][SymmetricKeyAlgorithm] as present in the signature. - * If no preference is given with regard to symmetric encryption algorithms, return an empty set. + * Return the preferred [SymmetricKeyAlgorithms][SymmetricKeyAlgorithm] as present in the + * signature. If no preference is given with regard to symmetric encryption algorithms, + * return an empty set. * * In any case, the resulting set is ordered by occurrence. + * * @param signature signature * @return ordered set of symmetric key algorithm preferences */ @JvmStatic - fun parsePreferredSymmetricKeyAlgorithms(signature: PGPSignature): Set = - getPreferredSymmetricAlgorithms(signature) - ?.preferences - ?.map { SymmetricKeyAlgorithm.fromId(it) } - ?.filterNotNull() - ?.toSet() ?: setOf() + fun parsePreferredSymmetricKeyAlgorithms( + signature: PGPSignature + ): Set = + getPreferredSymmetricAlgorithms(signature) + ?.preferences + ?.map { SymmetricKeyAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() + ?: setOf() /** * Return the hash algorithm preferences from the signatures hashed area. @@ -214,23 +218,25 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getPreferredHashAlgorithms(signature: PGPSignature): PreferredAlgorithms? = - hashed(signature, SignatureSubpacket.preferredHashAlgorithms) + hashed(signature, SignatureSubpacket.preferredHashAlgorithms) /** - * Return the preferred [HashAlgorithms][HashAlgorithm] as present in the signature. - * If no preference is given with regard to hash algorithms, return an empty set. + * Return the preferred [HashAlgorithms][HashAlgorithm] as present in the signature. If no + * preference is given with regard to hash algorithms, return an empty set. * * In any case, the resulting set is ordered by occurrence. + * * @param signature signature * @return ordered set of hash algorithm preferences */ @JvmStatic fun parsePreferredHashAlgorithms(signature: PGPSignature): Set = - getPreferredHashAlgorithms(signature) - ?.preferences - ?.map { HashAlgorithm.fromId(it) } - ?.filterNotNull() - ?.toSet() ?: setOf() + getPreferredHashAlgorithms(signature) + ?.preferences + ?.map { HashAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() + ?: setOf() /** * Return the compression algorithm preferences from the signatures hashed area. @@ -240,27 +246,32 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getPreferredCompressionAlgorithms(signature: PGPSignature): PreferredAlgorithms? = - hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms) + hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms) /** - * Return the preferred [CompressionAlgorithms][CompressionAlgorithm] as present in the signature. - * If no preference is given with regard to compression algorithms, return an empty set. + * Return the preferred [CompressionAlgorithms][CompressionAlgorithm] as present in the + * signature. If no preference is given with regard to compression algorithms, return an + * empty set. * * In any case, the resulting set is ordered by occurrence. + * * @param signature signature * @return ordered set of compression algorithm preferences */ @JvmStatic - fun parsePreferredCompressionAlgorithms(signature: PGPSignature): Set = - getPreferredCompressionAlgorithms(signature) - ?.preferences - ?.map { CompressionAlgorithm.fromId(it) } - ?.filterNotNull() - ?.toSet() ?: setOf() + fun parsePreferredCompressionAlgorithms( + signature: PGPSignature + ): Set = + getPreferredCompressionAlgorithms(signature) + ?.preferences + ?.map { CompressionAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() + ?: setOf() @JvmStatic fun getPreferredAeadAlgorithms(signature: PGPSignature): PreferredAEADCiphersuites? = - hashed(signature, SignatureSubpacket.preferredAEADAlgorithms) + hashed(signature, SignatureSubpacket.preferredAEADAlgorithms) /** * Return the primary user-id subpacket from the signatures hashed area. @@ -270,7 +281,7 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getPrimaryUserId(signature: PGPSignature): PrimaryUserID? = - hashed(signature, SignatureSubpacket.primaryUserId) + hashed(signature, SignatureSubpacket.primaryUserId) /** * Return the key flags subpacket from the signatures hashed area. @@ -280,22 +291,18 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getKeyFlags(signature: PGPSignature): KeyFlags? = - hashed(signature, SignatureSubpacket.keyFlags) + hashed(signature, SignatureSubpacket.keyFlags) /** - * Return a list of key flags carried by the signature. - * If the signature is null, or has no [KeyFlags] subpacket, return null. + * Return a list of key flags carried by the signature. If the signature is null, or has no + * [KeyFlags] subpacket, return null. * * @param signature signature * @return list of key flags */ @JvmStatic fun parseKeyFlags(signature: PGPSignature?): List? = - signature?.let { sig -> - getKeyFlags(sig)?.let { - KeyFlag.fromBitmask(it.flags) - } - } + signature?.let { sig -> getKeyFlags(sig)?.let { KeyFlag.fromBitmask(it.flags) } } /** * Return the features subpacket from the signatures hashed area. @@ -305,32 +312,29 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getFeatures(signature: PGPSignature): Features? = - hashed(signature, SignatureSubpacket.features) + hashed(signature, SignatureSubpacket.features) /** - * Parse out the features subpacket of a signature. - * If the signature has no features subpacket, return null. - * Otherwise, return the features as a feature set. + * Parse out the features subpacket of a signature. If the signature has no features + * subpacket, return null. Otherwise, return the features as a feature set. * * @param signature signature * @return features as set */ @JvmStatic fun parseFeatures(signature: PGPSignature): Set? = - getFeatures(signature)?.let { - Feature.fromBitmask(it.features.toInt()).toSet() - } + getFeatures(signature)?.let { Feature.fromBitmask(it.features.toInt()).toSet() } /** - * Return the signature target subpacket from the signature. - * We search for this subpacket in the hashed and unhashed area (in this order). + * Return the signature target subpacket from the signature. We search for this subpacket in + * the hashed and unhashed area (in this order). * * @param signature signature * @return signature target */ @JvmStatic fun getSignatureTarget(signature: PGPSignature): SignatureTarget? = - hashedOrUnhashed(signature, SignatureSubpacket.signatureTarget) + hashedOrUnhashed(signature, SignatureSubpacket.signatureTarget) /** * Return the notation data subpackets from the signatures hashed area. @@ -340,20 +344,22 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getHashedNotationData(signature: PGPSignature): List = - signature.hashedSubPackets.notationDataOccurrences.toList() + signature.hashedSubPackets.notationDataOccurrences.toList() /** - * Return a list of all [NotationData] objects from the hashed area of the signature that have a - * notation name equal to the given notationName argument. + * Return a list of all [NotationData] objects from the hashed area of the signature that + * have a notation name equal to the given notationName argument. * * @param signature signature * @param notationName notation name * @return list of matching notation data objects */ @JvmStatic - fun getHashedNotationData(signature: PGPSignature, notationName: String): List = - getHashedNotationData(signature) - .filter { it.notationName == notationName } + fun getHashedNotationData( + signature: PGPSignature, + notationName: String + ): List = + getHashedNotationData(signature).filter { it.notationName == notationName } /** * Return the notation data subpackets from the signatures unhashed area. @@ -363,11 +369,11 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getUnhashedNotationData(signature: PGPSignature): List = - signature.unhashedSubPackets.notationDataOccurrences.toList() + signature.unhashedSubPackets.notationDataOccurrences.toList() /** - * Return a list of all [NotationData] objects from the unhashed area of the signature that have a - * notation name equal to the given notationName argument. + * Return a list of all [NotationData] objects from the unhashed area of the signature that + * have a notation name equal to the given notationName argument. * * @param signature signature * @param notationName notation name @@ -375,8 +381,7 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getUnhashedNotationData(signature: PGPSignature, notationName: String) = - getUnhashedNotationData(signature) - .filter { it.notationName == notationName } + getUnhashedNotationData(signature).filter { it.notationName == notationName } /** * Return the revocation key subpacket from the signatures hashed area. @@ -386,28 +391,32 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getRevocationKey(signature: PGPSignature): RevocationKey? = - hashed(signature, SignatureSubpacket.revocationKey) + hashed(signature, SignatureSubpacket.revocationKey) /** * Return the signers user-id from the hashed area of the signature. - * TODO: Can this subpacket also be found in the unhashed area? * * @param signature signature * @return signers user-id + * + * TODO: Can this subpacket also be found in the unhashed area? */ @JvmStatic fun getSignerUserID(signature: PGPSignature): SignerUserID? = - hashed(signature, SignatureSubpacket.signerUserId) + hashed(signature, SignatureSubpacket.signerUserId) /** - * Return the intended recipients fingerprint subpackets from the hashed area of this signature. + * Return the intended recipients fingerprint subpackets from the hashed area of this + * signature. * * @param signature signature * @return intended recipient fingerprint subpackets */ @JvmStatic - fun getIntendedRecipientFingerprints(signature: PGPSignature): List = - signature.hashedSubPackets.intendedRecipientFingerprints.toList() + fun getIntendedRecipientFingerprints( + signature: PGPSignature + ): List = + signature.hashedSubPackets.intendedRecipientFingerprints.toList() /** * Return the embedded signature subpacket from the signatures hashed area or unhashed area. @@ -417,10 +426,9 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getEmbeddedSignature(signature: PGPSignature): PGPSignatureList = - signature.hashedSubPackets.embeddedSignatures.let { - if (it.isEmpty) signature.unhashedSubPackets.embeddedSignatures - else it - } + signature.hashedSubPackets.embeddedSignatures.let { + if (it.isEmpty) signature.unhashedSubPackets.embeddedSignatures else it + } /** * Return the signatures exportable certification subpacket from the hashed area. @@ -430,14 +438,12 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getExportableCertification(signature: PGPSignature): Exportable? = - hashed(signature, SignatureSubpacket.exportableCertification) + hashed(signature, SignatureSubpacket.exportableCertification) - /** - * Return true, if the signature is not explicitly marked as non-exportable. - */ + /** Return true, if the signature is not explicitly marked as non-exportable. */ @JvmStatic fun isExportable(signature: PGPSignature): Boolean = - getExportableCertification(signature)?.isExportable ?: true + getExportableCertification(signature)?.isExportable ?: true /** * Return the trust signature packet from the signatures hashed area. @@ -447,11 +453,11 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getTrustSignature(signature: PGPSignature): TrustSignature? = - hashed(signature, SignatureSubpacket.trustSignature) + hashed(signature, SignatureSubpacket.trustSignature) /** - * Return the trust depth set in the signatures [TrustSignature] packet, or [defaultDepth] if no such packet - * is found. + * Return the trust depth set in the signatures [TrustSignature] packet, or [defaultDepth] + * if no such packet is found. * * @param signature signature * @param defaultDepth default value that is returned if no trust signature packet is found @@ -459,11 +465,11 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getTrustDepthOr(signature: PGPSignature, defaultDepth: Int): Int = - getTrustSignature(signature)?.depth ?: defaultDepth + getTrustSignature(signature)?.depth ?: defaultDepth /** - * Return the trust amount set in the signatures [TrustSignature] packet, or [defaultAmount] if no such packet - * is found. + * Return the trust amount set in the signatures [TrustSignature] packet, or [defaultAmount] + * if no such packet is found. * * @param signature signature * @param defaultAmount default value that is returned if no trust signature packet is found @@ -471,7 +477,7 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getTrustAmountOr(signature: PGPSignature, defaultAmount: Int): Int = - getTrustSignature(signature)?.trustAmount ?: defaultAmount + getTrustSignature(signature)?.trustAmount ?: defaultAmount /** * Return all regular expression subpackets from the hashed area of the given signature. @@ -481,11 +487,11 @@ class SignatureSubpacketsUtil { */ @JvmStatic fun getRegularExpressions(signature: PGPSignature): List = - signature.hashedSubPackets.regularExpressions.toList() + signature.hashedSubPackets.regularExpressions.toList() /** - * Select a list of all signature subpackets of the given type, which are present in either the hashed - * or the unhashed area of the given signature. + * Select a list of all signature subpackets of the given type, which are present in either + * the hashed or the unhashed area of the given signature. * * @param signature signature * @param type subpacket type @@ -493,13 +499,16 @@ class SignatureSubpacketsUtil { * @return list of subpackets from the hashed/unhashed area */ @JvmStatic - fun

hashedOrUnhashed(signature: PGPSignature, type: SignatureSubpacket): P? { + fun

hashedOrUnhashed( + signature: PGPSignature, + type: SignatureSubpacket + ): P? { return hashed(signature, type) ?: unhashed(signature, type) } /** - * Select a list of all signature subpackets of the given type, which are present in the hashed area of - * the given signature. + * Select a list of all signature subpackets of the given type, which are present in the + * hashed area of the given signature. * * @param signature signature * @param type subpacket type @@ -507,13 +516,16 @@ class SignatureSubpacketsUtil { * @return list of subpackets from the hashed area */ @JvmStatic - fun

hashed(signature: PGPSignature, type: SignatureSubpacket): P? { + fun

hashed( + signature: PGPSignature, + type: SignatureSubpacket + ): P? { return getSignatureSubpacket(signature.hashedSubPackets, type) } /** - * Select a list of all signature subpackets of the given type, which are present in the unhashed area of - * the given signature. + * Select a list of all signature subpackets of the given type, which are present in the + * unhashed area of the given signature. * * @param signature signature * @param type subpacket type @@ -521,7 +533,10 @@ class SignatureSubpacketsUtil { * @return list of subpackets from the unhashed area */ @JvmStatic - fun

unhashed(signature: PGPSignature, type: SignatureSubpacket): P? { + fun

unhashed( + signature: PGPSignature, + type: SignatureSubpacket + ): P? { return getSignatureSubpacket(signature.unhashedSubPackets, type) } @@ -534,13 +549,13 @@ class SignatureSubpacketsUtil { * @return last occurrence of the subpacket in the vector */ @JvmStatic - fun

getSignatureSubpacket(vector: PGPSignatureSubpacketVector?, type: SignatureSubpacket): P? { + fun

getSignatureSubpacket( + vector: PGPSignatureSubpacketVector?, + type: SignatureSubpacket + ): P? { val allPackets = vector?.getSubpackets(type.code) ?: return null - return if (allPackets.isEmpty()) - null - else - @Suppress("UNCHECKED_CAST") - allPackets.last() as P + return if (allPackets.isEmpty()) null + else @Suppress("UNCHECKED_CAST") allPackets.last() as P } @JvmStatic @@ -553,24 +568,29 @@ class SignatureSubpacketsUtil { val mask = toBitmask(*flags) if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) { - throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag CERTIFY_OTHER.") + throw IllegalArgumentException( + "Algorithm $algorithm cannot be used with key flag CERTIFY_OTHER.") } if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.SIGN_DATA)) { - throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag SIGN_DATA.") + throw IllegalArgumentException( + "Algorithm $algorithm cannot be used with key flag SIGN_DATA.") } if (!algorithm.isEncryptionCapable() && hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) { - throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag ENCRYPT_COMMS.") + throw IllegalArgumentException( + "Algorithm $algorithm cannot be used with key flag ENCRYPT_COMMS.") } if (!algorithm.isEncryptionCapable() && hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) { - throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag ENCRYPT_STORAGE.") + throw IllegalArgumentException( + "Algorithm $algorithm cannot be used with key flag ENCRYPT_STORAGE.") } if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) { - throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag AUTHENTICATION.") + throw IllegalArgumentException( + "Algorithm $algorithm cannot be used with key flag AUTHENTICATION.") } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt index 53bdca0b..70146e0f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt @@ -4,6 +4,10 @@ package org.pgpainless.util +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream import org.bouncycastle.bcpg.ArmoredInputStream import org.bouncycastle.bcpg.ArmoredOutputStream import org.bouncycastle.openpgp.PGPKeyRing @@ -20,35 +24,21 @@ import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.decryption_verification.OpenPgpInputStream import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.util.KeyRingUtils -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream class ArmorUtils { companion object { // MessageIDs are 32 printable characters private val PATTER_MESSAGE_ID = "^\\S{32}$".toRegex() - /** - * Constant armor key for comments. - */ + /** Constant armor key for comments. */ const val HEADER_COMMENT = "Comment" - /** - * Constant armor key for program versions. - */ + /** Constant armor key for program versions. */ const val HEADER_VERSION = "Version" - /** - * Constant armor key for message IDs. Useful for split messages. - */ + /** Constant armor key for message IDs. Useful for split messages. */ const val HEADER_MESSAGEID = "MessageID" - /** - * Constant armor key for used hash algorithms in clearsigned messages. - */ + /** Constant armor key for used hash algorithms in clearsigned messages. */ const val HEADER_HASH = "Hash" - /** - * Constant armor key for message character sets. - */ + /** Constant armor key for message character sets. */ const val HEADER_CHARSET = "Charset" /** @@ -56,116 +46,111 @@ class ArmorUtils { * * @param secretKey secret key * @return ASCII armored encoding - * * @throws IOException in case of an io error */ @JvmStatic @Throws(IOException::class) fun toAsciiArmoredString(secretKey: PGPSecretKey): String = - toAsciiArmoredString(secretKey.encoded, keyToHeader(secretKey.publicKey)) + toAsciiArmoredString(secretKey.encoded, keyToHeader(secretKey.publicKey)) /** * Return the ASCII armored encoding of the given [PGPPublicKey]. * * @param publicKey public key * @return ASCII armored encoding - * * @throws IOException in case of an io error */ @JvmStatic @Throws(IOException::class) fun toAsciiArmoredString(publicKey: PGPPublicKey): String = - toAsciiArmoredString(publicKey.encoded, keyToHeader(publicKey)) + toAsciiArmoredString(publicKey.encoded, keyToHeader(publicKey)) /** * Return the ASCII armored encoding of the given [PGPSecretKeyRing]. * * @param secretKeys secret key ring * @return ASCII armored encoding - * * @throws IOException in case of an io error */ @JvmStatic @Throws(IOException::class) fun toAsciiArmoredString(secretKeys: PGPSecretKeyRing): String = - toAsciiArmoredString(secretKeys.encoded, keyToHeader(secretKeys.publicKey)) + toAsciiArmoredString(secretKeys.encoded, keyToHeader(secretKeys.publicKey)) /** * Return the ASCII armored encoding of the given [PGPPublicKeyRing]. * * @param certificate public key ring * @return ASCII armored encoding - * * @throws IOException in case of an io error */ @JvmStatic @Throws(IOException::class) fun toAsciiArmoredString(certificate: PGPPublicKeyRing): String = - toAsciiArmoredString(certificate.encoded, keyToHeader(certificate.publicKey)) + toAsciiArmoredString(certificate.encoded, keyToHeader(certificate.publicKey)) /** - * Return the ASCII armored encoding of the given [PGPSecretKeyRingCollection]. - * The encoding will use per-key ASCII armors protecting each [PGPSecretKeyRing] individually. - * Those armors are then concatenated with newlines in between. + * Return the ASCII armored encoding of the given [PGPSecretKeyRingCollection]. The encoding + * will use per-key ASCII armors protecting each [PGPSecretKeyRing] individually. Those + * armors are then concatenated with newlines in between. * * @param secretKeysCollection secret key ring collection * @return ASCII armored encoding - * * @throws IOException in case of an io error */ @JvmStatic @Throws(IOException::class) fun toAsciiArmoredString(secretKeysCollection: PGPSecretKeyRingCollection): String = - secretKeysCollection.keyRings.asSequence() - .joinToString("\n") { toAsciiArmoredString(it) } + secretKeysCollection.keyRings.asSequence().joinToString("\n") { + toAsciiArmoredString(it) + } /** - * Return the ASCII armored encoding of the given [PGPPublicKeyRingCollection]. - * The encoding will use per-key ASCII armors protecting each [PGPPublicKeyRing] individually. - * Those armors are then concatenated with newlines in between. + * Return the ASCII armored encoding of the given [PGPPublicKeyRingCollection]. The encoding + * will use per-key ASCII armors protecting each [PGPPublicKeyRing] individually. Those + * armors are then concatenated with newlines in between. * * @param certificates public key ring collection * @return ascii armored encoding - * * @throws IOException in case of an io error */ @JvmStatic @Throws(IOException::class) fun toAsciiArmoredString(certificates: PGPPublicKeyRingCollection): String = - certificates.joinToString("\n") { toAsciiArmoredString(it) } + certificates.joinToString("\n") { toAsciiArmoredString(it) } /** - * Return the ASCII armored representation of the given detached signature. - * If [export] is true, the signature will be stripped of non-exportable subpackets or trust-packets. - * If it is false, the signature will be encoded as-is. + * Return the ASCII armored representation of the given detached signature. If [export] is + * true, the signature will be stripped of non-exportable subpackets or trust-packets. If it + * is false, the signature will be encoded as-is. * * @param signature signature * @param export whether to exclude non-exportable subpackets or trust-packets. * @return ascii armored string - * * @throws IOException in case of an error in the [ArmoredOutputStream] */ @JvmStatic @JvmOverloads @Throws(IOException::class) fun toAsciiArmoredString(signature: PGPSignature, export: Boolean = false): String = - toAsciiArmoredString(signature.getEncoded(export)) + toAsciiArmoredString(signature.getEncoded(export)) /** - * Return the ASCII armored encoding of the given OpenPGP data bytes. - * The ASCII armor will include headers from the header map. + * Return the ASCII armored encoding of the given OpenPGP data bytes. The ASCII armor will + * include headers from the header map. * * @param bytes OpenPGP data * @param header header map * @return ASCII armored encoding - * * @throws IOException in case of an io error */ @JvmStatic @JvmOverloads @Throws(IOException::class) - fun toAsciiArmoredString(bytes: ByteArray, header: Map>? = null): String = - toAsciiArmoredString(bytes.inputStream(), header) + fun toAsciiArmoredString( + bytes: ByteArray, + header: Map>? = null + ): String = toAsciiArmoredString(bytes.inputStream(), header) /** * Return the ASCII armored encoding of the OpenPGP data from the given {@link InputStream}. @@ -174,26 +159,30 @@ class ArmorUtils { * @param inputStream input stream of OpenPGP data * @param header ASCII armor header map * @return ASCII armored encoding - * * @throws IOException in case of an io error */ @JvmStatic @JvmOverloads @Throws(IOException::class) - fun toAsciiArmoredString(inputStream: InputStream, header: Map>? = null): String = - ByteArrayOutputStream().apply { + fun toAsciiArmoredString( + inputStream: InputStream, + header: Map>? = null + ): String = + ByteArrayOutputStream() + .apply { toAsciiArmoredStream(this, header).run { Streams.pipeAll(inputStream, this) this.close() } - }.toString() + } + .toString() /** - * Return an [ArmoredOutputStream] prepared with headers for the given key ring, which wraps the given - * {@link OutputStream}. + * Return an [ArmoredOutputStream] prepared with headers for the given key ring, which wraps + * the given {@link OutputStream}. * - * The armored output stream can be used to encode the key ring by calling [PGPKeyRing.encode] - * with the armored output stream as an argument. + * The armored output stream can be used to encode the key ring by calling + * [PGPKeyRing.encode] with the armored output stream as an argument. * * @param keys OpenPGP key or certificate * @param outputStream wrapped output stream @@ -201,16 +190,18 @@ class ArmorUtils { */ @JvmStatic @Throws(IOException::class) - fun toAsciiArmoredStream(keys: PGPKeyRing, outputStream: OutputStream): ArmoredOutputStream = - toAsciiArmoredStream(outputStream, keyToHeader(keys.publicKey)) + fun toAsciiArmoredStream( + keys: PGPKeyRing, + outputStream: OutputStream + ): ArmoredOutputStream = toAsciiArmoredStream(outputStream, keyToHeader(keys.publicKey)) /** - * Create an [ArmoredOutputStream] wrapping the given [OutputStream]. - * The armored output stream will be prepared with armor headers given by header. + * Create an [ArmoredOutputStream] wrapping the given [OutputStream]. The armored output + * stream will be prepared with armor headers given by header. * * Note: Since the armored output stream is retrieved from [ArmoredOutputStreamFactory.get], - * it may already come with custom headers. Hence, the header entries given by header are appended below those - * already populated headers. + * it may already come with custom headers. Hence, the header entries given by header are + * appended below those already populated headers. * * @param outputStream output stream to wrap * @param header map of header entries @@ -219,19 +210,20 @@ class ArmorUtils { @JvmStatic @JvmOverloads @Throws(IOException::class) - fun toAsciiArmoredStream(outputStream: OutputStream, header: Map>? = null): ArmoredOutputStream = - ArmoredOutputStreamFactory.get(outputStream).apply { - header?.forEach { entry -> - entry.value.forEach { value -> - addHeader(entry.key, value) - } - } + fun toAsciiArmoredStream( + outputStream: OutputStream, + header: Map>? = null + ): ArmoredOutputStream = + ArmoredOutputStreamFactory.get(outputStream).apply { + header?.forEach { entry -> + entry.value.forEach { value -> addHeader(entry.key, value) } } + } /** - * Generate a header map for ASCII armor from the given [PGPPublicKey]. - * The header map consists of a comment field of the keys pretty-printed fingerprint, - * as well as the primary or first user-id plus the count of remaining user-ids. + * Generate a header map for ASCII armor from the given [PGPPublicKey]. The header map + * consists of a comment field of the keys pretty-printed fingerprint, as well as the + * primary or first user-id plus the count of remaining user-ids. * * @param publicKey public key * @return header map @@ -241,124 +233,142 @@ class ArmorUtils { val headerMap = mutableMapOf>() val userIds = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey) val first: String? = userIds.firstOrNull() - val primary: String? = userIds.firstOrNull { - publicKey.getSignaturesForID(it)?.asSequence()?.any { sig -> - sig.hashedSubPackets.isPrimaryUserID - } ?: false - } + val primary: String? = + userIds.firstOrNull { + publicKey.getSignaturesForID(it)?.asSequence()?.any { sig -> + sig.hashedSubPackets.isPrimaryUserID + } + ?: false + } // Fingerprint - headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add(OpenPgpFingerprint.of(publicKey).prettyPrint()) + headerMap + .getOrPut(HEADER_COMMENT) { mutableSetOf() } + .add(OpenPgpFingerprint.of(publicKey).prettyPrint()) // Primary / First User ID - (primary ?: first)?.let { headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add(it) } + (primary ?: first)?.let { + headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add(it) + } // X-1 further identities when (userIds.size) { - 0, 1 -> {} + 0, + 1 -> {} 2 -> headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add("1 further identity") - else -> headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() }.add("${userIds.size - 1} further identities") + else -> + headerMap + .getOrPut(HEADER_COMMENT) { mutableSetOf() } + .add("${userIds.size - 1} further identities") } return headerMap } /** - * Set the version header entry in the ASCII armor. - * If the version info is null or only contains whitespace characters, then the version header will be removed. + * Set the version header entry in the ASCII armor. If the version info is null or only + * contains whitespace characters, then the version header will be removed. * * @param armor armored output stream * @param version version header. */ @JvmStatic - @Deprecated("Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + + @Deprecated( + "Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + "Use ArmoredOutputStream builder instead.") fun setVersionHeader(armor: ArmoredOutputStream, version: String?) = - armor.setHeader(HEADER_VERSION, version?.let { it.ifBlank { null } }) + armor.setHeader(HEADER_VERSION, version?.let { it.ifBlank { null } }) /** - * Add an ASCII armor header entry about the used hash algorithm into the [ArmoredOutputStream]. + * Add an ASCII armor header entry about the used hash algorithm into the + * [ArmoredOutputStream]. * * @param armor armored output stream * @param hashAlgorithm hash algorithm - * - * @see - * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor + * @see RFC 4880 - + * OpenPGP Message Format §6.2. Forming ASCII Armor */ @JvmStatic - @Deprecated("Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + + @Deprecated( + "Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + "Use ArmoredOutputStream builder instead.") fun addHashAlgorithmHeader(armor: ArmoredOutputStream, hashAlgorithm: HashAlgorithm) = - armor.addHeader(HEADER_HASH, hashAlgorithm.algorithmName) + armor.addHeader(HEADER_HASH, hashAlgorithm.algorithmName) /** * Add an ASCII armor comment header entry into the [ArmoredOutputStream]. * * @param armor armored output stream * @param comment free-text comment - * - * @see - * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor + * @see RFC 4880 - + * OpenPGP Message Format §6.2. Forming ASCII Armor */ @JvmStatic - @Deprecated("Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + + @Deprecated( + "Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + "Use ArmoredOutputStream builder instead.") fun addCommentHeader(armor: ArmoredOutputStream, comment: String) = - armor.addHeader(HEADER_COMMENT, comment) + armor.addHeader(HEADER_COMMENT, comment) /** * Add an ASCII armor message-id header entry into the [ArmoredOutputStream]. * * @param armor armored output stream * @param messageId message id - * - * @see - * RFC 4880 - OpenPGP Message Format §6.2. Forming ASCII Armor + * @see RFC 4880 - + * OpenPGP Message Format §6.2. Forming ASCII Armor */ @JvmStatic - @Deprecated("Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + + @Deprecated( + "Changing ASCII armor headers after ArmoredOutputStream creation is deprecated. " + "Use ArmoredOutputStream builder instead.") fun addMessageIdHeader(armor: ArmoredOutputStream, messageId: String) { - require(PATTER_MESSAGE_ID.matches(messageId)) { "MessageIDs MUST consist of 32 printable characters." } + require(PATTER_MESSAGE_ID.matches(messageId)) { + "MessageIDs MUST consist of 32 printable characters." + } armor.addHeader(HEADER_MESSAGEID, messageId) } /** - * Extract all ASCII armor header values of type comment from the given [ArmoredInputStream]. + * Extract all ASCII armor header values of type comment from the given + * [ArmoredInputStream]. * * @param armor armored input stream * @return list of comment headers */ @JvmStatic fun getCommentHeaderValues(armor: ArmoredInputStream): List = - getArmorHeaderValues(armor, HEADER_COMMENT) + getArmorHeaderValues(armor, HEADER_COMMENT) /** - * Extract all ASCII armor header values of type message id from the given [ArmoredInputStream]. + * Extract all ASCII armor header values of type message id from the given + * [ArmoredInputStream]. * * @param armor armored input stream * @return list of message-id headers */ @JvmStatic fun getMessageIdHeaderValues(armor: ArmoredInputStream): List = - getArmorHeaderValues(armor, HEADER_MESSAGEID) + getArmorHeaderValues(armor, HEADER_MESSAGEID) /** - * Return all ASCII armor header values of type hash-algorithm from the given [ArmoredInputStream]. + * Return all ASCII armor header values of type hash-algorithm from the given + * [ArmoredInputStream]. * * @param armor armored input stream * @return list of hash headers */ @JvmStatic fun getHashHeaderValues(armor: ArmoredInputStream): List = - getArmorHeaderValues(armor, HEADER_HASH) + getArmorHeaderValues(armor, HEADER_HASH) /** - * Return a list of [HashAlgorithm] enums extracted from the hash header entries of the given [ArmoredInputStream]. + * Return a list of [HashAlgorithm] enums extracted from the hash header entries of the + * given [ArmoredInputStream]. * * @param armor armored input stream * @return list of hash algorithms from the ASCII header */ @JvmStatic fun getHashAlgorithms(armor: ArmoredInputStream): List = - getHashHeaderValues(armor).mapNotNull { HashAlgorithm.fromName(it) } + getHashHeaderValues(armor).mapNotNull { HashAlgorithm.fromName(it) } /** * Return all ASCII armor header values of type version from the given [ArmoredInputStream]. @@ -368,7 +378,7 @@ class ArmorUtils { */ @JvmStatic fun getVersionHeaderValues(armor: ArmoredInputStream): List = - getArmorHeaderValues(armor, HEADER_VERSION) + getArmorHeaderValues(armor, HEADER_VERSION) /** * Return all ASCII armor header values of type charset from the given [ArmoredInputStream]. @@ -378,10 +388,11 @@ class ArmorUtils { */ @JvmStatic fun getCharsetHeaderValues(armor: ArmoredInputStream): List = - getArmorHeaderValues(armor, HEADER_CHARSET) + getArmorHeaderValues(armor, HEADER_CHARSET) /** - * Return all ASCII armor header values of the given headerKey from the given [ArmoredInputStream]. + * Return all ASCII armor header values of the given headerKey from the given + * [ArmoredInputStream]. * * @param armor armored input stream * @param key ASCII armor header key @@ -389,34 +400,33 @@ class ArmorUtils { */ @JvmStatic fun getArmorHeaderValues(armor: ArmoredInputStream, key: String): List = - armor.armorHeaders - .filter { it.startsWith("$key: ") } - .map { it.substring(key.length + 2) } // key.len + ": ".len + armor.armorHeaders + .filter { it.startsWith("$key: ") } + .map { it.substring(key.length + 2) } // key.len + ": ".len /** - * Hacky workaround for #96. - * For `PGPPublicKeyRingCollection(InputStream, KeyFingerPrintCalculator)` - * or `PGPSecretKeyRingCollection(InputStream, KeyFingerPrintCalculator)` - * to read all PGPKeyRings properly, we apparently have to make sure that the [InputStream] that is given - * as constructor argument is a [PGPUtil.BufferedInputStreamExt]. - * Since [PGPUtil.getDecoderStream] will return an [org.bouncycastle.bcpg.ArmoredInputStream] - * if the underlying input stream contains armored data, we first dearmor the data ourselves to make sure that the - * end-result is a [PGPUtil.BufferedInputStreamExt]. + * Hacky workaround for #96. For `PGPPublicKeyRingCollection(InputStream, + * KeyFingerPrintCalculator)` or `PGPSecretKeyRingCollection(InputStream, + * KeyFingerPrintCalculator)` to read all PGPKeyRings properly, we apparently have to make + * sure that the [InputStream] that is given as constructor argument is a + * [PGPUtil.BufferedInputStreamExt]. Since [PGPUtil.getDecoderStream] will return an + * [org.bouncycastle.bcpg.ArmoredInputStream] if the underlying input stream contains + * armored data, we first dearmor the data ourselves to make sure that the end-result is a + * [PGPUtil.BufferedInputStreamExt]. * * @param inputStream input stream * @return BufferedInputStreamExt - * * @throws IOException in case of an IO error */ @JvmStatic @Throws(IOException::class) fun getDecoderStream(inputStream: InputStream): InputStream = - OpenPgpInputStream(inputStream).let { - if (it.isAsciiArmored) { - PGPUtil.getDecoderStream(ArmoredInputStreamFactory.get(it)) - } else { - it - } + OpenPgpInputStream(inputStream).let { + if (it.isAsciiArmored) { + PGPUtil.getDecoderStream(ArmoredInputStreamFactory.get(it)) + } else { + it } + } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt index 254b5c88..8198214d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredInputStreamFactory.kt @@ -4,14 +4,14 @@ package org.pgpainless.util -import org.bouncycastle.bcpg.ArmoredInputStream -import org.pgpainless.decryption_verification.ConsumerOptions import java.io.IOException import java.io.InputStream +import org.bouncycastle.bcpg.ArmoredInputStream +import org.pgpainless.decryption_verification.ConsumerOptions /** - * Factory class for instantiating preconfigured [ArmoredInputStream] instances. - * [get] will return an [ArmoredInputStream] that is set up to properly detect CRC errors v4 style. + * Factory class for instantiating preconfigured [ArmoredInputStream] instances. [get] will return + * an [ArmoredInputStream] that is set up to properly detect CRC errors v4 style. */ class ArmoredInputStreamFactory { @@ -31,14 +31,15 @@ class ArmoredInputStreamFactory { return when (inputStream) { is CRCingArmoredInputStreamWrapper -> inputStream is ArmoredInputStream -> CRCingArmoredInputStreamWrapper(inputStream) - else -> CRCingArmoredInputStreamWrapper( - ArmoredInputStream.builder().apply { - setParseForHeaders(true) - options?.let { - setIgnoreCRC(it.isDisableAsciiArmorCRC) + else -> + CRCingArmoredInputStreamWrapper( + ArmoredInputStream.builder() + .apply { + setParseForHeaders(true) + options?.let { setIgnoreCRC(it.isDisableAsciiArmorCRC) } } - }.build(inputStream)) + .build(inputStream)) } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt index 69a5520f..caf14e53 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmoredOutputStreamFactory.kt @@ -4,25 +4,25 @@ package org.pgpainless.util +import java.io.OutputStream import org.bouncycastle.bcpg.ArmoredOutputStream import org.pgpainless.encryption_signing.ProducerOptions -import java.io.OutputStream /** - * Factory to create configured [ArmoredOutputStream] instances. - * The configuration entails setting custom version and comment headers. + * Factory to create configured [ArmoredOutputStream] instances. The configuration entails setting + * custom version and comment headers. */ class ArmoredOutputStreamFactory { companion object { private const val PGPAINLESS = "PGPainless" - @JvmStatic - private var version: String? = PGPAINLESS + @JvmStatic private var version: String? = PGPAINLESS private var comment: String? = null /** - * Return an instance of the [ArmoredOutputStream] which might have pre-populated armor headers. + * Return an instance of the [ArmoredOutputStream] which might have pre-populated armor + * headers. * * @param outputStream output stream * @param options options @@ -31,39 +31,43 @@ class ArmoredOutputStreamFactory { @JvmStatic @JvmOverloads fun get(outputStream: OutputStream, options: ProducerOptions? = null): ArmoredOutputStream { - val builder = ArmoredOutputStream.builder().apply { - // set fields defined in ArmoredOutputStreamFactory - if (!version.isNullOrBlank()) setVersion(version) - if (!comment.isNullOrBlank()) setComment(comment) + val builder = + ArmoredOutputStream.builder().apply { + // set fields defined in ArmoredOutputStreamFactory + if (!version.isNullOrBlank()) setVersion(version) + if (!comment.isNullOrBlank()) setComment(comment) - // set (and potentially overwrite with) values from ProducerOptions - options?.let { - enableCRC(!it.isDisableAsciiArmorCRC) - if (it.isHideArmorHeaders) clearHeaders() - if (it.hasVersion()) setVersion(it.version) - if (it.hasComment()) addComment(it.comment) - // TODO: configure CRC + // set (and potentially overwrite with) values from ProducerOptions + options?.let { + enableCRC(!it.isDisableAsciiArmorCRC) + if (it.isHideArmorHeaders) clearHeaders() + if (it.hasVersion()) setVersion(it.version) + if (it.hasComment()) addComment(it.comment) + // TODO: configure CRC + } } - } return get(outputStream, builder) } /** - * Build an [ArmoredOutputStream] around the given [outputStream], configured according to the passed in - * [ArmoredOutputStream.Builder] instance. + * Build an [ArmoredOutputStream] around the given [outputStream], configured according to + * the passed in [ArmoredOutputStream.Builder] instance. * * @param outputStream output stream * @param builder builder instance */ @JvmStatic - fun get(outputStream: OutputStream, builder: ArmoredOutputStream.Builder): ArmoredOutputStream { + fun get( + outputStream: OutputStream, + builder: ArmoredOutputStream.Builder + ): ArmoredOutputStream { return builder.build(outputStream) } /** - * Overwrite the version header of ASCII armors with a custom value. - * Newlines in the version info string result in multiple version header entries. - * If this is set to

null
, then the version header is omitted altogether. + * Overwrite the version header of ASCII armors with a custom value. Newlines in the version + * info string result in multiple version header entries. If this is set to
null
, + * then the version header is omitted altogether. * * @param versionString version string */ @@ -72,25 +76,24 @@ class ArmoredOutputStreamFactory { version = if (versionString.isNullOrBlank()) null else versionString.trim() } - /** - * Reset the version header to its default value of [PGPAINLESS]. - */ + /** Reset the version header to its default value of [PGPAINLESS]. */ @JvmStatic fun resetVersionInfo() { version = PGPAINLESS } /** - * Set a comment header value in the ASCII armor header. - * If the comment contains newlines, it will be split into multiple header entries. - * - * @see [ProducerOptions.setComment] for how to set comments for individual messages. + * Set a comment header value in the ASCII armor header. If the comment contains newlines, + * it will be split into multiple header entries. * * @param commentString comment + * @see [ProducerOptions.setComment] for how to set comments for individual messages. */ @JvmStatic fun setComment(commentString: String) { - require(commentString.isNotBlank()) { "Comment cannot be empty. See resetComment() to clear the comment." } + require(commentString.isNotBlank()) { + "Comment cannot be empty. See resetComment() to clear the comment." + } comment = commentString.trim() } @@ -99,4 +102,4 @@ class ArmoredOutputStreamFactory { comment = null } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/DateUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/DateUtil.kt index de2052d6..712ac262 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/DateUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/DateUtil.kt @@ -4,10 +4,10 @@ package org.pgpainless.util +import java.util.* import openpgp.formatUTC import openpgp.parseUTC import openpgp.toSecondsPrecision -import java.util.* class DateUtil { @@ -19,8 +19,7 @@ class DateUtil { * @param dateString timestamp * @return date */ - @JvmStatic - fun parseUTCDate(dateString: String): Date = dateString.parseUTC() + @JvmStatic fun parseUTCDate(dateString: String): Date = dateString.parseUTC() /** * Format a date as UTC timestamp. @@ -28,23 +27,21 @@ class DateUtil { * @param date date * @return timestamp */ - @JvmStatic - fun formatUTCDate(date: Date): String = date.formatUTC() + @JvmStatic fun formatUTCDate(date: Date): String = date.formatUTC() /** * Floor a date down to seconds precision. + * * @param date date * @return floored date */ - @JvmStatic - fun toSecondsPrecision(date: Date): Date = date.toSecondsPrecision() + @JvmStatic fun toSecondsPrecision(date: Date): Date = date.toSecondsPrecision() /** * Return the current date "floored" to UTC precision. * * @return now */ - @JvmStatic - fun now() = toSecondsPrecision(Date()) + @JvmStatic fun now() = toSecondsPrecision(Date()) } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt index 9ca193a6..3aa22d0d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt @@ -8,13 +8,13 @@ class MultiMap : Iterable>> { private val map: Map> - constructor(): this(mutableMapOf()) - constructor(other: MultiMap): this(other.map) + constructor() : this(mutableMapOf()) + + constructor(other: MultiMap) : this(other.map) + constructor(content: Map>) { map = mutableMapOf() - content.forEach { - map[it.key] = it.value.toMutableSet() - } + content.forEach { map[it.key] = it.value.toMutableSet() } } override fun iterator(): Iterator>> { @@ -23,45 +23,58 @@ class MultiMap : Iterable>> { val size: Int get() = map.size + fun size() = size + val keys: Set get() = map.keys + fun keySet() = keys + val values: Collection> get() = map.values + fun values() = values + val entries: Set>> get() = map.entries + fun entrySet() = entries + fun isEmpty(): Boolean = map.isEmpty() + fun containsKey(key: K): Boolean = map.containsKey(key) + fun containsValue(value: V): Boolean = map.values.any { it.contains(value) } + fun contains(key: K, value: V): Boolean = map[key]?.contains(value) ?: false + operator fun get(key: K): Set? = map[key] - fun put(key: K, value: V) = - (map as MutableMap).put(key, mutableSetOf(value)) - fun plus(key: K, value: V) = - (map as MutableMap).getOrPut(key) { mutableSetOf() }.add(value) - fun put(key: K, values: Set) = - (map as MutableMap).put(key, values.toMutableSet()) + + fun put(key: K, value: V) = (map as MutableMap).put(key, mutableSetOf(value)) + + fun plus(key: K, value: V) = (map as MutableMap).getOrPut(key) { mutableSetOf() }.add(value) + + fun put(key: K, values: Set) = (map as MutableMap).put(key, values.toMutableSet()) + fun plus(key: K, values: Set) = - (map as MutableMap).getOrPut(key) { mutableSetOf() }.addAll(values) - fun putAll(other: MultiMap) = other.map.entries.forEach { - put(it.key, it.value) - } - fun plusAll(other: MultiMap) = other.map.entries.forEach { - plus(it.key, it.value) - } + (map as MutableMap).getOrPut(key) { mutableSetOf() }.addAll(values) + + fun putAll(other: MultiMap) = other.map.entries.forEach { put(it.key, it.value) } + + fun plusAll(other: MultiMap) = other.map.entries.forEach { plus(it.key, it.value) } + fun removeAll(key: K) = (map as MutableMap).remove(key) + fun remove(key: K, value: V) = (map as MutableMap)[key]?.remove(value) + fun clear() = (map as MutableMap).clear() + fun flatten() = map.flatMap { it.value }.toSet() override fun equals(other: Any?): Boolean { - return if (other == null) - false - else if (other !is MultiMap<*, *>) - false + return if (other == null) false + else if (other !is MultiMap<*, *>) false else if (this === other) { true } else { @@ -72,4 +85,4 @@ class MultiMap : Iterable>> { override fun hashCode(): Int { return map.hashCode() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/NotationRegistry.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/NotationRegistry.kt index 15dbe886..d2295f32 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/NotationRegistry.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/NotationRegistry.kt @@ -5,9 +5,9 @@ package org.pgpainless.util /** - * Registry for known notations. - * Since signature verification must reject signatures with critical notations that are not known to the application, - * there must be some way to tell PGPainless which notations actually are known. + * Registry for known notations. Since signature verification must reject signatures with critical + * notations that are not known to the application, there must be some way to tell PGPainless which + * notations actually are known. * * To add a notation name, call {@link #addKnownNotation(String)}. */ @@ -19,8 +19,8 @@ class NotationRegistry constructor(notations: Set = setOf()) { } /** - * Add a known notation name into the registry. - * This will cause critical notations with that name to no longer invalidate the signature. + * Add a known notation name into the registry. This will cause critical notations with that + * name to no longer invalidate the signature. * * @param notationName name of the notation */ @@ -36,10 +36,8 @@ class NotationRegistry constructor(notations: Set = setOf()) { */ fun isKnownNotation(notationName: String): Boolean = knownNotations.contains(notationName) - /** - * Clear all known notations from the registry. - */ + /** Clear all known notations from the registry. */ fun clear() { knownNotations.clear() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt index a64fda53..4d1e49d2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/Passphrase.kt @@ -11,9 +11,7 @@ import org.bouncycastle.util.Arrays * * @param chars may be null for empty passwords. */ -class Passphrase( - chars: CharArray? -) { +class Passphrase(chars: CharArray?) { private val lock = Any() private var valid = true private val chars: CharArray? @@ -23,17 +21,17 @@ class Passphrase( } /** - * Return a copy of the underlying char array. - * A return value of null represents an empty password. + * Return a copy of the underlying char array. A return value of null represents an empty + * password. * * @return passphrase chars. - * * @throws IllegalStateException in case the password has been cleared at this point. */ - fun getChars(): CharArray? = synchronized(lock) { - check(valid) { "Passphrase has been cleared." } - chars?.copyOf() - } + fun getChars(): CharArray? = + synchronized(lock) { + check(valid) { "Passphrase has been cleared." } + chars?.copyOf() + } /** * Return true if the passphrase has not yet been cleared. @@ -51,23 +49,20 @@ class Passphrase( val isEmpty: Boolean get() = synchronized(lock) { valid && chars == null } - /** - * Overwrite the char array with spaces and mark the [Passphrase] as invalidated. - */ - fun clear() = synchronized(lock) { - chars?.fill(' ') - valid = false - } + /** Overwrite the char array with spaces and mark the [Passphrase] as invalidated. */ + fun clear() = + synchronized(lock) { + chars?.fill(' ') + valid = false + } override fun equals(other: Any?): Boolean { - return if (other == null) - false - else if (this === other) - true - else if (other !is Passphrase) - false + return if (other == null) false + else if (this === other) true + else if (other !is Passphrase) false else - getChars() == null && other.getChars() == null || Arrays.constantTimeAreEqual(getChars(), other.getChars()) + getChars() == null && other.getChars() == null || + Arrays.constantTimeAreEqual(getChars(), other.getChars()) } override fun hashCode(): Int = getChars()?.let { String(it) }.hashCode() @@ -83,23 +78,23 @@ class Passphrase( @JvmStatic fun fromPassword(password: CharSequence) = Passphrase(password.toString().toCharArray()) - @JvmStatic - fun emptyPassphrase() = Passphrase(null) + @JvmStatic fun emptyPassphrase() = Passphrase(null) /** - * Return a copy of the passed in char array, with leading and trailing whitespace characters removed. - * If the passed in char array is null, return null. - * If the resulting char array is empty, return null as well. + * Return a copy of the passed in char array, with leading and trailing whitespace + * characters removed. If the passed in char array is null, return null. If the resulting + * char array is empty, return null as well. * * @param chars char array * @return copy of char array with leading and trailing whitespace characters removed */ @JvmStatic private fun trimWhitespace(chars: CharArray?): CharArray? { - return chars?.dropWhile { it.isWhitespace() } - ?.dropLastWhile { it.isWhitespace() } - ?.toCharArray() - ?.let { if (it.isEmpty()) null else it } + return chars + ?.dropWhile { it.isWhitespace() } + ?.dropLastWhile { it.isWhitespace() } + ?.toCharArray() + ?.let { if (it.isEmpty()) null else it } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/SessionKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/SessionKey.kt index 894d0869..ef8eb9e7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/SessionKey.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/SessionKey.kt @@ -10,21 +10,22 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm /** * A [SessionKey] is the symmetric key that is used to encrypt/decrypt an OpenPGP message payload. - * The OpenPGP message header contains a copy of the session key, encrypted for the public key of each recipient. + * The OpenPGP message header contains a copy of the session key, encrypted for the public key of + * each recipient. * * @param algorithm symmetric key algorithm * @param key bytes of the key */ -data class SessionKey(val algorithm: SymmetricKeyAlgorithm, - val key: ByteArray) { +data class SessionKey(val algorithm: SymmetricKeyAlgorithm, val key: ByteArray) { /** * Constructor to create a session key from a BC [PGPSessionKey] object. * * @param sessionKey BC session key */ - constructor(sessionKey: PGPSessionKey): - this(SymmetricKeyAlgorithm.requireFromId(sessionKey.algorithm), sessionKey.key) + constructor( + sessionKey: PGPSessionKey + ) : this(SymmetricKeyAlgorithm.requireFromId(sessionKey.algorithm), sessionKey.key) override fun toString(): String { return "${algorithm.algorithmId}:${Hex.toHexString(key)}" @@ -45,4 +46,4 @@ data class SessionKey(val algorithm: SymmetricKeyAlgorithm, override fun hashCode(): Int { return 31 * algorithm.hashCode() + key.contentHashCode() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/selection/userid/SelectUserId.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/selection/userid/SelectUserId.kt index 3c215d80..f2794925 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/selection/userid/SelectUserId.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/selection/userid/SelectUserId.kt @@ -4,15 +4,13 @@ package org.pgpainless.util.selection.userid +import java.util.function.Predicate import org.bouncycastle.openpgp.PGPKeyRing import org.pgpainless.PGPainless -import java.util.function.Predicate abstract class SelectUserId : Predicate, (String) -> Boolean { - /** - * Legacy glue code to forward accept() calls to invoke() instead. - */ + /** Legacy glue code to forward accept() calls to invoke() instead. */ @Deprecated("Use invoke() instead.", ReplaceWith("invoke(userId)")) protected fun accept(userId: String): Boolean = invoke(userId) @@ -27,10 +25,10 @@ abstract class SelectUserId : Predicate, (String) -> Boolean { * @return filter */ @JvmStatic - fun exactMatch(query: CharSequence) = object : SelectUserId() { - override fun invoke(userId: String): Boolean = - userId == query - } + fun exactMatch(query: CharSequence) = + object : SelectUserId() { + override fun invoke(userId: String): Boolean = userId == query + } /** * Filter for user-ids which start with the given [substring]. @@ -39,10 +37,10 @@ abstract class SelectUserId : Predicate, (String) -> Boolean { * @return filter */ @JvmStatic - fun startsWith(substring: CharSequence) = object : SelectUserId() { - override fun invoke(userId: String): Boolean = - userId.startsWith(substring) - } + fun startsWith(substring: CharSequence) = + object : SelectUserId() { + override fun invoke(userId: String): Boolean = userId.startsWith(substring) + } /** * Filter for user-ids which contain the given [substring]. @@ -51,54 +49,53 @@ abstract class SelectUserId : Predicate, (String) -> Boolean { * @return filter */ @JvmStatic - fun containsSubstring(substring: CharSequence) = object : SelectUserId() { - override fun invoke(userId: String): Boolean = - userId.contains(substring) - } + fun containsSubstring(substring: CharSequence) = + object : SelectUserId() { + override fun invoke(userId: String): Boolean = userId.contains(substring) + } /** - * Filter for user-ids which contain the given [email] address. - * Note: This only accepts user-ids which properly have the email address surrounded by angle brackets. + * Filter for user-ids which contain the given [email] address. Note: This only accepts + * user-ids which properly have the email address surrounded by angle brackets. * - * The argument [email] can both be a plain email address (`foo@bar.baz`), - * or surrounded by angle brackets (``), the result of the filter will be the same. + * The argument [email] can both be a plain email address (`foo@bar.baz`), or surrounded by + * angle brackets (``), the result of the filter will be the same. * * @param email email address * @return filter */ @JvmStatic fun containsEmailAddress(email: CharSequence) = - if (email.startsWith('<') && email.endsWith('>')) - containsSubstring(email) - else - containsSubstring("<$email>") + if (email.startsWith('<') && email.endsWith('>')) containsSubstring(email) + else containsSubstring("<$email>") @JvmStatic fun byEmail(email: CharSequence) = or(exactMatch(email), containsEmailAddress(email)) @JvmStatic - fun validUserId(keyRing: PGPKeyRing) = object : SelectUserId() { - private val info = PGPainless.inspectKeyRing(keyRing) - override fun invoke(userId: String): Boolean = - info.isUserIdValid(userId) - } + fun validUserId(keyRing: PGPKeyRing) = + object : SelectUserId() { + private val info = PGPainless.inspectKeyRing(keyRing) + + override fun invoke(userId: String): Boolean = info.isUserIdValid(userId) + } @JvmStatic - fun and(vararg filters: SelectUserId) = object : SelectUserId() { - override fun invoke(userId: String): Boolean = - filters.all { it.invoke(userId) } - } + fun and(vararg filters: SelectUserId) = + object : SelectUserId() { + override fun invoke(userId: String): Boolean = filters.all { it.invoke(userId) } + } @JvmStatic - fun or(vararg filters: SelectUserId) = object : SelectUserId() { - override fun invoke(userId: String): Boolean = - filters.any { it.invoke(userId) } - } + fun or(vararg filters: SelectUserId) = + object : SelectUserId() { + override fun invoke(userId: String): Boolean = filters.any { it.invoke(userId) } + } @JvmStatic - fun not(filter: SelectUserId) = object : SelectUserId() { - override fun invoke(userId: String): Boolean = - !filter.invoke(userId) - } + fun not(filter: SelectUserId) = + object : SelectUserId() { + override fun invoke(userId: String): Boolean = !filter.invoke(userId) + } } -} \ No newline at end of file +} From 29fe4faeed3bb486f1627f7c2479c8fda58d9dc9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 23 Oct 2023 14:27:54 +0200 Subject: [PATCH 149/155] Add .git-blame-ignore-revs file --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..447355cc --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Ignore initial spotlessApply using ktfmt +51e9bfc67f19e16a69790a8d92bd6b1c86a76a5f From 4f64868914f333940eefc443e663a3e21a50a8d2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 24 Oct 2023 10:15:16 +0200 Subject: [PATCH 150/155] Add dropbox-style editorconfig Source: https://github.com/facebook/ktfmt/blob/main/docs/editorconfig/.editorconfig-dropbox --- .editorconfig | 589 +++----------------------------------------------- 1 file changed, 30 insertions(+), 559 deletions(-) diff --git a/.editorconfig b/.editorconfig index 9bf812d1..bc214f67 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,513 +1,51 @@ -# SPDX-FileCopyrightText: 2021 Paul Schaub +# This .editorconfig section approximates ktfmt's formatting rules. You can include it in an +# existing .editorconfig file or use it standalone by copying it to /.editorconfig +# and making sure your editor is set to read settings from .editorconfig files. # -# SPDX-License-Identifier: CC0-1.0 +# It includes editor-specific config options for IntelliJ IDEA. +# +# If any option is wrong, PR are welcome -[*] -charset = utf-8 -end_of_line = lf -indent_size = 4 +[{*.kt,*.kts}] indent_style = space -insert_final_newline = false -max_line_length = 120 -tab_width = 4 +insert_final_newline = true +max_line_length = 100 +indent_size = 4 ij_continuation_indent_size = 8 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = false -ij_smart_tabs = false -ij_visual_guides = none -ij_wrap_on_typing = false - -[*.java] -ij_java_align_consecutive_assignments = false -ij_java_align_consecutive_variable_declarations = false -ij_java_align_group_field_declarations = false -ij_java_align_multiline_annotation_parameters = false -ij_java_align_multiline_array_initializer_expression = false -ij_java_align_multiline_assignment = false -ij_java_align_multiline_binary_operation = false -ij_java_align_multiline_chained_methods = false -ij_java_align_multiline_extends_list = false -ij_java_align_multiline_for = true -ij_java_align_multiline_method_parentheses = false -ij_java_align_multiline_parameters = true -ij_java_align_multiline_parameters_in_calls = false -ij_java_align_multiline_parenthesized_expression = false -ij_java_align_multiline_records = true -ij_java_align_multiline_resources = true -ij_java_align_multiline_ternary_operation = false -ij_java_align_multiline_text_blocks = false -ij_java_align_multiline_throws_list = false -ij_java_align_subsequent_simple_methods = false -ij_java_align_throws_keyword = false -ij_java_annotation_parameter_wrap = off -ij_java_array_initializer_new_line_after_left_brace = false -ij_java_array_initializer_right_brace_on_new_line = false -ij_java_array_initializer_wrap = off -ij_java_assert_statement_colon_on_next_line = false -ij_java_assert_statement_wrap = off -ij_java_assignment_wrap = off -ij_java_binary_operation_sign_on_next_line = false -ij_java_binary_operation_wrap = off -ij_java_blank_lines_after_anonymous_class_header = 0 -ij_java_blank_lines_after_class_header = 0 -ij_java_blank_lines_after_imports = 1 -ij_java_blank_lines_after_package = 1 -ij_java_blank_lines_around_class = 1 -ij_java_blank_lines_around_field = 0 -ij_java_blank_lines_around_field_in_interface = 0 -ij_java_blank_lines_around_initializer = 1 -ij_java_blank_lines_around_method = 1 -ij_java_blank_lines_around_method_in_interface = 1 -ij_java_blank_lines_before_class_end = 0 -ij_java_blank_lines_before_imports = 1 -ij_java_blank_lines_before_method_body = 0 -ij_java_blank_lines_before_package = 0 -ij_java_block_brace_style = end_of_line -ij_java_block_comment_at_first_column = true -ij_java_builder_methods = none -ij_java_call_parameters_new_line_after_left_paren = false -ij_java_call_parameters_right_paren_on_new_line = false -ij_java_call_parameters_wrap = off -ij_java_case_statement_on_separate_line = true -ij_java_catch_on_new_line = false -ij_java_class_annotation_wrap = split_into_lines -ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 10000 -ij_java_class_names_in_javadoc = 1 -ij_java_do_not_indent_top_level_class_members = false -ij_java_do_not_wrap_after_single_annotation = false -ij_java_do_while_brace_force = never -ij_java_doc_add_blank_line_after_description = true -ij_java_doc_add_blank_line_after_param_comments = false -ij_java_doc_add_blank_line_after_return = false -ij_java_doc_add_p_tag_on_empty_lines = true -ij_java_doc_align_exception_comments = true -ij_java_doc_align_param_comments = true -ij_java_doc_do_not_wrap_if_one_line = false -ij_java_doc_enable_formatting = true -ij_java_doc_enable_leading_asterisks = true -ij_java_doc_indent_on_continuation = false -ij_java_doc_keep_empty_lines = true -ij_java_doc_keep_empty_parameter_tag = true -ij_java_doc_keep_empty_return_tag = true -ij_java_doc_keep_empty_throws_tag = true -ij_java_doc_keep_invalid_tags = true -ij_java_doc_param_description_on_new_line = false -ij_java_doc_preserve_line_breaks = false -ij_java_doc_use_throws_not_exception_tag = true -ij_java_else_on_new_line = false -ij_java_enum_constants_wrap = off -ij_java_extends_keyword_wrap = off -ij_java_extends_list_wrap = off -ij_java_field_annotation_wrap = split_into_lines -ij_java_finally_on_new_line = false -ij_java_for_brace_force = never -ij_java_for_statement_new_line_after_left_paren = false -ij_java_for_statement_right_paren_on_new_line = false -ij_java_for_statement_wrap = off -ij_java_generate_final_locals = false -ij_java_generate_final_parameters = false -ij_java_if_brace_force = never -ij_java_imports_layout = $*,|,java.**,javax.**,|,* -ij_java_indent_case_from_switch = true -ij_java_insert_inner_class_imports = false -ij_java_insert_override_annotation = true -ij_java_keep_blank_lines_before_right_brace = 2 -ij_java_keep_blank_lines_between_package_declaration_and_header = 2 -ij_java_keep_blank_lines_in_code = 2 -ij_java_keep_blank_lines_in_declarations = 2 -ij_java_keep_builder_methods_indents = false -ij_java_keep_control_statement_in_one_line = true -ij_java_keep_first_column_comment = true -ij_java_keep_indents_on_empty_lines = false -ij_java_keep_line_breaks = true -ij_java_keep_multiple_expressions_in_one_line = false -ij_java_keep_simple_blocks_in_one_line = false -ij_java_keep_simple_classes_in_one_line = false -ij_java_keep_simple_lambdas_in_one_line = false -ij_java_keep_simple_methods_in_one_line = false -ij_java_label_indent_absolute = false -ij_java_label_indent_size = 0 -ij_java_lambda_brace_style = end_of_line -ij_java_layout_static_imports_separately = true -ij_java_line_comment_add_space = false -ij_java_line_comment_at_first_column = true -ij_java_method_annotation_wrap = split_into_lines -ij_java_method_brace_style = end_of_line -ij_java_method_call_chain_wrap = off -ij_java_method_parameters_new_line_after_left_paren = false -ij_java_method_parameters_right_paren_on_new_line = false -ij_java_method_parameters_wrap = off -ij_java_modifier_list_wrap = false -ij_java_names_count_to_use_import_on_demand = 1000 -ij_java_new_line_after_lparen_in_record_header = false -ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* -ij_java_parameter_annotation_wrap = off -ij_java_parentheses_expression_new_line_after_left_paren = false -ij_java_parentheses_expression_right_paren_on_new_line = false -ij_java_place_assignment_sign_on_next_line = false -ij_java_prefer_longer_names = true -ij_java_prefer_parameters_wrap = false -ij_java_record_components_wrap = normal -ij_java_repeat_synchronized = true -ij_java_replace_instanceof_and_cast = false -ij_java_replace_null_check = true -ij_java_replace_sum_lambda_with_method_ref = true -ij_java_resource_list_new_line_after_left_paren = false -ij_java_resource_list_right_paren_on_new_line = false -ij_java_resource_list_wrap = off -ij_java_rparen_on_new_line_in_record_header = false -ij_java_space_after_closing_angle_bracket_in_type_argument = false -ij_java_space_after_colon = true -ij_java_space_after_comma = true -ij_java_space_after_comma_in_type_arguments = true -ij_java_space_after_for_semicolon = true -ij_java_space_after_quest = true -ij_java_space_after_type_cast = true -ij_java_space_before_annotation_array_initializer_left_brace = false -ij_java_space_before_annotation_parameter_list = false -ij_java_space_before_array_initializer_left_brace = false -ij_java_space_before_catch_keyword = true -ij_java_space_before_catch_left_brace = true -ij_java_space_before_catch_parentheses = true -ij_java_space_before_class_left_brace = true -ij_java_space_before_colon = true -ij_java_space_before_colon_in_foreach = true -ij_java_space_before_comma = false -ij_java_space_before_do_left_brace = true -ij_java_space_before_else_keyword = true -ij_java_space_before_else_left_brace = true -ij_java_space_before_finally_keyword = true -ij_java_space_before_finally_left_brace = true -ij_java_space_before_for_left_brace = true -ij_java_space_before_for_parentheses = true -ij_java_space_before_for_semicolon = false -ij_java_space_before_if_left_brace = true -ij_java_space_before_if_parentheses = true -ij_java_space_before_method_call_parentheses = false -ij_java_space_before_method_left_brace = true -ij_java_space_before_method_parentheses = false -ij_java_space_before_opening_angle_bracket_in_type_parameter = false -ij_java_space_before_quest = true -ij_java_space_before_switch_left_brace = true -ij_java_space_before_switch_parentheses = true -ij_java_space_before_synchronized_left_brace = true -ij_java_space_before_synchronized_parentheses = true -ij_java_space_before_try_left_brace = true -ij_java_space_before_try_parentheses = true -ij_java_space_before_type_parameter_list = false -ij_java_space_before_while_keyword = true -ij_java_space_before_while_left_brace = true -ij_java_space_before_while_parentheses = true -ij_java_space_inside_one_line_enum_braces = false -ij_java_space_within_empty_array_initializer_braces = false -ij_java_space_within_empty_method_call_parentheses = false -ij_java_space_within_empty_method_parentheses = false -ij_java_spaces_around_additive_operators = true -ij_java_spaces_around_assignment_operators = true -ij_java_spaces_around_bitwise_operators = true -ij_java_spaces_around_equality_operators = true -ij_java_spaces_around_lambda_arrow = true -ij_java_spaces_around_logical_operators = true -ij_java_spaces_around_method_ref_dbl_colon = false -ij_java_spaces_around_multiplicative_operators = true -ij_java_spaces_around_relational_operators = true -ij_java_spaces_around_shift_operators = true -ij_java_spaces_around_type_bounds_in_type_parameters = true -ij_java_spaces_around_unary_operator = false -ij_java_spaces_within_angle_brackets = false -ij_java_spaces_within_annotation_parentheses = false -ij_java_spaces_within_array_initializer_braces = false -ij_java_spaces_within_braces = false -ij_java_spaces_within_brackets = false -ij_java_spaces_within_cast_parentheses = false -ij_java_spaces_within_catch_parentheses = false -ij_java_spaces_within_for_parentheses = false -ij_java_spaces_within_if_parentheses = false -ij_java_spaces_within_method_call_parentheses = false -ij_java_spaces_within_method_parentheses = false -ij_java_spaces_within_parentheses = false -ij_java_spaces_within_record_header = false -ij_java_spaces_within_switch_parentheses = false -ij_java_spaces_within_synchronized_parentheses = false -ij_java_spaces_within_try_parentheses = false -ij_java_spaces_within_while_parentheses = false -ij_java_special_else_if_treatment = true -ij_java_subclass_name_suffix = Impl -ij_java_ternary_operation_signs_on_next_line = false -ij_java_ternary_operation_wrap = off -ij_java_test_name_suffix = Test -ij_java_throws_keyword_wrap = off -ij_java_throws_list_wrap = off -ij_java_use_external_annotations = false -ij_java_use_fq_class_names = false -ij_java_use_relative_indents = false -ij_java_use_single_class_imports = true -ij_java_variable_annotation_wrap = off -ij_java_visibility = public -ij_java_while_brace_force = never -ij_java_while_on_new_line = false -ij_java_wrap_comments = false -ij_java_wrap_first_method_in_call_chain = false -ij_java_wrap_long_lines = false - -[*.properties] -ij_properties_align_group_field_declarations = false -ij_properties_keep_blank_lines = false -ij_properties_key_value_delimiter = equals -ij_properties_spaces_around_key_value_delimiter = false - -[.editorconfig] -ij_editorconfig_align_group_field_declarations = false -ij_editorconfig_space_after_colon = false -ij_editorconfig_space_after_comma = true -ij_editorconfig_space_before_colon = false -ij_editorconfig_space_before_comma = false -ij_editorconfig_spaces_around_assignment_operators = true - -[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] -ij_xml_align_attributes = true -ij_xml_align_text = false -ij_xml_attribute_wrap = normal -ij_xml_block_comment_at_first_column = true -ij_xml_keep_blank_lines = 2 -ij_xml_keep_indents_on_empty_lines = false -ij_xml_keep_line_breaks = true -ij_xml_keep_line_breaks_in_text = true -ij_xml_keep_whitespaces = false -ij_xml_keep_whitespaces_around_cdata = preserve -ij_xml_keep_whitespaces_inside_cdata = false -ij_xml_line_comment_at_first_column = true -ij_xml_space_after_tag_name = false -ij_xml_space_around_equals_in_attribute = false -ij_xml_space_inside_empty_tag = false -ij_xml_text_wrap = normal -ij_xml_use_custom_settings = false - -[{*.bash,*.sh,*.zsh}] -indent_size = 2 -tab_width = 2 -ij_shell_binary_ops_start_line = false -ij_shell_keep_column_alignment_padding = false -ij_shell_minify_program = false -ij_shell_redirect_followed_by_space = false -ij_shell_switch_cases_indented = false -ij_shell_use_unix_line_separator = true - -[{*.gant,*.gradle,*.groovy,*.gy}] -ij_groovy_align_group_field_declarations = false -ij_groovy_align_multiline_array_initializer_expression = false -ij_groovy_align_multiline_assignment = false -ij_groovy_align_multiline_binary_operation = false -ij_groovy_align_multiline_chained_methods = false -ij_groovy_align_multiline_extends_list = false -ij_groovy_align_multiline_for = true -ij_groovy_align_multiline_list_or_map = true -ij_groovy_align_multiline_method_parentheses = false -ij_groovy_align_multiline_parameters = true -ij_groovy_align_multiline_parameters_in_calls = false -ij_groovy_align_multiline_resources = true -ij_groovy_align_multiline_ternary_operation = false -ij_groovy_align_multiline_throws_list = false -ij_groovy_align_named_args_in_map = true -ij_groovy_align_throws_keyword = false -ij_groovy_array_initializer_new_line_after_left_brace = false -ij_groovy_array_initializer_right_brace_on_new_line = false -ij_groovy_array_initializer_wrap = off -ij_groovy_assert_statement_wrap = off -ij_groovy_assignment_wrap = off -ij_groovy_binary_operation_wrap = off -ij_groovy_blank_lines_after_class_header = 0 -ij_groovy_blank_lines_after_imports = 1 -ij_groovy_blank_lines_after_package = 1 -ij_groovy_blank_lines_around_class = 1 -ij_groovy_blank_lines_around_field = 0 -ij_groovy_blank_lines_around_field_in_interface = 0 -ij_groovy_blank_lines_around_method = 1 -ij_groovy_blank_lines_around_method_in_interface = 1 -ij_groovy_blank_lines_before_imports = 1 -ij_groovy_blank_lines_before_method_body = 0 -ij_groovy_blank_lines_before_package = 0 -ij_groovy_block_brace_style = end_of_line -ij_groovy_block_comment_at_first_column = true -ij_groovy_call_parameters_new_line_after_left_paren = false -ij_groovy_call_parameters_right_paren_on_new_line = false -ij_groovy_call_parameters_wrap = off -ij_groovy_catch_on_new_line = false -ij_groovy_class_annotation_wrap = split_into_lines -ij_groovy_class_brace_style = end_of_line -ij_groovy_class_count_to_use_import_on_demand = 5 -ij_groovy_do_while_brace_force = never -ij_groovy_else_on_new_line = false -ij_groovy_enum_constants_wrap = off -ij_groovy_extends_keyword_wrap = off -ij_groovy_extends_list_wrap = off -ij_groovy_field_annotation_wrap = split_into_lines -ij_groovy_finally_on_new_line = false -ij_groovy_for_brace_force = never -ij_groovy_for_statement_new_line_after_left_paren = false -ij_groovy_for_statement_right_paren_on_new_line = false -ij_groovy_for_statement_wrap = off -ij_groovy_if_brace_force = never -ij_groovy_import_annotation_wrap = 2 -ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* -ij_groovy_indent_case_from_switch = true -ij_groovy_indent_label_blocks = true -ij_groovy_insert_inner_class_imports = false -ij_groovy_keep_blank_lines_before_right_brace = 2 -ij_groovy_keep_blank_lines_in_code = 2 -ij_groovy_keep_blank_lines_in_declarations = 2 -ij_groovy_keep_control_statement_in_one_line = true -ij_groovy_keep_first_column_comment = true -ij_groovy_keep_indents_on_empty_lines = false -ij_groovy_keep_line_breaks = true -ij_groovy_keep_multiple_expressions_in_one_line = false -ij_groovy_keep_simple_blocks_in_one_line = false -ij_groovy_keep_simple_classes_in_one_line = true -ij_groovy_keep_simple_lambdas_in_one_line = true -ij_groovy_keep_simple_methods_in_one_line = true -ij_groovy_label_indent_absolute = false -ij_groovy_label_indent_size = 0 -ij_groovy_lambda_brace_style = end_of_line -ij_groovy_layout_static_imports_separately = true -ij_groovy_line_comment_add_space = false -ij_groovy_line_comment_at_first_column = true -ij_groovy_method_annotation_wrap = split_into_lines -ij_groovy_method_brace_style = end_of_line -ij_groovy_method_call_chain_wrap = off -ij_groovy_method_parameters_new_line_after_left_paren = false -ij_groovy_method_parameters_right_paren_on_new_line = false -ij_groovy_method_parameters_wrap = off -ij_groovy_modifier_list_wrap = false -ij_groovy_names_count_to_use_import_on_demand = 3 -ij_groovy_parameter_annotation_wrap = off -ij_groovy_parentheses_expression_new_line_after_left_paren = false -ij_groovy_parentheses_expression_right_paren_on_new_line = false -ij_groovy_prefer_parameters_wrap = false -ij_groovy_resource_list_new_line_after_left_paren = false -ij_groovy_resource_list_right_paren_on_new_line = false -ij_groovy_resource_list_wrap = off -ij_groovy_space_after_assert_separator = true -ij_groovy_space_after_colon = true -ij_groovy_space_after_comma = true -ij_groovy_space_after_comma_in_type_arguments = true -ij_groovy_space_after_for_semicolon = true -ij_groovy_space_after_quest = true -ij_groovy_space_after_type_cast = true -ij_groovy_space_before_annotation_parameter_list = false -ij_groovy_space_before_array_initializer_left_brace = false -ij_groovy_space_before_assert_separator = false -ij_groovy_space_before_catch_keyword = true -ij_groovy_space_before_catch_left_brace = true -ij_groovy_space_before_catch_parentheses = true -ij_groovy_space_before_class_left_brace = true -ij_groovy_space_before_closure_left_brace = true -ij_groovy_space_before_colon = true -ij_groovy_space_before_comma = false -ij_groovy_space_before_do_left_brace = true -ij_groovy_space_before_else_keyword = true -ij_groovy_space_before_else_left_brace = true -ij_groovy_space_before_finally_keyword = true -ij_groovy_space_before_finally_left_brace = true -ij_groovy_space_before_for_left_brace = true -ij_groovy_space_before_for_parentheses = true -ij_groovy_space_before_for_semicolon = false -ij_groovy_space_before_if_left_brace = true -ij_groovy_space_before_if_parentheses = true -ij_groovy_space_before_method_call_parentheses = false -ij_groovy_space_before_method_left_brace = true -ij_groovy_space_before_method_parentheses = false -ij_groovy_space_before_quest = true -ij_groovy_space_before_switch_left_brace = true -ij_groovy_space_before_switch_parentheses = true -ij_groovy_space_before_synchronized_left_brace = true -ij_groovy_space_before_synchronized_parentheses = true -ij_groovy_space_before_try_left_brace = true -ij_groovy_space_before_try_parentheses = true -ij_groovy_space_before_while_keyword = true -ij_groovy_space_before_while_left_brace = true -ij_groovy_space_before_while_parentheses = true -ij_groovy_space_in_named_argument = true -ij_groovy_space_in_named_argument_before_colon = false -ij_groovy_space_within_empty_array_initializer_braces = false -ij_groovy_space_within_empty_method_call_parentheses = false -ij_groovy_spaces_around_additive_operators = true -ij_groovy_spaces_around_assignment_operators = true -ij_groovy_spaces_around_bitwise_operators = true -ij_groovy_spaces_around_equality_operators = true -ij_groovy_spaces_around_lambda_arrow = true -ij_groovy_spaces_around_logical_operators = true -ij_groovy_spaces_around_multiplicative_operators = true -ij_groovy_spaces_around_regex_operators = true -ij_groovy_spaces_around_relational_operators = true -ij_groovy_spaces_around_shift_operators = true -ij_groovy_spaces_within_annotation_parentheses = false -ij_groovy_spaces_within_array_initializer_braces = false -ij_groovy_spaces_within_braces = true -ij_groovy_spaces_within_brackets = false -ij_groovy_spaces_within_cast_parentheses = false -ij_groovy_spaces_within_catch_parentheses = false -ij_groovy_spaces_within_for_parentheses = false -ij_groovy_spaces_within_gstring_injection_braces = false -ij_groovy_spaces_within_if_parentheses = false -ij_groovy_spaces_within_list_or_map = false -ij_groovy_spaces_within_method_call_parentheses = false -ij_groovy_spaces_within_method_parentheses = false -ij_groovy_spaces_within_parentheses = false -ij_groovy_spaces_within_switch_parentheses = false -ij_groovy_spaces_within_synchronized_parentheses = false -ij_groovy_spaces_within_try_parentheses = false -ij_groovy_spaces_within_tuple_expression = false -ij_groovy_spaces_within_while_parentheses = false -ij_groovy_special_else_if_treatment = true -ij_groovy_ternary_operation_wrap = off -ij_groovy_throws_keyword_wrap = off -ij_groovy_throws_list_wrap = off -ij_groovy_use_flying_geese_braces = false -ij_groovy_use_fq_class_names = false -ij_groovy_use_fq_class_names_in_javadoc = true -ij_groovy_use_relative_indents = false -ij_groovy_use_single_class_imports = true -ij_groovy_variable_annotation_wrap = off -ij_groovy_while_brace_force = never -ij_groovy_while_on_new_line = false -ij_groovy_wrap_long_lines = false - -[{*.gradle.kts,*.kt,*.kts,*.main.kts,*.space.kts}] +ij_java_names_count_to_use_import_on_demand = 9999 ij_kotlin_align_in_columns_case_branch = false ij_kotlin_align_multiline_binary_operation = false ij_kotlin_align_multiline_extends_list = false ij_kotlin_align_multiline_method_parentheses = false ij_kotlin_align_multiline_parameters = true ij_kotlin_align_multiline_parameters_in_calls = false -ij_kotlin_allow_trailing_comma = false -ij_kotlin_allow_trailing_comma_on_call_site = false -ij_kotlin_assignment_wrap = off +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ij_kotlin_assignment_wrap = normal ij_kotlin_blank_lines_after_class_header = 0 ij_kotlin_blank_lines_around_block_when_branches = 0 ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 ij_kotlin_block_comment_at_first_column = true -ij_kotlin_call_parameters_new_line_after_left_paren = false +ij_kotlin_call_parameters_new_line_after_left_paren = true ij_kotlin_call_parameters_right_paren_on_new_line = false -ij_kotlin_call_parameters_wrap = off +ij_kotlin_call_parameters_wrap = on_every_item ij_kotlin_catch_on_new_line = false ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL ij_kotlin_continuation_indent_for_chained_calls = true ij_kotlin_continuation_indent_for_expression_bodies = true ij_kotlin_continuation_indent_in_argument_lists = true -ij_kotlin_continuation_indent_in_elvis = true -ij_kotlin_continuation_indent_in_if_conditions = true -ij_kotlin_continuation_indent_in_parameter_lists = true -ij_kotlin_continuation_indent_in_supertype_lists = true +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false ij_kotlin_else_on_new_line = false ij_kotlin_enum_constants_wrap = off -ij_kotlin_extends_list_wrap = off +ij_kotlin_extends_list_wrap = normal ij_kotlin_field_annotation_wrap = split_into_lines ij_kotlin_finally_on_new_line = false ij_kotlin_if_rparen_on_new_line = false ij_kotlin_import_nested_classes = false -ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ ij_kotlin_insert_whitespaces_in_simple_one_line_method = true ij_kotlin_keep_blank_lines_before_right_brace = 2 ij_kotlin_keep_blank_lines_in_code = 2 @@ -519,13 +57,13 @@ ij_kotlin_lbrace_on_next_line = false ij_kotlin_line_comment_add_space = false ij_kotlin_line_comment_at_first_column = true ij_kotlin_method_annotation_wrap = split_into_lines -ij_kotlin_method_call_chain_wrap = off -ij_kotlin_method_parameters_new_line_after_left_paren = false -ij_kotlin_method_parameters_right_paren_on_new_line = false -ij_kotlin_method_parameters_wrap = off -ij_kotlin_name_count_to_use_star_import = 5 -ij_kotlin_name_count_to_use_star_import_for_members = 3 -ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.** +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 9999 +ij_kotlin_name_count_to_use_star_import_for_members = 9999 +ij_java_names_count_to_use_import_on_demand = 9999 ij_kotlin_parameter_annotation_wrap = off ij_kotlin_space_after_comma = true ij_kotlin_space_after_extend_colon = true @@ -552,72 +90,5 @@ ij_kotlin_spaces_around_when_arrow = true ij_kotlin_variable_annotation_wrap = off ij_kotlin_while_on_new_line = false ij_kotlin_wrap_elvis_expressions = 1 -ij_kotlin_wrap_expression_body_functions = 0 +ij_kotlin_wrap_expression_body_functions = 1 ij_kotlin_wrap_first_method_in_call_chain = false - -[{*.har,*.json}] -indent_size = 2 -ij_json_keep_blank_lines_in_code = 0 -ij_json_keep_indents_on_empty_lines = false -ij_json_keep_line_breaks = true -ij_json_space_after_colon = true -ij_json_space_after_comma = true -ij_json_space_before_colon = true -ij_json_space_before_comma = false -ij_json_spaces_within_braces = false -ij_json_spaces_within_brackets = false -ij_json_wrap_long_lines = false - -[{*.htm,*.html,*.sht,*.shtm,*.shtml}] -ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 -ij_html_align_attributes = true -ij_html_align_text = false -ij_html_attribute_wrap = normal -ij_html_block_comment_at_first_column = true -ij_html_do_not_align_children_of_min_lines = 0 -ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p -ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot -ij_html_enforce_quotes = false -ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var -ij_html_keep_blank_lines = 2 -ij_html_keep_indents_on_empty_lines = false -ij_html_keep_line_breaks = true -ij_html_keep_line_breaks_in_text = true -ij_html_keep_whitespaces = false -ij_html_keep_whitespaces_inside = span,pre,textarea -ij_html_line_comment_at_first_column = true -ij_html_new_line_after_last_attribute = never -ij_html_new_line_before_first_attribute = never -ij_html_quote_style = double -ij_html_remove_new_line_before_tags = br -ij_html_space_after_tag_name = false -ij_html_space_around_equality_in_attribute = false -ij_html_space_inside_empty_tag = false -ij_html_text_wrap = normal -ij_html_uniform_ident = false - -[{*.markdown,*.md}] -ij_markdown_force_one_space_after_blockquote_symbol = true -ij_markdown_force_one_space_after_header_symbol = true -ij_markdown_force_one_space_after_list_bullet = true -ij_markdown_force_one_space_between_words = true -ij_markdown_keep_indents_on_empty_lines = false -ij_markdown_max_lines_around_block_elements = 1 -ij_markdown_max_lines_around_header = 1 -ij_markdown_max_lines_between_paragraphs = 1 -ij_markdown_min_lines_around_block_elements = 1 -ij_markdown_min_lines_around_header = 1 -ij_markdown_min_lines_between_paragraphs = 1 - -[{*.yaml,*.yml}] -indent_size = 2 -ij_yaml_align_values_properties = do_not_align -ij_yaml_autoinsert_sequence_marker = true -ij_yaml_block_mapping_on_new_line = false -ij_yaml_indent_sequence_value = true -ij_yaml_keep_indents_on_empty_lines = false -ij_yaml_keep_line_breaks = true -ij_yaml_sequence_on_new_line = false -ij_yaml_space_before_colon = false -ij_yaml_spaces_within_braces = true -ij_yaml_spaces_within_brackets = true From 733d5d6b82e8ff036a827b0ab5ee79823b135c47 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 24 Oct 2023 10:21:10 +0200 Subject: [PATCH 151/155] Decrease continuation_indent from 8 to 4 --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index bc214f67..a9ff980b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ indent_style = space insert_final_newline = true max_line_length = 100 indent_size = 4 -ij_continuation_indent_size = 8 +ij_continuation_indent_size = 4 # was 8 ij_java_names_count_to_use_import_on_demand = 9999 ij_kotlin_align_in_columns_case_branch = false ij_kotlin_align_multiline_binary_operation = false From e9d8ddc57b5a4f5f5fd48b88c4d9fbec31dc5d2e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 25 Oct 2023 19:07:52 +0200 Subject: [PATCH 152/155] Kotlin conversion: SignatureValidator --- .../consumer/SignatureValidator.java | 681 ----------------- .../extensions/PGPSignatureExtensions.kt | 11 + .../signature/consumer/SignatureValidator.kt | 693 ++++++++++++++++++ 3 files changed, 704 insertions(+), 681 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidator.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java deleted file mode 100644 index 18bf5883..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java +++ /dev/null @@ -1,681 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.consumer; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; - -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.NotationData; -import org.bouncycastle.bcpg.sig.SignatureCreationTime; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SignatureSubpacket; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.policy.Policy; -import org.pgpainless.signature.SignatureUtils; -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; -import org.pgpainless.util.DateUtil; -import org.pgpainless.util.NotationRegistry; - -/** - * A collection of validators that perform validation steps over signatures. - */ -public abstract class SignatureValidator { - - public abstract void verify(PGPSignature signature) throws SignatureValidationException; - - /** - * Check, whether there is the possibility that the given signature was created by the given key. - * {@link #verify(PGPSignature)} throws a {@link SignatureValidationException} if we can say with certainty that - * the signature was not created by the given key (e.g. if the sig carries another issuer, issuer fingerprint packet). - * - * If there is no information found in the signature about who created it (no issuer, no fingerprint), - * {@link #verify(PGPSignature)} will simply return since it is plausible that the given key created the sig. - * - * @param signingKey signing key - * @return validator that throws a {@link SignatureValidationException} if the signature was not possibly made by - * the given key. - */ - public static SignatureValidator wasPossiblyMadeByKey(PGPPublicKey signingKey) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - OpenPgpFingerprint signingKeyFingerprint = OpenPgpFingerprint.of(signingKey); - - Long issuer = SignatureSubpacketsUtil.getIssuerKeyIdAsLong(signature); - if (issuer != null) { - if (issuer != signingKey.getKeyID()) { - throw new SignatureValidationException("Signature was not created by " + - signingKeyFingerprint + " (signature issuer: " + Long.toHexString(issuer) + ")"); - } - } - - OpenPgpFingerprint fingerprint = - SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature); - if (fingerprint != null) { - if (!fingerprint.equals(signingKeyFingerprint)) { - throw new SignatureValidationException("Signature was not created by " + - signingKeyFingerprint + " (signature fingerprint: " + fingerprint + ")"); - } - } - - // No issuer information found, so we cannot rule out that we did not create the sig - } - }; - - } - - /** - * Verify that a subkey binding signature - if the subkey is signing-capable - contains a valid primary key - * binding signature. - * - * @param primaryKey primary key - * @param subkey subkey - * @param policy policy - * @param referenceDate reference date for signature verification - * @return validator - */ - public static SignatureValidator hasValidPrimaryKeyBindingSignatureIfRequired(PGPPublicKey primaryKey, - PGPPublicKey subkey, Policy policy, - Date referenceDate) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - if (!PublicKeyAlgorithm.requireFromId(signature.getKeyAlgorithm()).isSigningCapable()) { - // subkey is not signing capable -> No need to process embedded sigs - return; - } - - KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(signature); - if (keyFlags == null) { - return; - } - if (!KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.SIGN_DATA) - && !KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.CERTIFY_OTHER)) { - return; - } - - try { - PGPSignatureList embeddedSignatures = SignatureSubpacketsUtil.getEmbeddedSignature(signature); - if (embeddedSignatures == null) { - throw new SignatureValidationException( - "Missing primary key binding signature on signing capable subkey " + - Long.toHexString(subkey.getKeyID()), Collections.emptyMap()); - } - - boolean hasValidPrimaryKeyBinding = false; - Map rejectedEmbeddedSigs = new ConcurrentHashMap<>(); - for (PGPSignature embedded : embeddedSignatures) { - - if (SignatureType.valueOf(embedded.getSignatureType()) == SignatureType.PRIMARYKEY_BINDING) { - - try { - signatureStructureIsAcceptable(subkey, policy).verify(embedded); - signatureIsEffective(referenceDate).verify(embedded); - correctPrimaryKeyBindingSignature(primaryKey, subkey).verify(embedded); - - hasValidPrimaryKeyBinding = true; - break; - } catch (SignatureValidationException e) { - rejectedEmbeddedSigs.put(embedded, e); - } - } - } - - if (!hasValidPrimaryKeyBinding) { - throw new SignatureValidationException( - "Missing primary key binding signature on signing capable subkey " + - Long.toHexString(subkey.getKeyID()), rejectedEmbeddedSigs); - } - } catch (PGPException e) { - throw new SignatureValidationException("Cannot process list of embedded signatures.", e); - } - } - }; - } - - /** - * Verify that a signature has an acceptable structure. - * - * @param signingKey signing key - * @param policy policy - * @return validator - */ - public static SignatureValidator signatureStructureIsAcceptable(PGPPublicKey signingKey, Policy policy) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - signatureIsNotMalformed(signingKey).verify(signature); - if (signature.getVersion() >= 4) { - signatureDoesNotHaveCriticalUnknownNotations(policy.getNotationRegistry()).verify(signature); - signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature); - } - signatureUsesAcceptableHashAlgorithm(policy).verify(signature); - signatureUsesAcceptablePublicKeyAlgorithm(policy, signingKey).verify(signature); - } - }; - } - - /** - * Verify that a signature was made using an acceptable {@link PublicKeyAlgorithm}. - * - * @param policy policy - * @param signingKey signing key - * @return validator - */ - public static SignatureValidator signatureUsesAcceptablePublicKeyAlgorithm(Policy policy, - PGPPublicKey signingKey) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - PublicKeyAlgorithm algorithm = PublicKeyAlgorithm.requireFromId(signingKey.getAlgorithm()); - int bitStrength = signingKey.getBitStrength(); - if (bitStrength == -1) { - throw new SignatureValidationException("Cannot determine bit strength of signing key."); - } - if (!policy.getPublicKeyAlgorithmPolicy().isAcceptable(algorithm, bitStrength)) { - throw new SignatureValidationException("Signature was made using unacceptable key. " + - algorithm + " (" + bitStrength + - " bits) is not acceptable according to the public key algorithm policy."); - } - } - }; - } - - /** - * Verify that a signature uses an acceptable {@link HashAlgorithm}. - * - * @param policy policy - * @return validator - */ - public static SignatureValidator signatureUsesAcceptableHashAlgorithm(Policy policy) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - try { - HashAlgorithm hashAlgorithm = HashAlgorithm.requireFromId(signature.getHashAlgorithm()); - Policy.HashAlgorithmPolicy hashAlgorithmPolicy = - getHashAlgorithmPolicyForSignature(signature, policy); - if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm(), signature.getCreationTime())) { - throw new SignatureValidationException("Signature uses unacceptable hash algorithm " + - hashAlgorithm + " (Signature creation time: " + - DateUtil.formatUTCDate(signature.getCreationTime()) + ")"); - } - } catch (NoSuchElementException e) { - throw new SignatureValidationException("Signature uses unknown hash algorithm " + - signature.getHashAlgorithm()); - } - } - }; - } - - /** - * Return the applicable {@link Policy.HashAlgorithmPolicy} for the given {@link PGPSignature}. - * Revocation signatures are being policed using a different policy than non-revocation signatures. - * - * @param signature signature - * @param policy revocation policy for revocation sigs, normal policy for non-rev sigs - * @return policy - */ - private static Policy.HashAlgorithmPolicy getHashAlgorithmPolicyForSignature(PGPSignature signature, - Policy policy) { - SignatureType type = SignatureType.valueOf(signature.getSignatureType()); - Policy.HashAlgorithmPolicy hashAlgorithmPolicy; - if (type == SignatureType.CERTIFICATION_REVOCATION || type == SignatureType.KEY_REVOCATION || - type == SignatureType.SUBKEY_REVOCATION) { - hashAlgorithmPolicy = policy.getRevocationSignatureHashAlgorithmPolicy(); - } else { - hashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy(); - } - return hashAlgorithmPolicy; - } - - /** - * Verify that a signature does not carry critical unknown notations. - * - * @param registry notation registry of known notations - * @return validator - */ - public static SignatureValidator signatureDoesNotHaveCriticalUnknownNotations(NotationRegistry registry) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - List hashedNotations = SignatureSubpacketsUtil.getHashedNotationData(signature); - for (NotationData notation : hashedNotations) { - if (!notation.isCritical()) { - continue; - } - if (!registry.isKnownNotation(notation.getNotationName())) { - throw new SignatureValidationException("Signature contains unknown critical notation '" + - notation.getNotationName() + "' in its hashed area."); - } - } - } - }; - } - - /** - * Verify that a signature does not contain critical unknown subpackets. - * - * @return validator - */ - public static SignatureValidator signatureDoesNotHaveCriticalUnknownSubpackets() { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); - for (int criticalTag : hashedSubpackets.getCriticalTags()) { - try { - SignatureSubpacket.requireFromCode(criticalTag); - } catch (NoSuchElementException e) { - throw new SignatureValidationException( - "Signature contains unknown critical subpacket of type " + - Long.toHexString(criticalTag)); - } - } - } - }; - } - - /** - * Verify that a signature is effective right now. - * - * @return validator - */ - public static SignatureValidator signatureIsEffective() { - return signatureIsEffective(new Date()); - } - - /** - * Verify that a signature is effective at the given reference date. - * - * @param referenceDate reference date for signature verification - * @return validator - */ - public static SignatureValidator signatureIsEffective(Date referenceDate) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - signatureIsAlreadyEffective(referenceDate).verify(signature); - signatureIsNotYetExpired(referenceDate).verify(signature); - } - }; - } - - /** - * Verify that a signature was created prior to the given reference date. - * - * @param referenceDate reference date for signature verification - * @return validator - */ - public static SignatureValidator signatureIsAlreadyEffective(Date referenceDate) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - Date signatureCreationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime(); - // Hard revocations are always effective - if (SignatureUtils.isHardRevocation(signature)) { - return; - } - - if (signatureCreationTime.after(referenceDate)) { - throw new SignatureValidationException("Signature was created at " + signatureCreationTime + - " and is therefore not yet valid at " + referenceDate); - } - } - }; - } - - /** - * Verify that a signature is not yet expired. - * - * @param referenceDate reference date for signature verification - * @return validator - */ - public static SignatureValidator signatureIsNotYetExpired(Date referenceDate) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - // Hard revocations do not expire - if (SignatureUtils.isHardRevocation(signature)) { - return; - } - - Date signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature); - if (signatureExpirationTime != null && signatureExpirationTime.before(referenceDate)) { - throw new SignatureValidationException("Signature is already expired (expiration: " + - signatureExpirationTime + ", validation: " + referenceDate + ")"); - } - } - }; - } - - /** - * Verify that a signature is not malformed. - * A signature is malformed if it has no hashed creation time subpacket, - * it predates the creation time of the signing key, or it predates the creation date - * of the signing key binding signature. - * - * @param creator signing key - * @return validator - */ - public static SignatureValidator signatureIsNotMalformed(PGPPublicKey creator) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - if (signature.getVersion() >= 4) { - signatureHasHashedCreationTime().verify(signature); - } - signatureDoesNotPredateSigningKey(creator).verify(signature); - if (signature.getSignatureType() != SignatureType.PRIMARYKEY_BINDING.getCode()) { - signatureDoesNotPredateSigningKeyBindingDate(creator).verify(signature); - } - } - }; - } - - public static SignatureValidator signatureDoesNotPredateSignee(PGPPublicKey signee) { - return signatureDoesNotPredateKeyCreation(signee); - } - - /** - * Verify that a signature has a hashed creation time subpacket. - * - * @return validator - */ - public static SignatureValidator signatureHasHashedCreationTime() { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - SignatureCreationTime creationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature); - if (creationTime == null) { - throw new SignatureValidationException( - "Malformed signature. Signature has no signature creation time subpacket in its hashed area."); - } - } - }; - } - - /** - * Verify that a signature does not predate the creation time of the signing key. - * - * @param key signing key - * @return validator - */ - public static SignatureValidator signatureDoesNotPredateSigningKey(PGPPublicKey key) { - return signatureDoesNotPredateKeyCreation(key); - } - - /** - * Verify that a signature does not predate the creation time of the given key. - * - * @param key key - * @return validator - */ - public static SignatureValidator signatureDoesNotPredateKeyCreation(PGPPublicKey key) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - Date keyCreationTime = key.getCreationTime(); - Date signatureCreationTime = signature.getCreationTime(); - - if (keyCreationTime.after(signatureCreationTime)) { - throw new SignatureValidationException("Signature predates key (key creation: " + - keyCreationTime + ", signature creation: " + signatureCreationTime + ")"); - } - } - }; - } - - /** - * Verify that a signature does not predate the binding date of the signing key. - * - * @param signingKey signing key - * @return validator - */ - public static SignatureValidator signatureDoesNotPredateSigningKeyBindingDate(PGPPublicKey signingKey) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - if (signingKey.isMasterKey()) { - return; - } - boolean predatesBindingSig = true; - Iterator bindingSignatures = - signingKey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode()); - if (!bindingSignatures.hasNext()) { - throw new SignatureValidationException("Signing subkey does not have a subkey binding signature."); - } - while (bindingSignatures.hasNext()) { - PGPSignature bindingSig = bindingSignatures.next(); - if (!bindingSig.getCreationTime().after(signature.getCreationTime())) { - predatesBindingSig = false; - } - } - if (predatesBindingSig) { - throw new SignatureValidationException( - "Signature was created before the signing key was bound to the key ring."); - } - } - }; - } - - /** - * Verify that a subkey binding signature is correct. - * - * @param primaryKey primary key - * @param subkey subkey - * @return validator - */ - public static SignatureValidator correctSubkeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - if (primaryKey.getKeyID() == subkey.getKeyID()) { - throw new SignatureValidationException("Primary key cannot be its own subkey."); - } - try { - signature.init(ImplementationFactory.getInstance() - .getPgpContentVerifierBuilderProvider(), primaryKey); - boolean valid = signature.verifyCertification(primaryKey, subkey); - if (!valid) { - throw new SignatureValidationException("Signature is not correct."); - } - } catch (PGPException | ClassCastException e) { - throw new SignatureValidationException("Cannot verify subkey binding signature correctness", e); - } - } - }; - } - - /** - * Verify that a primary key binding signature is correct. - * - * @param primaryKey primary key - * @param subkey subkey - * @return validator - */ - public static SignatureValidator correctPrimaryKeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - try { - signature.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), subkey); - boolean valid = signature.verifyCertification(primaryKey, subkey); - if (!valid) { - throw new SignatureValidationException("Primary Key Binding Signature is not correct."); - } - } catch (PGPException | ClassCastException e) { - throw new SignatureValidationException( - "Cannot verify primary key binding signature correctness", e); - } - } - }; - } - - /** - * Verify that a direct-key signature is correct. - * - * @param signer signing key - * @param signee signed key - * @return validator - */ - public static SignatureValidator correctSignatureOverKey(PGPPublicKey signer, PGPPublicKey signee) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - try { - signature.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), signer); - boolean valid; - if (signer.getKeyID() == signee.getKeyID() || signature.getSignatureType() == PGPSignature.DIRECT_KEY) { - valid = signature.verifyCertification(signee); - } else { - valid = signature.verifyCertification(signer, signee); - } - if (!valid) { - throw new SignatureValidationException("Signature is not correct."); - } - } catch (PGPException | ClassCastException e) { - throw new SignatureValidationException("Cannot verify direct-key signature correctness", e); - } - } - }; - } - - /** - * Verify that a signature is a certification signature. - * - * @return validator - */ - public static SignatureValidator signatureIsCertification() { - return signatureIsOfType( - SignatureType.POSITIVE_CERTIFICATION, - SignatureType.CASUAL_CERTIFICATION, - SignatureType.GENERIC_CERTIFICATION, - SignatureType.NO_CERTIFICATION); - } - - /** - * Verify that a signature type equals one of the given {@link SignatureType SignatureTypes}. - * - * @param signatureTypes one or more signature types - * @return validator - */ - public static SignatureValidator signatureIsOfType(SignatureType... signatureTypes) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - SignatureType type = SignatureType.valueOf(signature.getSignatureType()); - boolean valid = false; - for (SignatureType allowed : signatureTypes) { - if (type == allowed) { - valid = true; - break; - } - } - if (!valid) { - throw new SignatureValidationException("Signature is of type " + type + " while only " + - Arrays.toString(signatureTypes) + " are allowed here."); - } - } - }; - } - - /** - * Verify that a signature over a user-id is correct. - * - * @param userId user-id - * @param certifiedKey key carrying the user-id - * @param certifyingKey key that created the signature. - * @return validator - */ - public static SignatureValidator correctSignatureOverUserId(String userId, PGPPublicKey certifiedKey, - PGPPublicKey certifyingKey) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - try { - signature.init(ImplementationFactory.getInstance() - .getPgpContentVerifierBuilderProvider(), certifyingKey); - boolean valid = signature.verifyCertification(userId, certifiedKey); - if (!valid) { - throw new SignatureValidationException("Signature over user-id '" + userId + - "' is not correct."); - } - } catch (PGPException | ClassCastException e) { - throw new SignatureValidationException("Cannot verify signature over user-id '" + - userId + "'.", e); - } - } - }; - } - - /** - * Verify that a signature over a user-attribute packet is correct. - * - * @param userAttributes user attributes - * @param certifiedKey key carrying the user-attributes - * @param certifyingKey key that created the certification signature - * @return validator - */ - public static SignatureValidator correctSignatureOverUserAttributes(PGPUserAttributeSubpacketVector userAttributes, - PGPPublicKey certifiedKey, - PGPPublicKey certifyingKey) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - try { - signature.init(ImplementationFactory.getInstance() - .getPgpContentVerifierBuilderProvider(), certifyingKey); - boolean valid = signature.verifyCertification(userAttributes, certifiedKey); - if (!valid) { - throw new SignatureValidationException("Signature over user-attribute vector is not correct."); - } - } catch (PGPException | ClassCastException e) { - throw new SignatureValidationException("Cannot verify signature over user-attribute vector.", e); - } - } - }; - } - - public static SignatureValidator signatureWasCreatedInBounds(Date notBefore, Date notAfter) { - return new SignatureValidator() { - @Override - public void verify(PGPSignature signature) throws SignatureValidationException { - Date timestamp = signature.getCreationTime(); - if (notBefore != null && timestamp.before(notBefore)) { - throw new SignatureValidationException( - "Signature was made before the earliest allowed signature creation time. Created: " + - DateUtil.formatUTCDate(timestamp) + " Earliest allowed: " + - DateUtil.formatUTCDate(notBefore)); - } - if (notAfter != null && timestamp.after(notAfter)) { - throw new SignatureValidationException( - "Signature was made after the latest allowed signature creation time. Created: " + - DateUtil.formatUTCDate(timestamp) + " Latest allowed: " + - DateUtil.formatUTCDate(notAfter)); - } - } - }; - } - -} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt index 2be011bd..5547e48b 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -8,6 +8,8 @@ import java.util.* import openpgp.plusSeconds import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.RevocationState import org.pgpainless.algorithm.SignatureType import org.pgpainless.key.OpenPgpFingerprint @@ -94,3 +96,12 @@ fun PGPSignature?.toRevocationState() = val PGPSignature.fingerprint: OpenPgpFingerprint? get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this) + +val PGPSignature.publicKeyAlgorithm: PublicKeyAlgorithm + get() = PublicKeyAlgorithm.requireFromId(keyAlgorithm) + +val PGPSignature.signatureHashAlgorithm: HashAlgorithm + get() = HashAlgorithm.requireFromId(hashAlgorithm) + +fun PGPSignature.isOfType(type: SignatureType): Boolean = + SignatureType.requireFromCode(signatureType) == type diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidator.kt new file mode 100644 index 00000000..c7cbd6fd --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidator.kt @@ -0,0 +1,693 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import java.lang.Exception +import java.util.Date +import openpgp.formatUTC +import openpgp.openPgpKeyId +import org.bouncycastle.extensions.fingerprint +import org.bouncycastle.extensions.isHardRevocation +import org.bouncycastle.extensions.isOfType +import org.bouncycastle.extensions.publicKeyAlgorithm +import org.bouncycastle.extensions.signatureExpirationDate +import org.bouncycastle.extensions.signatureHashAlgorithm +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.algorithm.SignatureSubpacket +import org.pgpainless.algorithm.SignatureType +import org.pgpainless.exception.SignatureValidationException +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.policy.Policy +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil +import org.pgpainless.util.NotationRegistry + +abstract class SignatureValidator { + + @Throws(SignatureValidationException::class) abstract fun verify(signature: PGPSignature) + + companion object { + + /** + * Check, whether there is the possibility that the given signature was created by the given + * key. [verify] throws a [SignatureValidationException] if we can say with certainty that + * the signature was not created by the given key (e.g. if the sig carries another issuer, + * issuer fingerprint packet). + * + * If there is no information found in the signature about who created it (no issuer, no + * fingerprint), [verify] will simply return since it is plausible that the given key + * created the sig. + * + * @param signingKey signing key + * @return validator that throws a [SignatureValidationException] if the signature was not + * possibly made by the given key. + */ + @JvmStatic + fun wasPossiblyMadeByKey(signingKey: PGPPublicKey): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + val signingKeyFingerprint = OpenPgpFingerprint.of(signingKey) + val issuer = SignatureSubpacketsUtil.getIssuerKeyIdAsLong(signature) + + if (issuer != null) { + if (issuer != signingKey.keyID) { + throw SignatureValidationException( + "Signature was not created by" + + " $signingKeyFingerprint (signature issuer: ${issuer.openPgpKeyId()})") + } + } + + if (signature.fingerprint != null && + signature.fingerprint != signingKeyFingerprint) { + throw SignatureValidationException( + "Signature was not created by" + + " $signingKeyFingerprint (signature fingerprint: ${signature.fingerprint})") + } + } + + // No issuer information found, so we cannot rule out that we did not create the sig + } + } + + /** + * Verify that a subkey binding signature - if the subkey is signing-capable - contains a + * valid primary key binding signature. + * + * @param primaryKey primary key + * @param subkey subkey + * @param policy policy + * @param referenceDate reference date for signature verification + * @return validator + */ + @JvmStatic + fun hasValidPrimaryKeyBindingSignatureIfRequired( + primaryKey: PGPPublicKey, + subkey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (!signature.publicKeyAlgorithm.isSigningCapable()) { + // subkey is not signing capable -> No need to process embedded signatures + return + } + + // Make sure we have key flags + SignatureSubpacketsUtil.getKeyFlags(signature)?.let { + if (!KeyFlag.hasKeyFlag(it.flags, KeyFlag.SIGN_DATA) && + !KeyFlag.hasKeyFlag(it.flags, KeyFlag.CERTIFY_OTHER)) { + return + } + } + ?: return + + try { + val embeddedSignatures = + SignatureSubpacketsUtil.getEmbeddedSignature(signature) + if (embeddedSignatures.isEmpty) { + throw SignatureValidationException( + "Missing primary key binding" + + " signature on signing capable subkey ${subkey.keyID.openPgpKeyId()}", + mapOf()) + } + + val rejectedEmbeddedSignatures = mutableMapOf() + if (!embeddedSignatures.any { embedded -> + if (embedded.isOfType(SignatureType.PRIMARYKEY_BINDING)) { + try { + signatureStructureIsAcceptable(subkey, policy).verify(embedded) + signatureIsEffective(referenceTime).verify(embedded) + correctPrimaryKeyBindingSignature(primaryKey, subkey) + .verify(embedded) + return@any true + } catch (e: SignatureValidationException) { + rejectedEmbeddedSignatures[embedded] = e + } + } + false + }) { + throw SignatureValidationException( + "Missing primary key binding signature on signing capable subkey ${subkey.keyID.openPgpKeyId()}", + rejectedEmbeddedSignatures) + } + } catch (e: PGPException) { + throw SignatureValidationException( + "Cannot process list of embedded signatures.", e) + } + } + } + } + + /** + * Verify that a signature has an acceptable structure. + * + * @param signingKey signing key + * @param policy policy + * @return validator + */ + @JvmStatic + fun signatureStructureIsAcceptable( + signingKey: PGPPublicKey, + policy: Policy + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + signatureIsNotMalformed(signingKey).verify(signature) + if (signature.version >= 4) { + signatureDoesNotHaveCriticalUnknownNotations(policy.notationRegistry) + .verify(signature) + signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature) + } + signatureUsesAcceptableHashAlgorithm(policy).verify(signature) + signatureUsesAcceptablePublicKeyAlgorithm(policy, signingKey).verify(signature) + } + } + } + + /** + * Verify that a signature was made using an acceptable [PublicKeyAlgorithm]. + * + * @param policy policy + * @param signingKey signing key + * @return validator + */ + @JvmStatic + fun signatureUsesAcceptablePublicKeyAlgorithm( + policy: Policy, + signingKey: PGPPublicKey + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (signingKey.bitStrength == -1) { + throw SignatureValidationException( + "Cannot determine bit strength of signing key.") + } + if (!policy.publicKeyAlgorithmPolicy.isAcceptable( + signingKey.publicKeyAlgorithm, signingKey.bitStrength)) { + throw SignatureValidationException( + "Signature was made using unacceptable key. " + + "${signingKey.publicKeyAlgorithm} (${signingKey.bitStrength} bits) is " + + "not acceptable according to the public key algorithm policy.") + } + } + } + } + + @JvmStatic + fun signatureUsesAcceptableHashAlgorithm(policy: Policy): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + try { + val algorithmPolicy = getHashAlgorithmPolicyForSignature(signature, policy) + if (!algorithmPolicy.isAcceptable( + signature.signatureHashAlgorithm, signature.creationTime)) { + throw SignatureValidationException( + "Signature uses unacceptable" + + " hash algorithm ${signature.signatureHashAlgorithm}" + + " (Signature creation time: ${signature.creationTime.formatUTC()})") + } + } catch (e: NoSuchElementException) { + throw SignatureValidationException( + "Signature uses unknown hash" + " algorithm ${signature.hashAlgorithm}") + } + } + } + } + + /** + * Return the applicable [Policy.HashAlgorithmPolicy] for the given [PGPSignature]. + * Revocation signatures are being policed using a different policy than non-revocation + * signatures. + * + * @param signature signature + * @param policy revocation policy for revocation sigs, normal policy for non-rev sigs + * @return policy + */ + @JvmStatic + private fun getHashAlgorithmPolicyForSignature( + signature: PGPSignature, + policy: Policy + ): Policy.HashAlgorithmPolicy { + val type = SignatureType.requireFromCode(signature.signatureType) + return when (type) { + SignatureType.CERTIFICATION_REVOCATION, + SignatureType.KEY_REVOCATION, + SignatureType.SUBKEY_REVOCATION -> policy.revocationSignatureHashAlgorithmPolicy + else -> policy.signatureHashAlgorithmPolicy + } + } + + /** + * Verify that a signature does not carry critical unknown notations. + * + * @param registry notation registry of known notations + * @return validator + */ + @JvmStatic + fun signatureDoesNotHaveCriticalUnknownNotations( + registry: NotationRegistry + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + SignatureSubpacketsUtil.getHashedNotationData(signature) + .filter { it.isCritical && !registry.isKnownNotation(it.notationName) } + .forEach { + throw SignatureValidationException( + "Signature contains unknown critical notation '${it.notationName}' in its hashed area.") + } + } + } + } + + /** + * Verify that a signature does not contain critical unknown subpackets. + * + * @return validator + */ + @JvmStatic + fun signatureDoesNotHaveCriticalUnknownSubpackets(): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + signature.hashedSubPackets.criticalTags.forEach { + try { + SignatureSubpacket.requireFromCode(it) + } catch (e: NoSuchElementException) { + throw SignatureValidationException( + "Signature contains unknown critical subpacket of type 0x${Integer.toHexString(it)}") + } + } + } + } + } + + /** + * Verify that a signature is effective at the given reference date. + * + * @param referenceTime reference date for signature verification + * @return validator + */ + @JvmStatic + @JvmOverloads + fun signatureIsEffective(referenceTime: Date = Date()): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + signatureIsAlreadyEffective(referenceTime).verify(signature) + signatureIsNotYetExpired(referenceTime).verify(signature) + } + } + } + + /** + * Verify that a signature was created prior to the given reference date. + * + * @param referenceTime reference date for signature verification + * @return validator + */ + @JvmStatic + fun signatureIsAlreadyEffective(referenceTime: Date): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (signature.isHardRevocation) { + return + } + if (signature.creationTime > referenceTime) { + throw SignatureValidationException( + "Signature was created at ${signature.creationTime.formatUTC()} and" + + " is therefore not yet valid at ${referenceTime.formatUTC()}") + } + } + } + } + + /** + * Verify that a signature is not yet expired. + * + * @param referenceTime reference date for signature verification + * @return validator + */ + @JvmStatic + fun signatureIsNotYetExpired(referenceTime: Date): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (signature.isHardRevocation) { + return + } + val expirationDate = signature.signatureExpirationDate + if (expirationDate != null && expirationDate < referenceTime) { + throw SignatureValidationException( + "Signature is already expired " + + "(expiration: ${expirationDate.formatUTC()}," + + " validation: ${referenceTime.formatUTC()})") + } + } + } + } + + /** + * Verify that a signature is not malformed. A signature is malformed if it has no hashed + * creation time subpacket, it predates the creation time of the signing key, or it predates + * the creation date of the signing key binding signature. + * + * @param signingKey signing key + * @return validator + */ + @JvmStatic + fun signatureIsNotMalformed(signingKey: PGPPublicKey): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (signature.version >= 4) { + signatureHasHashedCreationTime().verify(signature) + } + signatureDoesNotPredateSigningKey(signingKey).verify(signature) + if (!signature.isOfType(SignatureType.PRIMARYKEY_BINDING)) { + signatureDoesNotPredateSigningKeyBindingDate(signingKey).verify(signature) + } + } + } + } + + @JvmStatic + fun signatureDoesNotPredateSignee(signee: PGPPublicKey): SignatureValidator { + return signatureDoesNotPredateKeyCreation(signee) + } + + /** + * Verify that a signature does not predate the creation time of the signing key. + * + * @param key signing key + * @return validator + */ + @JvmStatic + fun signatureDoesNotPredateSigningKey(signingKey: PGPPublicKey): SignatureValidator { + return signatureDoesNotPredateKeyCreation(signingKey) + } + + /** + * Verify that a signature does not predate the creation time of the given key. + * + * @param key key + * @return validator + */ + @JvmStatic + fun signatureDoesNotPredateKeyCreation(key: PGPPublicKey): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (key.creationTime > signature.creationTime) { + throw SignatureValidationException( + "Signature predates key" + + " (key creation: ${key.creationTime.formatUTC()}," + + " signature creation: ${signature.creationTime.formatUTC()})") + } + } + } + } + + /** + * Verify that a signature has a hashed creation time subpacket. + * + * @return validator + */ + @JvmStatic + fun signatureHasHashedCreationTime(): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (SignatureSubpacketsUtil.getSignatureCreationTime(signature) == null) { + throw SignatureValidationException( + "Malformed signature." + + "Signature has no signature creation time subpacket in its hashed area.") + } + } + } + } + + /** + * Verify that a signature does not predate the binding date of the signing key. + * + * @param signingKey signing key + * @return validator + */ + @JvmStatic + fun signatureDoesNotPredateSigningKeyBindingDate( + signingKey: PGPPublicKey + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (signingKey.isMasterKey) { + return + } + if (signingKey + .getSignaturesOfType(SignatureType.SUBKEY_BINDING.code) + .asSequence() + .map { + if (signature.creationTime < it.creationTime) { + throw SignatureValidationException( + "Signature was created " + + "before the signing key was bound to the certificate.") + } + } + .none()) { + throw SignatureValidationException( + "Signing subkey does not have a subkey binding signature.") + } + } + } + } + + /** + * Verify that a subkey binding signature is correct. + * + * @param primaryKey primary key + * @param subkey subkey + * @return validator + */ + @JvmStatic + fun correctSubkeyBindingSignature( + primaryKey: PGPPublicKey, + subkey: PGPPublicKey + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (primaryKey.keyID == subkey.keyID) { + throw SignatureValidationException("Primary key cannot be its own subkey.") + } + try { + signature.init( + ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider, + primaryKey) + if (!signature.verifyCertification(primaryKey, subkey)) { + throw SignatureValidationException("Signature is not correct.") + } + } catch (e: PGPException) { + throw SignatureValidationException( + "Cannot verify subkey binding signature correctness", e) + } catch (e: ClassCastException) { + throw SignatureValidationException( + "Cannot verify subkey binding signature correctness", e) + } + } + } + } + + /** + * Verify that a primary key binding signature is correct. + * + * @param primaryKey primary key + * @param subkey subkey + * @return validator + */ + @JvmStatic + fun correctPrimaryKeyBindingSignature( + primaryKey: PGPPublicKey, + subkey: PGPPublicKey + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (primaryKey.keyID == subkey.keyID) { + throw SignatureValidationException("Primary key cannot be its own subkey.") + } + try { + signature.init( + ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider, + subkey) + if (!signature.verifyCertification(primaryKey, subkey)) { + throw SignatureValidationException( + "Primary Key Binding Signature is not correct.") + } + } catch (e: PGPException) { + throw SignatureValidationException( + "Cannot verify primary key binding signature correctness", e) + } catch (e: ClassCastException) { + throw SignatureValidationException( + "Cannot verify primary key binding signature correctness", e) + } + } + } + } + + /** + * Verify that a direct-key signature is correct. + * + * @param signingKey signing key + * @param signedKey signed key + * @return validator + */ + @JvmStatic + fun correctSignatureOverKey( + signingKey: PGPPublicKey, + signedKey: PGPPublicKey + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + try { + signature.init( + ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider, + signingKey) + val valid = + if (signingKey.keyID == signedKey.keyID || + signature.isOfType(SignatureType.DIRECT_KEY)) { + signature.verifyCertification(signedKey) + } else { + signature.verifyCertification(signingKey, signedKey) + } + if (!valid) { + throw SignatureValidationException("Signature is not correct.") + } + } catch (e: PGPException) { + throw SignatureValidationException( + "Cannot verify direct-key signature correctness", e) + } catch (e: ClassCastException) { + throw SignatureValidationException( + "Cannot verify direct-key signature correctness", e) + } + } + } + } + + @JvmStatic + fun signatureIsCertification(): SignatureValidator { + return signatureIsOfType( + SignatureType.POSITIVE_CERTIFICATION, + SignatureType.CASUAL_CERTIFICATION, + SignatureType.GENERIC_CERTIFICATION, + SignatureType.NO_CERTIFICATION) + } + + /** + * Verify that a signature type equals one of the given [SignatureType]. + * + * @param signatureType one or more signature types + * @return validator + */ + @JvmStatic + fun signatureIsOfType(vararg signatureType: SignatureType): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + if (signatureType.none { signature.isOfType(it) }) { + throw SignatureValidationException( + "Signature is of type" + + " ${SignatureType.requireFromCode(signature.signatureType)}, " + + "while only ${signatureType.contentToString()} are allowed here.") + } + } + } + } + + /** + * Verify that a signature over a user-id is correct. + * + * @param userId user-id + * @param certifiedKey key carrying the user-id + * @param certifyingKey key that created the signature. + * @return validator + */ + @JvmStatic + fun correctSignatureOverUserId( + userId: CharSequence, + certifiedKey: PGPPublicKey, + certifyingKey: PGPPublicKey + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + try { + signature.init( + ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider, + certifyingKey) + if (!signature.verifyCertification(userId.toString(), certifiedKey)) { + throw SignatureValidationException( + "Signature over user-id '$userId' is not valid.") + } + } catch (e: PGPException) { + throw SignatureValidationException( + "Cannot verify signature over user-id '$userId'.", e) + } catch (e: ClassCastException) { + throw SignatureValidationException( + "Cannot verify signature over user-id '$userId'.", e) + } + } + } + } + + /** + * Verify that a signature over a user-attribute packet is correct. + * + * @param userAttributes user attributes + * @param certifiedKey key carrying the user-attributes + * @param certifyingKey key that created the certification signature + * @return validator + */ + @JvmStatic + fun correctSignatureOverUserAttributes( + userAttributes: PGPUserAttributeSubpacketVector, + certifiedKey: PGPPublicKey, + certifyingKey: PGPPublicKey + ): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + try { + signature.init( + ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider, + certifyingKey) + if (!signature.verifyCertification(userAttributes, certifiedKey)) { + throw SignatureValidationException( + "Signature over user-attributes is not correct.") + } + } catch (e: PGPException) { + throw SignatureValidationException( + "Cannot verify signature over user-attribute vector.", e) + } catch (e: ClassCastException) { + throw SignatureValidationException( + "Cannot verify signature over user-attribute vector.", e) + } + } + } + } + + @JvmStatic + fun signatureWasCreatedInBounds(notBefore: Date?, notAfter: Date?): SignatureValidator { + return object : SignatureValidator() { + override fun verify(signature: PGPSignature) { + val timestamp = signature.creationTime + if (notBefore != null && timestamp < notBefore) { + throw SignatureValidationException( + "Signature was made before the earliest allowed signature creation time." + + " Created: ${timestamp.formatUTC()}," + + " earliest allowed: ${notBefore.formatUTC()}") + } + if (notAfter != null && timestamp > notAfter) { + throw SignatureValidationException( + "Signature was made before the latest allowed signature creation time." + + " Created: ${timestamp.formatUTC()}," + + " latest allowed: ${notAfter.formatUTC()}") + } + } + } + } + } +} From dc05a492f5940012dcb5076a92c4ffdc1d5afb93 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 25 Oct 2023 19:08:03 +0200 Subject: [PATCH 153/155] Fix reuse --- .reuse/dep5 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.reuse/dep5 b/.reuse/dep5 index 96efa937..c03bbcae 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -9,6 +9,11 @@ Source: https://pgpainless.org # Copyright: $YEAR $NAME <$CONTACT> # License: ... +# GitBlameIgnore +Files: .git-blame-ignore-revs +Copyright: 2023 Paul Schaub +License: CC0-1.0 + # Documentation Files: docs/* Copyright: 2022 Paul Schaub @@ -23,6 +28,11 @@ Files: gradle* Copyright: 2015 the original author or authors. License: Apache-2.0 +# Editorconfig +Files: .editorconfig +Copyright: Facebook +License: Apache-2.0 + # PGPainless Logo Files: assets/repository-open-graph.png Copyright: 2021 Paul Schaub From 19b45644ae73d74db487a4b65052ee3ed2526027 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 26 Oct 2023 12:52:04 +0200 Subject: [PATCH 154/155] Kotlin conversion: SignatureVerifier --- .../signature/consumer/SignatureVerifier.java | 486 -------------- .../signature/consumer/package-info.java | 8 - .../pgpainless/signature/consumer/README.md | 0 .../signature/consumer/SignatureVerifier.kt | 596 ++++++++++++++++++ 4 files changed, 596 insertions(+), 494 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureVerifier.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/package-info.java rename pgpainless-core/src/main/{java => kotlin}/org/pgpainless/signature/consumer/README.md (100%) create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureVerifier.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureVerifier.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureVerifier.java deleted file mode 100644 index a55037e5..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureVerifier.java +++ /dev/null @@ -1,486 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.consumer; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.policy.Policy; - -/** - * Collection of static methods for signature verification. - * Signature verification entails validation of certain criteria (see {@link SignatureValidator}), as well as - * cryptographic verification of signature correctness. - */ -public final class SignatureVerifier { - - private SignatureVerifier() { - - } - - /** - * Verify a signature (certification or revocation) over a user-id. - * - * @param userId user-id - * @param signature certification signature - * @param signingKey key that created the certification - * @param keyWithUserId key carrying the user-id - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if signature verification is successful - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifySignatureOverUserId(String userId, PGPSignature signature, PGPPublicKey signingKey, - PGPPublicKey keyWithUserId, Policy policy, Date referenceDate) - throws SignatureValidationException { - SignatureType type = SignatureType.valueOf(signature.getSignatureType()); - switch (type) { - case GENERIC_CERTIFICATION: - case NO_CERTIFICATION: - case CASUAL_CERTIFICATION: - case POSITIVE_CERTIFICATION: - return verifyUserIdCertification(userId, signature, signingKey, keyWithUserId, policy, referenceDate); - case CERTIFICATION_REVOCATION: - return verifyUserIdRevocation(userId, signature, signingKey, keyWithUserId, policy, referenceDate); - default: - throw new SignatureValidationException("Signature is not a valid user-id certification/revocation signature: " + type); - } - } - - /** - * Verify a certification self-signature over a user-id. - * - * @param userId user-id - * @param signature certification signature - * @param primaryKey primary key - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the self-signature is verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyUserIdCertification(String userId, PGPSignature signature, PGPPublicKey primaryKey, - Policy policy, Date referenceDate) - throws SignatureValidationException { - return verifyUserIdCertification(userId, signature, primaryKey, primaryKey, policy, referenceDate); - } - - /** - * Verify a user-id certification. - * - * @param userId user-id - * @param signature certification signature - * @param signingKey key that created the certification - * @param keyWithUserId primary key that carries the user-id - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if signature verification is successful - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyUserIdCertification(String userId, PGPSignature signature, PGPPublicKey signingKey, - PGPPublicKey keyWithUserId, Policy policy, Date referenceDate) - throws SignatureValidationException { - SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature); - SignatureValidator.signatureIsCertification().verify(signature); - SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - SignatureValidator.correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature); - - return true; - } - - /** - * Verify a user-id revocation self-signature. - * - * @param userId user-id - * @param signature user-id revocation signature - * @param primaryKey primary key - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the user-id revocation signature is successfully verified - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyUserIdRevocation(String userId, PGPSignature signature, PGPPublicKey primaryKey, - Policy policy, Date referenceDate) - throws SignatureValidationException { - return verifyUserIdRevocation(userId, signature, primaryKey, primaryKey, policy, referenceDate); - } - - /** - * Verify a user-id revocation signature. - * - * @param userId user-id - * @param signature revocation signature - * @param signingKey key that created the revocation signature - * @param keyWithUserId primary key carrying the user-id - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the user-id revocation signature is successfully verified - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyUserIdRevocation(String userId, PGPSignature signature, PGPPublicKey signingKey, - PGPPublicKey keyWithUserId, Policy policy, Date referenceDate) - throws SignatureValidationException { - SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature); - SignatureValidator.signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - SignatureValidator.correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature); - - return true; - } - - /** - * Verify a certification self-signature over a user-attributes packet. - * - * @param userAttributes user attributes - * @param signature certification self-signature - * @param primaryKey primary key that carries the user-attributes - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the signature can be verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyUserAttributesCertification(PGPUserAttributeSubpacketVector userAttributes, - PGPSignature signature, PGPPublicKey primaryKey, - Policy policy, Date referenceDate) - throws SignatureValidationException { - return verifyUserAttributesCertification(userAttributes, signature, primaryKey, primaryKey, policy, - referenceDate); - } - - /** - * Verify a certification signature over a user-attributes packet. - * - * @param userAttributes user attributes - * @param signature certification signature - * @param signingKey key that created the user-attributes certification - * @param keyWithUserAttributes key that carries the user-attributes certification - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the signature can be verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyUserAttributesCertification(PGPUserAttributeSubpacketVector userAttributes, - PGPSignature signature, PGPPublicKey signingKey, - PGPPublicKey keyWithUserAttributes, Policy policy, - Date referenceDate) - throws SignatureValidationException { - SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature); - SignatureValidator.signatureIsCertification().verify(signature); - SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - SignatureValidator.correctSignatureOverUserAttributes(userAttributes, keyWithUserAttributes, signingKey) - .verify(signature); - - return true; - } - - /** - * Verify a user-attributes revocation self-signature. - * - * @param userAttributes user-attributes - * @param signature user-attributes revocation signature - * @param primaryKey primary key that carries the user-attributes - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the revocation signature can be verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyUserAttributesRevocation(PGPUserAttributeSubpacketVector userAttributes, - PGPSignature signature, PGPPublicKey primaryKey, - Policy policy, Date referenceDate) - throws SignatureValidationException { - return verifyUserAttributesRevocation(userAttributes, signature, primaryKey, primaryKey, policy, referenceDate); - } - - /** - * Verify a user-attributes revocation signature. - * - * @param userAttributes user-attributes - * @param signature revocation signature - * @param signingKey revocation key - * @param keyWithUserAttributes key that carries the user-attributes - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the revocation signature can be verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyUserAttributesRevocation(PGPUserAttributeSubpacketVector userAttributes, - PGPSignature signature, PGPPublicKey signingKey, - PGPPublicKey keyWithUserAttributes, Policy policy, - Date referenceDate) - throws SignatureValidationException { - SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature); - SignatureValidator.signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - SignatureValidator.correctSignatureOverUserAttributes(userAttributes, keyWithUserAttributes, signingKey) - .verify(signature); - - return true; - } - - /** - * Verify a subkey binding signature. - * - * @param signature binding signature - * @param primaryKey primary key - * @param subkey subkey - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the binding signature can be verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifySubkeyBindingSignature(PGPSignature signature, PGPPublicKey primaryKey, - PGPPublicKey subkey, Policy policy, Date referenceDate) - throws SignatureValidationException { - SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); - SignatureValidator.signatureDoesNotPredateSignee(subkey).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - SignatureValidator.hasValidPrimaryKeyBindingSignatureIfRequired(primaryKey, subkey, policy, referenceDate) - .verify(signature); - SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(signature); - - return true; - } - - /** - * Verify a subkey revocation signature. - * - * @param signature subkey revocation signature - * @param primaryKey primary key - * @param subkey subkey - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the subkey revocation signature can be verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifySubkeyBindingRevocation(PGPSignature signature, PGPPublicKey primaryKey, - PGPPublicKey subkey, Policy policy, Date referenceDate) - throws SignatureValidationException { - SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_REVOCATION).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); - SignatureValidator.signatureDoesNotPredateSignee(subkey).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - SignatureValidator.correctSignatureOverKey(primaryKey, subkey).verify(signature); - - return true; - } - - /** - * Verify a direct-key self-signature. - * - * @param signature signature - * @param primaryKey primary key - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the signature can be verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyDirectKeySignature(PGPSignature signature, PGPPublicKey primaryKey, - Policy policy, Date referenceDate) - throws SignatureValidationException { - return verifyDirectKeySignature(signature, primaryKey, primaryKey, policy, referenceDate); - } - - /** - * Verify a direct-key signature. - * - * @param signature signature - * @param signingKey signing key - * @param signedKey signed key - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if signature verification is successful - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyDirectKeySignature(PGPSignature signature, PGPPublicKey signingKey, - PGPPublicKey signedKey, Policy policy, Date referenceDate) - throws SignatureValidationException { - SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); - SignatureValidator.signatureDoesNotPredateSignee(signedKey).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(signature); - - return true; - } - - /** - * Verify a key revocation signature. - * - * @param signature signature - * @param primaryKey primary key - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if signature verification is successful - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyKeyRevocationSignature(PGPSignature signature, PGPPublicKey primaryKey, - Policy policy, Date referenceDate) - throws SignatureValidationException { - SignatureValidator.signatureIsOfType(SignatureType.KEY_REVOCATION).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - SignatureValidator.correctSignatureOverKey(primaryKey, primaryKey).verify(signature); - - return true; - } - - /** - * Initialize a signature and verify it afterwards by updating it with the signed data. - * - * @param signature OpenPGP signature - * @param signedData input stream containing the signed data - * @param signingKey the key that created the signature - * @param policy policy - * @param referenceDate reference date of signature verification - * @return true if the signature is successfully verified - * - * @throws SignatureValidationException if the signature verification fails for some reason - */ - public static boolean verifyUninitializedSignature(PGPSignature signature, InputStream signedData, - PGPPublicKey signingKey, Policy policy, Date referenceDate) - throws SignatureValidationException { - initializeSignatureAndUpdateWithSignedData(signature, signedData, signingKey); - return verifyInitializedSignature(signature, signingKey, policy, referenceDate); - } - - /** - * Initialize a signature and then update it with the signed data from the given {@link InputStream}. - * - * @param signature OpenPGP signature - * @param signedData input stream containing signed data - * @param signingKey key that created the signature - * - * @throws SignatureValidationException in case the signature cannot be verified for some reason - */ - public static void initializeSignatureAndUpdateWithSignedData(PGPSignature signature, InputStream signedData, - PGPPublicKey signingKey) - throws SignatureValidationException { - try { - signature.init(ImplementationFactory.getInstance().getPgpContentVerifierBuilderProvider(), signingKey); - int read; - byte[] buf = new byte[8192]; - byte lastByte = -1; - while ((read = signedData.read(buf)) != -1) { - // If we previously omitted a newline, but the stream is not yet empty, add it now - if (lastByte == (byte) '\n') { - signature.update(lastByte); - } - lastByte = buf[read - 1]; - - if (lastByte == (byte) '\n') { - // if last byte in buffer is newline, omit it for now - signature.update(buf, 0, read - 1); - } else { - // otherwise, write buffer as usual - signature.update(buf, 0, read); - } - } - } catch (PGPException e) { - throw new SignatureValidationException("Cannot init signature.", e); - } catch (IOException e) { - throw new SignatureValidationException("Cannot update signature.", e); - } - } - - /** - * Verify an initialized signature. - * An initialized signature was already updated with the signed data. - * - * @param signature OpenPGP signature - * @param signingKey key that created the signature - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if signature is verified successfully - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifyInitializedSignature(PGPSignature signature, PGPPublicKey signingKey, Policy policy, - Date referenceDate) - throws SignatureValidationException { - SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); - SignatureValidator.signatureIsEffective(referenceDate).verify(signature); - - try { - if (!signature.verify()) { - throw new SignatureValidationException("Signature is not correct."); - } - return true; - } catch (PGPException e) { - throw new SignatureValidationException("Could not verify signature correctness.", e); - } - } - - public static boolean verifyOnePassSignature(PGPSignature signature, PGPPublicKey signingKey, - OnePassSignatureCheck onePassSignature, Policy policy) - throws SignatureValidationException { - try { - SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature); - SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); - SignatureValidator.signatureIsEffective().verify(signature); - } catch (SignatureValidationException e) { - throw new SignatureValidationException("Signature is not valid: " + e.getMessage(), e); - } - - try { - if (onePassSignature.getSignature() == null) { - throw new IllegalStateException("No comparison signature provided."); - } - if (!onePassSignature.getOnePassSignature().verify(signature)) { - throw new SignatureValidationException("Bad signature of key " + - Long.toHexString(signingKey.getKeyID())); - } - } catch (PGPException e) { - throw new SignatureValidationException("Could not verify correctness of One-Pass-Signature: " + - e.getMessage(), e); - } - - return true; - } - - /** - * Verify a signature (certification or revocation) over a user-id. - * - * @param userId user-id - * @param signature self-signature - * @param primaryKey primary key that created the signature - * @param policy policy - * @param referenceDate reference date for signature verification - * @return true if the signature is successfully verified - * - * @throws SignatureValidationException if signature verification fails for some reason - */ - public static boolean verifySignatureOverUserId(String userId, PGPSignature signature, PGPPublicKey primaryKey, - Policy policy, Date referenceDate) - throws SignatureValidationException { - return verifySignatureOverUserId(userId, signature, primaryKey, primaryKey, policy, referenceDate); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/package-info.java deleted file mode 100644 index e8e53285..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to OpenPGP signature verification. - */ -package org.pgpainless.signature.consumer; diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/README.md b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/README.md similarity index 100% rename from pgpainless-core/src/main/java/org/pgpainless/signature/consumer/README.md rename to pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/README.md diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureVerifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureVerifier.kt new file mode 100644 index 00000000..77793c90 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureVerifier.kt @@ -0,0 +1,596 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import java.io.IOException +import java.io.InputStream +import java.util.* +import openpgp.openPgpKeyId +import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector +import org.pgpainless.algorithm.SignatureType +import org.pgpainless.exception.SignatureValidationException +import org.pgpainless.implementation.ImplementationFactory.Companion.getInstance +import org.pgpainless.policy.Policy +import org.pgpainless.signature.consumer.SignatureValidator.Companion.correctSignatureOverKey +import org.pgpainless.signature.consumer.SignatureValidator.Companion.correctSignatureOverUserAttributes +import org.pgpainless.signature.consumer.SignatureValidator.Companion.correctSignatureOverUserId +import org.pgpainless.signature.consumer.SignatureValidator.Companion.correctSubkeyBindingSignature +import org.pgpainless.signature.consumer.SignatureValidator.Companion.hasValidPrimaryKeyBindingSignatureIfRequired +import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureDoesNotPredateSignee +import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureIsCertification +import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureIsEffective +import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureIsOfType +import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureStructureIsAcceptable +import org.pgpainless.signature.consumer.SignatureValidator.Companion.wasPossiblyMadeByKey + +/** + * Collection of static methods for signature verification. Signature verification entails + * validation of certain criteria (see [SignatureValidator]), as well as cryptographic verification + * of signature correctness. + */ +class SignatureVerifier { + + companion object { + + /** + * Verify a signature (certification or revocation) over a user-id. + * + * @param userId user-id + * @param signature certification signature + * @param signingKey key that created the certification + * @param keyWithUserId key carrying the user-id + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if signature verification is successful + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifySignatureOverUserId( + userId: CharSequence, + signature: PGPSignature, + signingKey: PGPPublicKey, + keyWithUserId: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + val type = SignatureType.requireFromCode(signature.signatureType) + return when (type) { + SignatureType.GENERIC_CERTIFICATION, + SignatureType.NO_CERTIFICATION, + SignatureType.CASUAL_CERTIFICATION, + SignatureType.POSITIVE_CERTIFICATION -> + verifyUserIdCertification( + userId, signature, signingKey, keyWithUserId, policy, referenceTime) + SignatureType.CERTIFICATION_REVOCATION -> + verifyUserIdRevocation( + userId, signature, signingKey, keyWithUserId, policy, referenceTime) + else -> + throw SignatureValidationException( + "Signature is not a valid user-id certification/revocation signature: $type") + } + } + + /** + * Verify a certification self-signature over a user-id. + * + * @param userId user-id + * @param signature certification signature + * @param primaryKey primary key + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the self-signature is verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUserIdCertification( + userId: CharSequence, + signature: PGPSignature, + primaryKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + return verifyUserIdCertification( + userId, signature, primaryKey, primaryKey, policy, referenceTime) + } + + /** + * Verify a user-id certification. + * + * @param userId user-id + * @param signature certification signature + * @param signingKey key that created the certification + * @param keyWithUserId primary key that carries the user-id + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if signature verification is successful + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUserIdCertification( + userId: CharSequence, + signature: PGPSignature, + signingKey: PGPPublicKey, + keyWithUserId: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + wasPossiblyMadeByKey(signingKey).verify(signature) + signatureIsCertification().verify(signature) + signatureStructureIsAcceptable(signingKey, policy).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature) + + return true + } + + /** + * Verify a user-id revocation self-signature. + * + * @param userId user-id + * @param signature user-id revocation signature + * @param primaryKey primary key + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the user-id revocation signature is successfully verified + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUserIdRevocation( + userId: CharSequence, + signature: PGPSignature, + primaryKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + return verifyUserIdRevocation( + userId, signature, primaryKey, primaryKey, policy, referenceTime) + } + + /** + * Verify a user-id revocation signature. + * + * @param userId user-id + * @param signature revocation signature + * @param signingKey key that created the revocation signature + * @param keyWithUserId primary key carrying the user-id + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the user-id revocation signature is successfully verified + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUserIdRevocation( + userId: CharSequence, + signature: PGPSignature, + signingKey: PGPPublicKey, + keyWithUserId: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + wasPossiblyMadeByKey(signingKey).verify(signature) + signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature) + signatureStructureIsAcceptable(signingKey, policy).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature) + + return true + } + + /** + * Verify a certification self-signature over a user-attributes packet. + * + * @param userAttributes user attributes + * @param signature certification self-signature + * @param primaryKey primary key that carries the user-attributes + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the signature can be verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUserAttributesCertification( + userAttributes: PGPUserAttributeSubpacketVector, + signature: PGPSignature, + primaryKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + return verifyUserAttributesCertification( + userAttributes, signature, primaryKey, primaryKey, policy, referenceTime) + } + + /** + * Verify a certification signature over a user-attributes packet. + * + * @param userAttributes user attributes + * @param signature certification signature + * @param signingKey key that created the user-attributes certification + * @param keyWithAttributes key that carries the user-attributes certification + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the signature can be verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUserAttributesCertification( + userAttributes: PGPUserAttributeSubpacketVector, + signature: PGPSignature, + signingKey: PGPPublicKey, + keyWithAttributes: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + wasPossiblyMadeByKey(signingKey).verify(signature) + signatureIsCertification().verify(signature) + signatureStructureIsAcceptable(signingKey, policy).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + correctSignatureOverUserAttributes(userAttributes, keyWithAttributes, signingKey) + .verify(signature) + + return true + } + + /** + * Verify a user-attributes revocation self-signature. + * + * @param userAttributes user-attributes + * @param signature user-attributes revocation signature + * @param primaryKey primary key that carries the user-attributes + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the revocation signature can be verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUserAttributesRevocation( + userAttributes: PGPUserAttributeSubpacketVector, + signature: PGPSignature, + primaryKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + return verifyUserAttributesRevocation( + userAttributes, signature, primaryKey, primaryKey, policy, referenceTime) + } + + /** + * Verify a user-attributes revocation signature. + * + * @param userAttributes user-attributes + * @param signature revocation signature + * @param signingKey revocation key + * @param keyWithAttributes key that carries the user-attributes + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the revocation signature can be verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUserAttributesRevocation( + userAttributes: PGPUserAttributeSubpacketVector, + signature: PGPSignature, + signingKey: PGPPublicKey, + keyWithAttributes: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + wasPossiblyMadeByKey(signingKey).verify(signature) + signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature) + signatureStructureIsAcceptable(signingKey, policy).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + correctSignatureOverUserAttributes(userAttributes, keyWithAttributes, signingKey) + .verify(signature) + + return true + } + + /** + * Verify a subkey binding signature. + * + * @param signature binding signature + * @param primaryKey primary key + * @param subkey subkey + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the binding signature can be verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifySubkeyBindingSignature( + signature: PGPSignature, + primaryKey: PGPPublicKey, + subkey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature) + signatureStructureIsAcceptable(primaryKey, policy).verify(signature) + signatureDoesNotPredateSignee(subkey).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + hasValidPrimaryKeyBindingSignatureIfRequired(primaryKey, subkey, policy, referenceTime) + .verify(signature) + correctSubkeyBindingSignature(primaryKey, subkey).verify(signature) + + return true + } + + /** + * Verify a subkey revocation signature. + * + * @param signature subkey revocation signature + * @param primaryKey primary key + * @param subkey subkey + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the subkey revocation signature can be verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifySubkeyBindingRevocation( + signature: PGPSignature, + primaryKey: PGPPublicKey, + subkey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + signatureIsOfType(SignatureType.SUBKEY_REVOCATION).verify(signature) + signatureStructureIsAcceptable(primaryKey, policy).verify(signature) + signatureDoesNotPredateSignee(subkey).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + correctSignatureOverKey(primaryKey, subkey).verify(signature) + + return true + } + + /** + * Verify a direct-key self-signature. + * + * @param signature signature + * @param primaryKey primary key + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the signature can be verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyDirectKeySignature( + signature: PGPSignature, + primaryKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + return verifyDirectKeySignature( + signature, primaryKey, primaryKey, policy, referenceTime) + } + + /** + * Verify a direct-key signature. + * + * @param signature signature + * @param signingKey signing key + * @param signedKey signed key + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if signature verification is successful + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyDirectKeySignature( + signature: PGPSignature, + signingKey: PGPPublicKey, + signedKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature) + signatureStructureIsAcceptable(signingKey, policy).verify(signature) + signatureDoesNotPredateSignee(signedKey).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + correctSignatureOverKey(signingKey, signedKey).verify(signature) + + return true + } + + /** + * Verify a key revocation signature. + * + * @param signature signature + * @param primaryKey primary key + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if signature verification is successful + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyKeyRevocationSignature( + signature: PGPSignature, + primaryKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + signatureIsOfType(SignatureType.KEY_REVOCATION).verify(signature) + signatureStructureIsAcceptable(primaryKey, policy).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + correctSignatureOverKey(primaryKey, primaryKey).verify(signature) + + return true + } + + /** + * Initialize a signature and verify it afterwards by updating it with the signed data. + * + * @param signature OpenPGP signature + * @param signedData input stream containing the signed data + * @param signingKey the key that created the signature + * @param policy policy + * @param referenceTime reference date of signature verification + * @return true if the signature is successfully verified + * @throws SignatureValidationException if the signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyUninitializedSignature( + signature: PGPSignature, + signedData: InputStream, + signingKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + initializeSignatureAndUpdateWithSignedData(signature, signedData, signingKey) + return verifyInitializedSignature(signature, signingKey, policy, referenceTime) + } + + /** + * Initialize a signature and then update it with the signed data from the given + * [InputStream]. + * + * @param signature OpenPGP signature + * @param signedData input stream containing signed data + * @param signingKey key that created the signature + * @throws SignatureValidationException in case the signature cannot be verified for some + * reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun initializeSignatureAndUpdateWithSignedData( + signature: PGPSignature, + signedData: InputStream, + signingKey: PGPPublicKey + ) { + try { + signature.init( + getInstance().pgpContentVerifierBuilderProvider, + signingKey, + ) + var read: Int + val buf = ByteArray(8192) + var lastByte: Byte = -1 + while (signedData.read(buf).also { read = it } != -1) { + // If we previously omitted a newline, but the stream is not yet empty, add it + // now + if (lastByte == '\n'.code.toByte()) { + signature.update(lastByte) + } + lastByte = buf[read - 1] + if (lastByte == '\n'.code.toByte()) { + // if last byte in buffer is newline, omit it for now + signature.update(buf, 0, read - 1) + } else { + // otherwise, write buffer as usual + signature.update(buf, 0, read) + } + } + } catch (e: PGPException) { + throw SignatureValidationException("Cannot init signature.", e) + } catch (e: IOException) { + throw SignatureValidationException("Cannot update signature.", e) + } + } + + /** + * Verify an initialized signature. An initialized signature was already updated with the + * signed data. + * + * @param signature OpenPGP signature + * @param signingKey key that created the signature + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if signature is verified successfully + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyInitializedSignature( + signature: PGPSignature, + signingKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + wasPossiblyMadeByKey(signingKey).verify(signature) + signatureStructureIsAcceptable(signingKey, policy).verify(signature) + signatureIsEffective(referenceTime).verify(signature) + + return try { + if (!signature.verify()) { + throw SignatureValidationException("Signature is not correct.") + } + true + } catch (e: PGPException) { + throw SignatureValidationException("Could not verify signature correctness.", e) + } + } + + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifyOnePassSignature( + signature: PGPSignature, + signingKey: PGPPublicKey, + onePassSignature: OnePassSignatureCheck, + policy: Policy + ): Boolean { + try { + wasPossiblyMadeByKey(signingKey).verify(signature) + signatureStructureIsAcceptable(signingKey, policy).verify(signature) + signatureIsEffective().verify(signature) + } catch (e: SignatureValidationException) { + throw SignatureValidationException("Signature is not valid: ${e.message}", e) + } + + try { + checkNotNull(onePassSignature.signature) { "No comparison signature provided." } + if (!onePassSignature.onePassSignature.verify(signature)) { + throw SignatureValidationException( + "Bad signature of key ${signingKey.keyID.openPgpKeyId()}") + } + } catch (e: PGPException) { + throw SignatureValidationException( + "Could not verify correctness of One-Pass-Signature: ${e.message}", e) + } + + return true + } + + /** + * Verify a signature (certification or revocation) over a user-id. + * + * @param userId user-id + * @param signature self-signature + * @param primaryKey primary key that created the signature + * @param policy policy + * @param referenceTime reference date for signature verification + * @return true if the signature is successfully verified + * @throws SignatureValidationException if signature verification fails for some reason + */ + @JvmStatic + @Throws(SignatureValidationException::class) + fun verifySignatureOverUserId( + userId: CharSequence, + signature: PGPSignature, + primaryKey: PGPPublicKey, + policy: Policy, + referenceTime: Date + ): Boolean { + return verifySignatureOverUserId( + userId, signature, primaryKey, primaryKey, policy, referenceTime) + } + } +} From f4bfb9dc04ba511695403072650354aca4f66e5e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 26 Oct 2023 12:52:21 +0200 Subject: [PATCH 155/155] Remove test with expired key --- .../KleopatraCompatibilityTest.java | 272 ------------------ 1 file changed, 272 deletions(-) delete mode 100644 pgpainless-core/src/test/java/investigations/KleopatraCompatibilityTest.java diff --git a/pgpainless-core/src/test/java/investigations/KleopatraCompatibilityTest.java b/pgpainless-core/src/test/java/investigations/KleopatraCompatibilityTest.java deleted file mode 100644 index cb6a26b9..00000000 --- a/pgpainless-core/src/test/java/investigations/KleopatraCompatibilityTest.java +++ /dev/null @@ -1,272 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package investigations; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.util.io.Streams; -import org.junit.jupiter.api.Test; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.DocumentSignatureType; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.decryption_verification.ConsumerOptions; -import org.pgpainless.decryption_verification.DecryptionStream; -import org.pgpainless.decryption_verification.MessageInspector; -import org.pgpainless.encryption_signing.EncryptionOptions; -import org.pgpainless.encryption_signing.EncryptionStream; -import org.pgpainless.encryption_signing.ProducerOptions; -import org.pgpainless.encryption_signing.SigningOptions; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.implementation.JceImplementationFactory; -import org.pgpainless.key.protection.SecretKeyRingProtector; - -public class KleopatraCompatibilityTest { - - public static final String KLEOPATRA_PUBKEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + - "\n" + - "mQGNBGF4StQBDADgAGvvtzCrSa5I9/jIZq0SKxoz7Hz61YM2Hs/hPedXfQeW7lrf\n" + - "qutyXSIb8L964v9u2RGnzteaPwciGSyoMal5teAPOsv6cp7kIDksQH8iJm/9FhoJ\n" + - "hFl2Yx5BX6sBtoXwY63Kf9Vpx/Std9tN34HHI7zrbO70rv6ZcDPFHyWoVdoDZOX1\n" + - "DWbBnOP3SoaNaPnbwEBfEkPwyN/NsnxTfe+IsCYC2byC3NZwYA5FscWFioeJ/UpF\n" + - "HMgZ6utn9mfTexOYEE0mL1mhrc7PbRjDlNasW3GLrpeVN55anT0jvtNXulG4POzG\n" + - "fJ8g3qddcbTXYhQItjurBlkYLV1JOhdCN83IJRect4EIKBkLuEKO0/a7bE6HC7nr\n" + - "PLw9MWGgcnDe2cTc4a6nAGC/eMeCONQlyAvOIEIXibbz4OB0dTNA5YYTMBHVO7n0\n" + - "GbNg8eqw+N+IijboLtJly+LshP81IdQMHg0h6K3+bfYV0rwC/XmR387s+pVpAp5k\n" + - "Lrw8Rt+BsQSY2O8AEQEAAbQhS2xlb3BhdHJhIDxrbGVvcGF0cmFAdGVzdC5kb21h\n" + - "aW4+iQHUBBMBCgA+FiEEzYzHEulLyE5PkaUp6EVgKKoTP1kFAmF4StQCGwMFCQPB\n" + - "7cwFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQ6EVgKKoTP1nClwv/exOrk3H0\n" + - "aKOqwB6gMtA9bOJgYG4Lm3v9EM39mGScTJgAaZMlJIVMZ7qBUCEbw9lOJMZLguVm\n" + - "VJN8sVYE6zNdPGxmQLLciXDheGIdmQDi/K1j2ujKWUVZEvasiu7pO2Gcl3Kjqaeu\n" + - "dpvKtEDPUHtkqzTQHgxpQpSky58wubLyoX/bNnCky3M/tu774kJ2HGHTy6S4c1KH\n" + - "f6k4X96vP1V7yoKp+dukYLXwtm73JAi7nX/wOmoQI4I60fs26ZFDpoEkAZVjZtj6\n" + - "qfT9unS+XZeklc0siaZ5wZvVuSGWcI4v4/rA/ZU9KjDriatEu0ZzE/Xll1MHQyh4\n" + - "B31zjwP8LmLSrNHMLmT+7nM+nCfCoo71uZGkuuR0sKa6bToBUOls1olRmKaZf9NS\n" + - "JjW0K0xL3TEzduM7h+oDNLf5bSSZFoDGwsHRW6E53l7ZDe7tOH+ZGSDuCbIVu4dQ\n" + - "6k0NVMFI+gxTwQU/4RS3heRvn739P7VRLyUl4gX0/q8EanHPQX9NXIuSuQGNBGF4\n" + - "StQBDADMeuyDHP4np/ZnfaHXKLnz6C+6lrF/B0LhGXDxvN+cCpFvybmqGZ74DOkK\n" + - "VXVlmXjvb22p6+oOD163/KOqfrjKT/oeVhMglMc2raNy5+XWHcjKBhprxbX9bIhr\n" + - "QEjmvP57pIfQ83s1dgQsWlxIwX1g86X04u6tnG+fwNdGrhZwcbaivJT5F82uKKIq\n" + - "gtDbqcUtqOQpg+zUO2rdbgjWw5LZPBiC/dHkWydGvzWrnAgDmVAsJita2F+Pxwmn\n" + - "i3p5qU2hBJmJuVo15w6elST1Svn3jim5gqbXXhh2BwDSDPEp0uRZlV6r9RMlH+js\n" + - "4IvKiveGzdXTzmbPl8U+4HHynPM1TWRxCaXNF4w4Blnlqzgg0jFXVzV0tXk1HJTc\n" + - "P4Lmmo0xpf5OEsbCZv61qDJO20QMHw9Y9qU/lcCsXvmtFfEDTZSfvIEAlpo7tvIn\n" + - "3H94EiVc5FNpRfWrngwPnwt3m3QkmG3lkd5WnxuyjH/LbKMtuBC/3QuKNrrySvXF\n" + - "L4SL51cAEQEAAYkBvAQYAQoAJhYhBM2MxxLpS8hOT5GlKehFYCiqEz9ZBQJheErU\n" + - "AhsMBQkDwe3MAAoJEOhFYCiqEz9ZkhsL/itexY5+qkWjjGd8cLAtrJTzhQRlk6s7\n" + - "t7eBFSuTywlKC1f1wVpu5djOHTPH8H0JWMAAxtHQluk3IcQruBMFoao3xma+2HW1\n" + - "x4C0AfrL4C00zxUUxqtmfZi81NU0izmFNABdcEHGbE8jN86wIaiAnS1em61F+vju\n" + - "MTMLJVq56SQJhWSymf4z4d8gVIy7WzeSuHnHcDbMcCfFzN1kn2T/k5gav4wEcz3n\n" + - "LizUYsT+rFKizgVzSDLlSFcJQPd+a8Kwbo/hnzDt9zgmVirzU0/2Sgd0d6Iatplk\n" + - "YPzWmjATe3htmKrGXD4R/rF7aEnPCkR8k8WMLPleuenCRGQi5KKzNuevY2U8A4Mi\n" + - "KNt5EM8WdqcXD3Pv7nsVi4dNc8IK1TZ4BfN3YBFQL+hO/Fk7apiqZDu3sNpG7JR0\n" + - "V37ltHAK0HFdznyP79oixknV6pfdAVbIyzQXk/FqnpvbjCY4v/DWLz6a4n8tYQPh\n" + - "g94JEXpwhb9guKuzYzP/QeBp4qFu5FO87w==\n" + - "=Jz7i\n" + - "-----END PGP PUBLIC KEY BLOCK-----\n"; - public static final String KLEOPATRA_SECKEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + - "\n" + - "lQVYBGF4StQBDADgAGvvtzCrSa5I9/jIZq0SKxoz7Hz61YM2Hs/hPedXfQeW7lrf\n" + - "qutyXSIb8L964v9u2RGnzteaPwciGSyoMal5teAPOsv6cp7kIDksQH8iJm/9FhoJ\n" + - "hFl2Yx5BX6sBtoXwY63Kf9Vpx/Std9tN34HHI7zrbO70rv6ZcDPFHyWoVdoDZOX1\n" + - "DWbBnOP3SoaNaPnbwEBfEkPwyN/NsnxTfe+IsCYC2byC3NZwYA5FscWFioeJ/UpF\n" + - "HMgZ6utn9mfTexOYEE0mL1mhrc7PbRjDlNasW3GLrpeVN55anT0jvtNXulG4POzG\n" + - "fJ8g3qddcbTXYhQItjurBlkYLV1JOhdCN83IJRect4EIKBkLuEKO0/a7bE6HC7nr\n" + - "PLw9MWGgcnDe2cTc4a6nAGC/eMeCONQlyAvOIEIXibbz4OB0dTNA5YYTMBHVO7n0\n" + - "GbNg8eqw+N+IijboLtJly+LshP81IdQMHg0h6K3+bfYV0rwC/XmR387s+pVpAp5k\n" + - "Lrw8Rt+BsQSY2O8AEQEAAQAL/jBENv3Iud52umyzrfI0mZ9cFUHR994uqp67RezR\n" + - "Y2tpH/0IMCGY2THj2oktt3y5s/OFJ3ZCrhdo9FcHGKXHSa7Vn0l40GIPV6htPxSH\n" + - "cz1/Dct5ezPIxmQpmGfavuTYGQVC3TxQjkJEWTcVp/YgLn0j+L2708N6f5a9ZBJa\n" + - "E0mx8g+gKqLCd/1JGp/6+YI39/q/cr9plqUoC31ts7dj3/zSg+ZCV4nVHwnI0Np4\n" + - "o0iSoID9yIaa3I0lHwNgR1/82UVEla94QGKSRQqjTrgsTLPFIACNtSI/5iaPdKZK\n" + - "a01oic1LKGEpuqpHAbnPnCAKrtWODk8B/3U4CABflXufI3GTYOZeaGZvd6I/lx/t\n" + - "HQcg5SKE8vNIB1YZ2+rSsznAFmexaLjPVG3XhGQdBVoV/mmlcI71TUEcL9kXYMh6\n" + - "JnwH5/F2kG9JAXC+0Y3R9Ji+wabVGMUHxugcXpQa0d/malCZaS/dviDUfZ1KbDjH\n" + - "Jlzew7cmfRtiw4tfczboekeSbQYA6bh6IFqQlcW7qj34TAg8h8t34Q2o2U0VMj96\n" + - "OiG8B/LARF89ue4CaQQMQt16BHeMhePBAhPCkkCEtmbXremHsrtn6C149cS9GAEt\n" + - "fSAHVGedVDHzke/K84uHzBbY0dS6O6u2ApvWOutgWpB5Le4A7WTslyAdLWRZ1l69\n" + - "H2706M9fgGClVsQByCNVksDEbOizIlAkFOq0b39u8dnb063A9ReAuk/argCA7JHU\n" + - "j3BFIF5crIn+YrWl6slFuoXGWTXlBgD1WsVhU4hXJ5g35TOso0rEND/wHrjW0W4F\n" + - "LViA5yAt9sVLNGgm9Ye3YSVIHId6HiJQZmsWJb81WD5dvBLl74icZmfSWtRTwvCZ\n" + - "0k3rYlu3Ex4bQUwoyhSlDoPJ9YMaumd1yaM3nMeyrlaHYIpV8NtqSuqJc7i2iNX1\n" + - "3s9AotipHYEUOlsp936bNEuh0m8xXEZ2C8qjpNenymg8XfNd/IH2M4Sjzz+pN5sS\n" + - "gQt+pQhYFnW0Gersb/X3OsAtLtRE5kMF/3v7GAz7usMcajqbh9qB+Ytp4n1u3aQC\n" + - "ck1exVOwdLDZgsHfojO1SEFd3IafO01xp+TmS8qIoZvKJegM+qq9px1PHSTRnb4D\n" + - "8tuBxtdoUE7n+g3Li74je7+DEdcq6g9ZjgyeosCHGItUwTcCMqnHa+ikjQjsnnzu\n" + - "eSwvVSfMJQYyZrZ5qYgQZKcovkFDvgXiC/jqfDd6GeAfbxzL2cyAYWvUdGln79O3\n" + - "Tc7ZWd0Xn6IaMPVPRBvH4RsaWqFdO0pIOuH7tCFLbGVvcGF0cmEgPGtsZW9wYXRy\n" + - "YUB0ZXN0LmRvbWFpbj6JAdQEEwEKAD4WIQTNjMcS6UvITk+RpSnoRWAoqhM/WQUC\n" + - "YXhK1AIbAwUJA8HtzAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRDoRWAoqhM/\n" + - "WcKXC/97E6uTcfRoo6rAHqAy0D1s4mBgbgube/0Qzf2YZJxMmABpkyUkhUxnuoFQ\n" + - "IRvD2U4kxkuC5WZUk3yxVgTrM108bGZAstyJcOF4Yh2ZAOL8rWPa6MpZRVkS9qyK\n" + - "7uk7YZyXcqOpp652m8q0QM9Qe2SrNNAeDGlClKTLnzC5svKhf9s2cKTLcz+27vvi\n" + - "QnYcYdPLpLhzUod/qThf3q8/VXvKgqn526RgtfC2bvckCLudf/A6ahAjgjrR+zbp\n" + - "kUOmgSQBlWNm2Pqp9P26dL5dl6SVzSyJpnnBm9W5IZZwji/j+sD9lT0qMOuJq0S7\n" + - "RnMT9eWXUwdDKHgHfXOPA/wuYtKs0cwuZP7ucz6cJ8KijvW5kaS65HSwprptOgFQ\n" + - "6WzWiVGYppl/01ImNbQrTEvdMTN24zuH6gM0t/ltJJkWgMbCwdFboTneXtkN7u04\n" + - "f5kZIO4JshW7h1DqTQ1UwUj6DFPBBT/hFLeF5G+fvf0/tVEvJSXiBfT+rwRqcc9B\n" + - "f01ci5KdBVgEYXhK1AEMAMx67IMc/ien9md9odcoufPoL7qWsX8HQuEZcPG835wK\n" + - "kW/JuaoZnvgM6QpVdWWZeO9vbanr6g4PXrf8o6p+uMpP+h5WEyCUxzato3Ln5dYd\n" + - "yMoGGmvFtf1siGtASOa8/nukh9DzezV2BCxaXEjBfWDzpfTi7q2cb5/A10auFnBx\n" + - "tqK8lPkXza4ooiqC0NupxS2o5CmD7NQ7at1uCNbDktk8GIL90eRbJ0a/NaucCAOZ\n" + - "UCwmK1rYX4/HCaeLenmpTaEEmYm5WjXnDp6VJPVK+feOKbmCptdeGHYHANIM8SnS\n" + - "5FmVXqv1EyUf6Ozgi8qK94bN1dPOZs+XxT7gcfKc8zVNZHEJpc0XjDgGWeWrOCDS\n" + - "MVdXNXS1eTUclNw/guaajTGl/k4SxsJm/rWoMk7bRAwfD1j2pT+VwKxe+a0V8QNN\n" + - "lJ+8gQCWmju28ifcf3gSJVzkU2lF9aueDA+fC3ebdCSYbeWR3lafG7KMf8tsoy24\n" + - "EL/dC4o2uvJK9cUvhIvnVwARAQABAAv9ExmcWWGY6p1e1StACyKrvqO+lEBFPidb\n" + - "Jj7udODT8PXFFgW9c60cU0aUHLn/fZ5d/zI6XSKYj02nkaoQo6QIoM/i/iMY0En1\n" + - "aHRvDb7+51w1iDa/uwy8biVNgi8pYBw2l9gLiQdlR94ej6y1GBAIJR6ShD26VmSE\n" + - "F2O3osuEybtleEKt660/MiMWMBWzaqwAq2jY6c5/4xHVw+87oMv4k0AbeLOQKojK\n" + - "h2o5mi5jSpVvOWCAsOYAhHlEEUQPDFQ1rbJ3P3XcRZE4EIxP2eKDyfyOXRTihLDl\n" + - "/9hqOf57wo0C43bnc1BkD6sk+ptKgUifpUHHejg/i7HINFivh7jCgCtoskf2P9BL\n" + - "WFuaPZVLQSVE5X2PsgeIYK9/eGeNxfXgtwRyUd8DtBge11tsMaENUTm39p36my2K\n" + - "jBgoEdBIQo1Mpi1EZba+L6pyw9bPFnj5H+opSe+X9/spkS9DyPOPGY7rCSTgv+7q\n" + - "Ph2WbtRRJslitLEjT9tNgwMRGWsgdbcpBgDgzujDUQb1coCdgw1gsQSTPir9hJxF\n" + - "Q+2DAbGpkqiYayHJqH7T9wGhiY8QoqLIejNawkrel4yTtYmM7pgtURWrkzz/jHAT\n" + - "3NNRTyvFqMmjwOIoV83tW8247uA8eofc981wEVayJ4y/KDcvU04FBrjCEoOUQMXw\n" + - "Ychr4cGiEckGBxAib6fVxjsU3PUIuUDpm9NC53Rc0GmwlduiZSJqRZQRgytLxWdM\n" + - "Va4c5oHdc0qpjCgk5qkW/09lI5kxTlMk3E0GAOjZ+HSQV8rI7qu7G0Qno9MP0Q49\n" + - "Qo5Hf4uV+I/6Iim/Eq5LWKmmairIob47jLtmhoIo7LArTm+9NsThFidc6wjRYgtT\n" + - "kGx4KUTEl8d0/mHV8GBzNNyRM7UOoLVjgf4tljFa8d2hQNMXZyBsIkLyoL6cL2sx\n" + - "aMZWl9jjh0bYE4TiTDIO1cfddxGjCPG9i12Z+yMl5p0g+r+IUAbuSh4+Yo7PUIKF\n" + - "8v+mqZRC9M9C/T/qOAB2gL2vDEZ4TdLAZfYUMwX9E/I1e0gHPlqXmQ/znTkjuCXd\n" + - "JopVXmvvku8SvVFb4pcW1k5Tk3iEj7nilQ64I5bONFUot+qKTtxAM2Fwxo0EjFZD\n" + - "TCP5RbY60iJcnhpk5mDGD41O1xe2HBkJw8dC5xUr1pPs+7Y8gMXN3qK4JcrLfLSO\n" + - "pOb623ir9jtJWLjv1wOvr7KsWZxg8XOQq8+AkEprUjb8v8WsJY5c7L8vSJ5OYlOP\n" + - "gv9Tj3MVmV1jGhH9pR+zGcclyathY3Ytloy1zZxR3WCJAbwEGAEKACYWIQTNjMcS\n" + - "6UvITk+RpSnoRWAoqhM/WQUCYXhK1AIbDAUJA8HtzAAKCRDoRWAoqhM/WZIbC/4r\n" + - "XsWOfqpFo4xnfHCwLayU84UEZZOrO7e3gRUrk8sJSgtX9cFabuXYzh0zx/B9CVjA\n" + - "AMbR0JbpNyHEK7gTBaGqN8Zmvth1tceAtAH6y+AtNM8VFMarZn2YvNTVNIs5hTQA\n" + - "XXBBxmxPIzfOsCGogJ0tXputRfr47jEzCyVauekkCYVkspn+M+HfIFSMu1s3krh5\n" + - "x3A2zHAnxczdZJ9k/5OYGr+MBHM95y4s1GLE/qxSos4Fc0gy5UhXCUD3fmvCsG6P\n" + - "4Z8w7fc4JlYq81NP9koHdHeiGraZZGD81powE3t4bZiqxlw+Ef6xe2hJzwpEfJPF\n" + - "jCz5XrnpwkRkIuSiszbnr2NlPAODIijbeRDPFnanFw9z7+57FYuHTXPCCtU2eAXz\n" + - "d2ARUC/oTvxZO2qYqmQ7t7DaRuyUdFd+5bRwCtBxXc58j+/aIsZJ1eqX3QFWyMs0\n" + - "F5Pxap6b24wmOL/w1i8+muJ/LWED4YPeCRF6cIW/YLirs2Mz/0HgaeKhbuRTvO8=\n" + - "=cgLL\n" + - "-----END PGP PRIVATE KEY BLOCK-----\n"; - // signed and encrypted - public static final String KLEOPATRA_MESSAGE = "-----BEGIN PGP MESSAGE-----\n" + - "\n" + - "hQGMA2gglaIyvWSdAQv+NjugJ3Sqk7F9PnVOphT8TNc1i1rHlU9bDDeyZ2Czl6KA\n" + - "YXwSP5KmwgTJH+vt9N5xrbKOGCuSCJNeb0wzH/YpQHLL5Hx5Pk0KtNH8BCevApkM\n" + - "Rcn4EKiXMmTFyib0fCPlqvEvqdD1ni1IliHNLxR/TYCSxbmu3TqPie70PiLsB32l\n" + - "6QKDi1U3HftsZOLLgIPbd1IqnSMeT3E15oD8LTQe3k/CV+huA54wrIeqDxfJpcAu\n" + - "rvb4rLVvGmaF67FXekMEDjD3cdk2m6WJ8c1myh3EUpDRlMPobhgeEV+h28heGuhu\n" + - "g2Id97DMfUhxypGbQ/rlwHE3UMvdW3YS0KRT7UfPee0F2m737b/aWO341LOzJz94\n" + - "xggPafIC6IseQQVZirocG1CLl0lauWZoXbfmzrXCT+YGNuaNjlE01BYPBjgEygle\n" + - "7Kur60YkB0H6fACskcudWDRFTsjEgIZa3riHou7XmvqupvJC+hyYdH3QqyFMvdix\n" + - "03/E9ePUs051Bvzn+a/dhQGMAwAAAAAAAAAAAQv/RtljqQ2BsB0KkdzZtnfY+CZZ\n" + - "PBYvloxplK+Bs8aoLVujyI7g6weOvFD49tSowsvJ//DleDpcKe4UZA/WRj5HlB1J\n" + - "5zLK5qlWb8El6QKlwEKB02zHDv244Bm9ZROnSK3CrEqRcfdBQIx4ThEOZlG0cE60\n" + - "iTbrda2SYUDpHh4Re/qhw/wvc0uUf+59u8WU5AIpgfLBU2fNEjOr6LMIsR3Edvf8\n" + - "zIFUrHlfvKQaAnZYU79dA0ZnTYgLiwMWB19nvhnSIdWAC3tJUsiuEIzEzA+vVbG/\n" + - "YrTOMR+vFm+dVOcVanzn0vnV0n8+np1kM1V2JgGRKV6XybS1oUbNPvpv79FBgfPi\n" + - "F3WghBHZf9lTaj4w7LtQSojvC0YxSoxfTif/MMxNZUoexQbk0jE97ibeFk6rqrBn\n" + - "46G2WbrrReDyOUekSkM5MQ/bZ1GJuFfC+kGyHETBejsfn0ZKa2RUla9k3vYFcDJC\n" + - "Et7Vwv81SF4yzvSwiV0rFx1RcyZaGlJumjCkqaHHhQIMAwAAAAAAAAAAAQ/+KTsY\n" + - "vnPMOmjmLqu7BQwx3jUaEmCXTurv5XHMbAEcq0UAwHJ/XAcJe7B1707Fu1sSJJjV\n" + - "3rJVHGUv7+APg5/vALx/FGlKk/12M8NhgOreLCLa/vQ9NmNrcfqGQdZtpk6OQxLv\n" + - "DcnCbUjyTO2IFRjmzEy8d8rne/FMC1MZD9hY1IboJWk5fN1NCsIIbPn3OSqVIaa1\n" + - "9fJj6D7SGacraBNJwl0x22ipV4yLtpo2DtnPJGK4xgTm0eW7eUK3nIfC/foHEgxG\n" + - "Bny1axnKqC9TFhAQ7Eo+Wh9eAiXtFBY7po7tfYmhb6mHBMAfYsVvCCyLNqUbXiV9\n" + - "kXWMBf0yxtNQlkx1jK+iqfGBm3EfHKncXGfl6zxwkh1FZXcY2EyCavkGND+3Gexg\n" + - "vbCUltulq1Fv1WjOGz9Icc5pK9AyjUuc/AQ4k7WhCVhCmbpsb/Cq6LsiqOC219dE\n" + - "r5TLGr+K1289PVOgbd06BL5NVP6qeO5fyWUA/Bs+exxqEDKce0f0ppKkcGNAv9p/\n" + - "Lg57FxT8aYVBgSoTv1DASquZANrO3kp7M3nC5lVzUldz8aS4YEirLLTF0MBnZEZ8\n" + - "MRcG8h8oSKozw+cuJXNF+bFiKM0wwRyw0AXGt69/lrPlWKMCfuK3n8vqxVPJ78JD\n" + - "ut8xHNWelqS2uO0qinvfbBcKzptYUm8ctNbHlSLS6QEnmjoiF/jobEDWsp6yBaym\n" + - "o7h9VQrmCKjKsoQzoF5KYHW87BLb2YRnx5WwTvN1BvZTNqNjkm9tuDTIwhTUx/L/\n" + - "B8l+KqpGcrmsldQ/pF/W3m2mFlsqpb02uWJSpXQ7NEavjvPThKPJHUnni4YtCg5b\n" + - "v8Zy/zvYgGj5y4DDjM84Xw/HcMdyHsWIcGosZ6W/jJhO7sECXqS6HoF5zFsIBPX9\n" + - "dEM4GS5TapLe0s7DyC0bK7VbPgLMBxPmbBSVp3O72qKpvgc6PAggTJHNhd6MLsJA\n" + - "JAiAOF/KNNZxSdMWIXqMyMviSPeU9+KclOG7iiR75Q5kIbpj9hWo5ullxr6XrHl2\n" + - "HFR+5jnmbSNwz/cf0vwkTnNG/Crofyy0kPTfGp5Ku4hp0wIhWXM9f8m7tuoxI3ep\n" + - "uNwB7FOs3xemsxAmoufyWcsyxnVf/3OJLWejIcIK1v3NmoiSxFQXl2cmiRVLTtAT\n" + - "oNjUT9QDQiyi8YR+CepV6RnBSmRomr7HfRAoACaCg6ToaXm0Dc8OQSge2X80ifdD\n" + - "NUcfhQAivaVAqhAogUIaPp9yqwTWaZ00N5cPH4HItPJtukb+Fsove2SoF+iPQre6\n" + - "hDjZCNyfUjT+wnca315nN+9D6Z1JgV5YEM23sFKp4M732Zdb5JlR0DXfDEuQH1NL\n" + - "hXOcpr9LpAvASH7weiVTEYxNz5KzFkUQA5YKLLeDwtcK\n" + - "=MgH4\n" + - "-----END PGP MESSAGE-----\n"; - public static final String PGPAINLESS_MESSAGE = "-----BEGIN PGP MESSAGE-----\n" + - "Version: PGPainless\n" + - "\n" + - "hQGMA2gglaIyvWSdAQv/Y9Wx763qAM95+teCUPPNRc5Iwqbc5uFjxfbcwsHIWdiZ\n" + - "n2wHNUmd2dUqYgpqOcBwZ/fUJuoHj/uXKZ1pbz2QSVYaL9MulKpgWiVAo0K2w0Oc\n" + - "97KfZ0d66tcZIhslVpZW06+lXuwMyjjjExe32fAkPFnYyTNORljyYlb/RDSkh7Ke\n" + - "Q+48fLR2kitV0WyRZ+d9cMfx2+D2gWYiaFGek9SrhI8L+nNd4UKvM4K4sSq4JHYf\n" + - "DCxGPWYOaTculuX8dfDh3ftHbrmL2Ca7Iv4NB0kSduG8Gin2OWyeSIPIwpF2ci9g\n" + - "cIBssAYhmS88FQit5pW9z2RZ/e9XmYIP++kz3/EdI6DqkiPUv1fiHTrJBC93LvVg\n" + - "pq75h9RNFuUlqR09SVuB/uZB6tYgv77vy5lPFo+wmLjM41aS4+qI1hBI3Ym4XTc1\n" + - "spPA0sEHtQTQ/xRNYqGpwunJniMF3ukWpOB6UNvQld+p2lj8czexhEAcne1cjey/\n" + - "f0/WUnluSt0HIg8Mnd7s0ukBhb4YxjvARjuqi6PikGz4JPshRwB8dPtS9FQiRxL7\n" + - "obaPHXlmLwohEtT3akzoIj/9C3Y7qnfreSllDgRDxRVFPXy5QnQqpsTy2JuJ4cvo\n" + - "p55RE2kyJ3vBZlB6T53pSgC00hQnNxoqgy7aejRItlec7zx5DnEg8t4rA7LYEGLT\n" + - "MBLWbTRc/njH6GTyc/3x7j9k8V83exqpF6fXrE3GP1C3fBxHY2S9/5BFAlzimplz\n" + - "Mow4S15D04EllRRk6f9HKY598xS4QlDEW/f3utwkQ8+/lNqesVuV8n76WDldMv2O\n" + - "5gTqAZ/pKhDKRLY6km4B2+2IAt2zg+V141wryHJgE/4VyUbu7zZxDIcDouuATQvt\n" + - "wNMnntqy3NTbM7DefSiYe9IUsTUz/g0VQJikoJx+rdX6YzQnRk/cmwvELnskQjSk\n" + - "aGd92A4ousaM299IOkbpLvFaJdrs7cLH0rEQTG5S3tRJSLEnjr94BUVtpIhQDo3i\n" + - "455UahKcCx/KhyIzo+8OdH0TYZf5ZFGLdTrqgi0ybAHcLrXkM+g2JOsst99CeRUq\n" + - "f/T4oFvuDSlLU56iWlLVE7gvDBibXfWIJ65YBHY4ueEzBC/3xOVj+dmTM2JfUSX7\n" + - "mqD25NaDCOuN4WhJmZHC1wyipj3KYT2bLg4gasHr/LvEI+Df/DREdXtrYAqPqZYU\n" + - "0QuubMF4n3hMqmu2wA==\n" + - "=fMRM\n" + - "-----END PGP MESSAGE-----"; - - @Test - public void testMessageDecryptionAndVerification() throws PGPException, IOException { - ImplementationFactory.setFactoryImplementation(new JceImplementationFactory()); - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KLEOPATRA_SECKEY); - PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(KLEOPATRA_PUBKEY); - - ConsumerOptions options = new ConsumerOptions() - .addDecryptionKey(secretKeys) - .addVerificationCert(publicKeys); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() - .onInputStream(new ByteArrayInputStream(KLEOPATRA_MESSAGE.getBytes(StandardCharsets.UTF_8))) - .withOptions(options); - - Streams.pipeAll(decryptionStream, out); - decryptionStream.close(); - } - - @Test - public void testEncryptAndSignMessage() throws PGPException, IOException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KLEOPATRA_SECKEY); - PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(KLEOPATRA_PUBKEY); - - ProducerOptions options = ProducerOptions.signAndEncrypt( - EncryptionOptions.encryptCommunications() - .addRecipient(publicKeys) - .overrideEncryptionAlgorithm(SymmetricKeyAlgorithm.AES_128), - SigningOptions.get() - .addInlineSignature(SecretKeyRingProtector.unprotectedKeys(), secretKeys, DocumentSignatureType.BINARY_DOCUMENT) - ); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() - .onOutputStream(out) - .withOptions(options); - - ByteArrayInputStream in = new ByteArrayInputStream("Hallo, Welt!\n\n".getBytes(StandardCharsets.UTF_8)); - Streams.pipeAll(in, encryptionStream); - encryptionStream.close(); - } - - @Test - public void testMessageInspection() throws PGPException, IOException { - MessageInspector.EncryptionInfo info = MessageInspector.determineEncryptionInfoForMessage( - new ByteArrayInputStream(KLEOPATRA_MESSAGE.getBytes(StandardCharsets.UTF_8))); - } -}

- * After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}. + * After making the desired changes in the builder, the modified key can be extracted using + * {@link SecretKeyRingEditorInterface#done()}. * * @param secretKeys secret key ring * @param referenceTime reference time used as signature creation date @@ -141,11 +138,12 @@ class PGPainless private constructor() { @JvmStatic @JvmOverloads fun modifyKeyRing(secretKey: PGPSecretKeyRing, referenceTime: Date = Date()) = - SecretKeyRingEditor(secretKey, referenceTime) + SecretKeyRingEditor(secretKey, referenceTime) /** - * Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] / [PGPSecretKeyRing]. - * This method can be used to determine expiration dates, key flags and other information about a key at a specific time. + * Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] / + * [PGPSecretKeyRing]. This method can be used to determine expiration dates, key flags and + * other information about a key at a specific time. * * @param keyRing key ring * @param referenceTime date of inspection @@ -154,22 +152,20 @@ class PGPainless private constructor() { @JvmStatic @JvmOverloads fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) = - KeyRingInfo(key, referenceTime) + KeyRingInfo(key, referenceTime) /** * Access, and make changes to PGPainless policy on acceptable/default algorithms etc. * * @return policy */ - @JvmStatic - fun getPolicy() = Policy.getInstance() + @JvmStatic fun getPolicy() = Policy.getInstance() /** * Create different kinds of signatures on other keys. * * @return builder */ - @JvmStatic - fun certify() = CertifyCertificate() + @JvmStatic fun certify() = CertifyCertificate() } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt index 61672122..253b37dd 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt @@ -4,10 +4,7 @@ package org.pgpainless.algorithm -enum class AEADAlgorithm( - val algorithmId: Int, - val ivLength: Int, - val tagLength: Int) { +enum class AEADAlgorithm(val algorithmId: Int, val ivLength: Int, val tagLength: Int) { EAX(1, 16, 16), OCB(2, 15, 16), GCM(3, 12, 16), @@ -16,15 +13,12 @@ enum class AEADAlgorithm( companion object { @JvmStatic fun fromId(id: Int): AEADAlgorithm? { - return values().firstOrNull { - algorithm -> algorithm.algorithmId == id - } + return values().firstOrNull { algorithm -> algorithm.algorithmId == id } } @JvmStatic fun requireFromId(id: Int): AEADAlgorithm { - return fromId(id) ?: - throw NoSuchElementException("No AEADAlgorithm found for id $id") + return fromId(id) ?: throw NoSuchElementException("No AEADAlgorithm found for id $id") } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt index 0e4997fc..867bf1b8 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt @@ -5,9 +5,10 @@ package org.pgpainless.algorithm class AlgorithmSuite( - symmetricKeyAlgorithms: List, - hashAlgorithms: List, - compressionAlgorithms: List) { + symmetricKeyAlgorithms: List, + hashAlgorithms: List, + compressionAlgorithms: List +) { val symmetricKeyAlgorithms: Set = symmetricKeyAlgorithms.toSet() val hashAlgorithms: Set = hashAlgorithms.toSet() @@ -16,30 +17,31 @@ class AlgorithmSuite( companion object { @JvmStatic - val defaultSymmetricKeyAlgorithms = listOf( + val defaultSymmetricKeyAlgorithms = + listOf( SymmetricKeyAlgorithm.AES_256, SymmetricKeyAlgorithm.AES_192, SymmetricKeyAlgorithm.AES_128) @JvmStatic - val defaultHashAlgorithms = listOf( + val defaultHashAlgorithms = + listOf( HashAlgorithm.SHA512, HashAlgorithm.SHA384, HashAlgorithm.SHA256, HashAlgorithm.SHA224) @JvmStatic - val defaultCompressionAlgorithms = listOf( + val defaultCompressionAlgorithms = + listOf( CompressionAlgorithm.ZLIB, CompressionAlgorithm.BZIP2, CompressionAlgorithm.ZIP, CompressionAlgorithm.UNCOMPRESSED) @JvmStatic - val defaultAlgorithmSuite = AlgorithmSuite( - defaultSymmetricKeyAlgorithms, - defaultHashAlgorithms, - defaultCompressionAlgorithms) + val defaultAlgorithmSuite = + AlgorithmSuite( + defaultSymmetricKeyAlgorithms, defaultHashAlgorithms, defaultCompressionAlgorithms) } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CertificationType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CertificationType.kt index 33025ad6..5617109c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CertificationType.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CertificationType.kt @@ -4,18 +4,17 @@ package org.pgpainless.algorithm -enum class CertificationType( - val signatureType: SignatureType -) { +enum class CertificationType(val signatureType: SignatureType) { /** - * The issuer of this certification does not make any particular assertion as to how well the certifier has - * checked that the owner of the key is in fact the person described by the User ID. + * The issuer of this certification does not make any particular assertion as to how well the + * certifier has checked that the owner of the key is in fact the person described by the User + * ID. */ GENERIC(SignatureType.GENERIC_CERTIFICATION), /** - * The issuer of this certification has not done any verification of the claim that the owner of this key is - * the User ID specified. + * The issuer of this certification has not done any verification of the claim that the owner of + * this key is the User ID specified. */ NONE(SignatureType.NO_CERTIFICATION), @@ -31,4 +30,4 @@ enum class CertificationType( ; fun asSignatureType() = signatureType -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CompressionAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CompressionAlgorithm.kt index 73179722..da4085bd 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CompressionAlgorithm.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/CompressionAlgorithm.kt @@ -20,22 +20,20 @@ enum class CompressionAlgorithm(val algorithmId: Int) { companion object { /** - * Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. - * If an invalid id is provided, null is returned. + * Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If + * an invalid id is provided, null is returned. * * @param id id * @return compression algorithm */ @JvmStatic fun fromId(id: Int): CompressionAlgorithm? { - return values().firstOrNull { - c -> c.algorithmId == id - } + return values().firstOrNull { c -> c.algorithmId == id } } /** - * Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. - * If an invalid id is provided, throw an [NoSuchElementException]. + * Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If + * an invalid id is provided, throw an [NoSuchElementException]. * * @param id id * @return compression algorithm @@ -43,8 +41,8 @@ enum class CompressionAlgorithm(val algorithmId: Int) { */ @JvmStatic fun requireFromId(id: Int): CompressionAlgorithm { - return fromId(id) ?: - throw NoSuchElementException("No CompressionAlgorithm found for id $id") + return fromId(id) + ?: throw NoSuchElementException("No CompressionAlgorithm found for id $id") } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/DocumentSignatureType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/DocumentSignatureType.kt index b3cd17ac..e41a4605 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/DocumentSignatureType.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/DocumentSignatureType.kt @@ -6,15 +6,11 @@ package org.pgpainless.algorithm enum class DocumentSignatureType(val signatureType: SignatureType) { - /** - * Signature is calculated over the unchanged binary data. - */ + /** Signature is calculated over the unchanged binary data. */ BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT), /** - * The signature is calculated over the text data with its line endings - * converted to ``. + * The signature is calculated over the text data with its line endings converted to ``. */ CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT), - ; -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/EncryptionPurpose.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/EncryptionPurpose.kt index f7e5ce2d..1b4bbe6e 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/EncryptionPurpose.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/EncryptionPurpose.kt @@ -5,18 +5,10 @@ package org.pgpainless.algorithm enum class EncryptionPurpose { - /** - * The stream will encrypt communication that goes over the wire. - * E.g. EMail, Chat... - */ + /** The stream will encrypt communication that goes over the wire. E.g. EMail, Chat... */ COMMUNICATIONS, - /** - * The stream will encrypt data at rest. - * E.g. Encrypted backup... - */ + /** The stream will encrypt data at rest. E.g. Encrypted backup... */ STORAGE, - /** - * The stream will use keys with either flags to encrypt the data. - */ + /** The stream will use keys with either flags to encrypt the data. */ ANY -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Feature.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Feature.kt index 2e0058b5..3f9be1f5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Feature.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Feature.kt @@ -12,42 +12,44 @@ package org.pgpainless.algorithm enum class Feature(val featureId: Byte) { /** - * Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification - * Detection Code Packets. + * Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using + * Modification Detection Code Packets. * - * See [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14) + * See + * [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14) */ MODIFICATION_DETECTION(0x01), /** - * Support for Authenticated Encryption with Additional Data (AEAD). - * If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets. + * Support for Authenticated Encryption with Additional Data (AEAD). If a key announces this + * feature, it signals support for consuming AEAD Encrypted Data Packets. * - * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! - * NOTE: This value is currently RESERVED. + * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! NOTE: This value is currently RESERVED. * - * See [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-) + * See + * [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-) */ GNUPG_AEAD_ENCRYPTED_DATA(0x02), /** - * If a key announces this feature, it is a version 5 public key. - * The version 5 format is similar to the version 4 format except for the addition of a count for the key material. - * This count helps to parse secret key packets (which are an extension of the public key packet format) in the case - * of an unknown algorithm. - * In addition, fingerprints of version 5 keys are calculated differently from version 4 keys. + * If a key announces this feature, it is a version 5 public key. The version 5 format is + * similar to the version 4 format except for the addition of a count for the key material. This + * count helps to parse secret key packets (which are an extension of the public key packet + * format) in the case of an unknown algorithm. In addition, fingerprints of version 5 keys are + * calculated differently from version 4 keys. * - * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! - * NOTE: This value is currently RESERVED. + * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! NOTE: This value is currently RESERVED. * - * See [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats) + * See + * [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats) */ GNUPG_VERSION_5_PUBLIC_KEY(0x04), /** * Support for Symmetrically Encrypted Integrity Protected Data packet version 2. * - * See [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd) + * See + * [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd) */ MODIFICATION_DETECTION_2(0x08), ; @@ -55,29 +57,26 @@ enum class Feature(val featureId: Byte) { companion object { @JvmStatic fun fromId(id: Byte): Feature? { - return values().firstOrNull { - f -> f.featureId == id - } + return values().firstOrNull { f -> f.featureId == id } } @JvmStatic fun requireFromId(id: Byte): Feature { - return fromId(id) ?: - throw NoSuchElementException("Unknown feature id encountered: $id") + return fromId(id) ?: throw NoSuchElementException("Unknown feature id encountered: $id") } @JvmStatic fun fromBitmask(bitmask: Int): List { - return values().filter { - it.featureId.toInt() and bitmask != 0 - } + return values().filter { it.featureId.toInt() and bitmask != 0 } } @JvmStatic fun toBitmask(vararg features: Feature): Byte { - return features.map { it.featureId.toInt() } - .reduceOrNull { mask, f -> mask or f }?.toByte() - ?: 0 + return features + .map { it.featureId.toInt() } + .reduceOrNull { mask, f -> mask or f } + ?.toByte() + ?: 0 } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/HashAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/HashAlgorithm.kt index 9c433efe..3360e7fe 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/HashAlgorithm.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/HashAlgorithm.kt @@ -11,36 +11,33 @@ package org.pgpainless.algorithm */ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) { - @Deprecated("MD5 is deprecated") - MD5 (1, "MD5"), - SHA1 (2, "SHA1"), - RIPEMD160 (3, "RIPEMD160"), - SHA256 (8, "SHA256"), - SHA384 (9, "SHA384"), - SHA512 (10, "SHA512"), - SHA224 (11, "SHA224"), - SHA3_256 (12, "SHA3-256"), - SHA3_512 (14, "SHA3-512"), + @Deprecated("MD5 is deprecated") MD5(1, "MD5"), + SHA1(2, "SHA1"), + RIPEMD160(3, "RIPEMD160"), + SHA256(8, "SHA256"), + SHA384(9, "SHA384"), + SHA512(10, "SHA512"), + SHA224(11, "SHA224"), + SHA3_256(12, "SHA3-256"), + SHA3_512(14, "SHA3-512"), ; companion object { /** - * Return the [HashAlgorithm] value that corresponds to the provided algorithm id. - * If an invalid algorithm id was provided, null is returned. + * Return the [HashAlgorithm] value that corresponds to the provided algorithm id. If an + * invalid algorithm id was provided, null is returned. * * @param id numeric id * @return enum value */ @JvmStatic fun fromId(id: Int): HashAlgorithm? { - return values().firstOrNull { - h -> h.algorithmId == id - } + return values().firstOrNull { h -> h.algorithmId == id } } /** - * Return the [HashAlgorithm] value that corresponds to the provided algorithm id. - * If an invalid algorithm id was provided, throw a [NoSuchElementException]. + * Return the [HashAlgorithm] value that corresponds to the provided algorithm id. If an + * invalid algorithm id was provided, throw a [NoSuchElementException]. * * @param id algorithm id * @return enum value @@ -48,15 +45,15 @@ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) { */ @JvmStatic fun requireFromId(id: Int): HashAlgorithm { - return fromId(id) ?: - throw NoSuchElementException("No HashAlgorithm found for id $id") + return fromId(id) ?: throw NoSuchElementException("No HashAlgorithm found for id $id") } /** - * Return the [HashAlgorithm] value that corresponds to the provided name. - * If an invalid algorithm name was provided, null is returned. + * Return the [HashAlgorithm] value that corresponds to the provided name. If an invalid + * algorithm name was provided, null is returned. * - * See [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4) + * See + * [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4) * for a list of algorithms and names. * * @param name text name @@ -65,12 +62,9 @@ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) { @JvmStatic fun fromName(name: String): HashAlgorithm? { return name.uppercase().let { algoName -> - values().firstOrNull { - it.algorithmName == algoName - } ?: values().firstOrNull { - it.algorithmName == algoName.replace("-", "") - } + values().firstOrNull { it.algorithmName == algoName } + ?: values().firstOrNull { it.algorithmName == algoName.replace("-", "") } } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/KeyFlag.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/KeyFlag.kt index d8686cd6..b8bc2d96 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/KeyFlag.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/KeyFlag.kt @@ -6,40 +6,26 @@ package org.pgpainless.algorithm enum class KeyFlag(val flag: Int) { - /** - * This key may be used to certify third-party keys. - */ - CERTIFY_OTHER (1), + /** This key may be used to certify third-party keys. */ + CERTIFY_OTHER(1), - /** - * This key may be used to sign data. - */ - SIGN_DATA (2), + /** This key may be used to sign data. */ + SIGN_DATA(2), - /** - * This key may be used to encrypt communications. - */ - ENCRYPT_COMMS (4), + /** This key may be used to encrypt communications. */ + ENCRYPT_COMMS(4), - /** - * This key may be used to encrypt storage. - */ + /** This key may be used to encrypt storage. */ ENCRYPT_STORAGE(8), - /** - * The private component of this key may have been split by a secret-sharing mechanism. - */ - SPLIT (16), + /** The private component of this key may have been split by a secret-sharing mechanism. */ + SPLIT(16), - /** - * This key may be used for authentication. - */ - AUTHENTICATION (32), + /** This key may be used for authentication. */ + AUTHENTICATION(32), - /** - * The private component of this key may be in the possession of more than one person. - */ - SHARED (128), + /** The private component of this key may be in the possession of more than one person. */ + SHARED(128), ; companion object { @@ -52,9 +38,7 @@ enum class KeyFlag(val flag: Int) { */ @JvmStatic fun fromBitmask(bitmask: Int): List { - return values().filter { - it.flag and bitmask != 0 - } + return values().filter { it.flag and bitmask != 0 } } /** @@ -65,13 +49,12 @@ enum class KeyFlag(val flag: Int) { */ @JvmStatic fun toBitmask(vararg flags: KeyFlag): Int { - return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f } - ?: 0 + return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f } ?: 0 } /** - * Return true if the provided bitmask has the bit for the provided flag set. - * Return false if the mask does not contain the flag. + * Return true if the provided bitmask has the bit for the provided flag set. Return false + * if the mask does not contain the flag. * * @param mask bitmask * @param flag flag to be tested for @@ -84,9 +67,7 @@ enum class KeyFlag(val flag: Int) { @JvmStatic fun containsAny(mask: Int, vararg flags: KeyFlag): Boolean { - return flags.any { - hasKeyFlag(mask, it) - } + return flags.any { hasKeyFlag(mask, it) } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/OpenPgpPacket.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/OpenPgpPacket.kt index f05599bd..ecb8db35 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/OpenPgpPacket.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/OpenPgpPacket.kt @@ -22,7 +22,6 @@ enum class OpenPgpPacket(val tag: Int) { UATTR(17), SEIPD(18), MDC(19), - EXP_1(60), EXP_2(61), EXP_3(62), @@ -32,15 +31,13 @@ enum class OpenPgpPacket(val tag: Int) { companion object { @JvmStatic fun fromTag(tag: Int): OpenPgpPacket? { - return values().firstOrNull { - it.tag == tag - } + return values().firstOrNull { it.tag == tag } } @JvmStatic fun requireFromTag(tag: Int): OpenPgpPacket { - return fromTag(tag) ?: - throw NoSuchElementException("No OpenPGP packet known for tag $tag") + return fromTag(tag) + ?: throw NoSuchElementException("No OpenPGP packet known for tag $tag") } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/PublicKeyAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/PublicKeyAlgorithm.kt index 9fad8e7d..4a218673 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/PublicKeyAlgorithm.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/PublicKeyAlgorithm.kt @@ -10,87 +10,73 @@ package org.pgpainless.algorithm * See [RFC4880: Public-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.1) */ enum class PublicKeyAlgorithm( - val algorithmId: Int, - val signingCapable: Boolean, - val encryptionCapable: Boolean) { + val algorithmId: Int, + val signingCapable: Boolean, + val encryptionCapable: Boolean +) { - /** - * RSA capable of encryption and signatures. - */ - RSA_GENERAL (1, true, true), + /** RSA capable of encryption and signatures. */ + RSA_GENERAL(1, true, true), /** * RSA with usage encryption. * - * @deprecated see Deprecation notice + * @deprecated see Deprecation + * notice */ - @Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL", - ReplaceWith("RSA_GENERAL")) - RSA_ENCRYPT (2, false, true), + @Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL")) + RSA_ENCRYPT(2, false, true), /** * RSA with usage of creating signatures. * - * @deprecated see Deprecation notice + * @deprecated see Deprecation + * notice */ - @Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL", - ReplaceWith("RSA_GENERAL")) - RSA_SIGN (3, true, false), + @Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL")) + RSA_SIGN(3, true, false), - /** - * ElGamal with usage encryption. - */ - ELGAMAL_ENCRYPT (16, false, true), + /** ElGamal with usage encryption. */ + ELGAMAL_ENCRYPT(16, false, true), - /** - * Digital Signature Algorithm. - */ - DSA (17, true, false), + /** Digital Signature Algorithm. */ + DSA(17, true, false), - /** - * Elliptic Curve Diffie-Hellman. - */ - ECDH (18, false, true), + /** Elliptic Curve Diffie-Hellman. */ + ECDH(18, false, true), - /** - * Elliptic Curve Digital Signature Algorithm. - */ - ECDSA (19, true, false), + /** Elliptic Curve Digital Signature Algorithm. */ + ECDSA(19, true, false), /** * ElGamal General. * - * @deprecated see Deprecation notice + * @deprecated see Deprecation + * notice */ - @Deprecated("ElGamal is deprecated") - ELGAMAL_GENERAL (20, true, true), + @Deprecated("ElGamal is deprecated") ELGAMAL_GENERAL(20, true, true), - /** - * Diffie-Hellman key exchange algorithm. - */ - DIFFIE_HELLMAN (21, false, true), + /** Diffie-Hellman key exchange algorithm. */ + DIFFIE_HELLMAN(21, false, true), - /** - * Digital Signature Algorithm based on twisted Edwards Curves. - */ - EDDSA (22, true, false), + /** Digital Signature Algorithm based on twisted Edwards Curves. */ + EDDSA(22, true, false), ; fun isSigningCapable(): Boolean = signingCapable + fun isEncryptionCapable(): Boolean = encryptionCapable companion object { @JvmStatic fun fromId(id: Int): PublicKeyAlgorithm? { - return values().firstOrNull { - it.algorithmId == id - } + return values().firstOrNull { it.algorithmId == id } } @JvmStatic fun requireFromId(id: Int): PublicKeyAlgorithm { - return fromId(id) ?: - throw NoSuchElementException("No PublicKeyAlgorithm found for id $id") + return fromId(id) + ?: throw NoSuchElementException("No PublicKeyAlgorithm found for id $id") } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationState.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationState.kt index d5d95d45..ba288469 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationState.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationState.kt @@ -4,53 +4,48 @@ package org.pgpainless.algorithm -import org.pgpainless.util.DateUtil import java.lang.AssertionError import java.util.* import kotlin.NoSuchElementException +import org.pgpainless.util.DateUtil -class RevocationState private constructor( - val type: RevocationStateType, - private val _date: Date?): Comparable { +class RevocationState private constructor(val type: RevocationStateType, private val _date: Date?) : + Comparable { val date: Date get() { if (!isSoftRevocation()) { - throw NoSuchElementException("RevocationStateType is not equal to 'softRevoked'. Cannot extract date.") + throw NoSuchElementException( + "RevocationStateType is not equal to 'softRevoked'. Cannot extract date.") } return _date!! } - private constructor(type: RevocationStateType): this(type, null) + private constructor(type: RevocationStateType) : this(type, null) fun isSoftRevocation() = type == RevocationStateType.softRevoked + fun isHardRevocation() = type == RevocationStateType.hardRevoked + fun isNotRevoked() = type == RevocationStateType.notRevoked companion object { - @JvmStatic - fun notRevoked() = RevocationState(RevocationStateType.notRevoked) + @JvmStatic fun notRevoked() = RevocationState(RevocationStateType.notRevoked) @JvmStatic fun softRevoked(date: Date) = RevocationState(RevocationStateType.softRevoked, date) - @JvmStatic - fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked) + @JvmStatic fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked) } override fun compareTo(other: RevocationState): Int { - return when(type) { - RevocationStateType.notRevoked -> - if (other.isNotRevoked()) 0 - else -1 + return when (type) { + RevocationStateType.notRevoked -> if (other.isNotRevoked()) 0 else -1 RevocationStateType.softRevoked -> if (other.isNotRevoked()) 1 // Compare soft dates in reverse - else if (other.isSoftRevocation()) other.date.compareTo(date) - else -1 - RevocationStateType.hardRevoked -> - if (other.isHardRevocation()) 0 - else 1 + else if (other.isSoftRevocation()) other.date.compareTo(date) else -1 + RevocationStateType.hardRevoked -> if (other.isHardRevocation()) 0 else 1 else -> throw AssertionError("Unknown type: $type") } } @@ -80,8 +75,9 @@ class RevocationState private constructor( return false } if (isSoftRevocation()) { - return DateUtil.toSecondsPrecision(date).time == DateUtil.toSecondsPrecision(other.date).time + return DateUtil.toSecondsPrecision(date).time == + DateUtil.toSecondsPrecision(other.date).time } return true } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationStateType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationStateType.kt index bf18e27f..1f95ad7b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationStateType.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/RevocationStateType.kt @@ -5,18 +5,12 @@ package org.pgpainless.algorithm enum class RevocationStateType { - /** - * Certificate is not revoked. - */ + /** Certificate is not revoked. */ notRevoked, - /** - * Certificate is revoked with a soft revocation. - */ + /** Certificate is revoked with a soft revocation. */ softRevoked, - /** - * Certificate is revoked with a hard revocation. - */ + /** Certificate is revoked with a hard revocation. */ hardRevoked -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureSubpacket.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureSubpacket.kt index fcb0c90a..a0efd618 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureSubpacket.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureSubpacket.kt @@ -7,23 +7,24 @@ package org.pgpainless.algorithm import org.bouncycastle.bcpg.SignatureSubpacketTags.* /** - * Enumeration of possible subpackets that might be found in the hashed and unhashed area of an OpenPGP signature. + * Enumeration of possible subpackets that might be found in the hashed and unhashed area of an + * OpenPGP signature. * - * See [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1) + * See + * [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1) */ enum class SignatureSubpacket(val code: Int) { /** - * The time the signature was made. - * MUST be present in the hashed area of the signature. + * The time the signature was made. MUST be present in the hashed area of the signature. * * See [Signature Creation Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.4) */ signatureCreationTime(2), /** - * The validity period of the signature. This is the number of seconds - * after the signature creation time that the signature expires. If - * this is not present or has a value of zero, it never expires. + * The validity period of the signature. This is the number of seconds after the signature + * creation time that the signature expires. If this is not present or has a value of zero, it + * never expires. * * See [Signature Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.10) */ @@ -37,90 +38,76 @@ enum class SignatureSubpacket(val code: Int) { exportableCertification(4), /** - * Signer asserts that the key is not only valid but also trustworthy at - * the specified level. Level 0 has the same meaning as an ordinary - * validity signature. Level 1 means that the signed key is asserted to - * be a valid, trusted introducer, with the 2nd octet of the body - * specifying the degree of trust. Level 2 means that the signed key is - * asserted to be trusted to issue level 1 trust signatures, i.e., that - * it is a "meta introducer". Generally, a level n trust signature - * asserts that a key is trusted to issue level n-1 trust signatures. - * The trust amount is in a range from 0-255, interpreted such that - * values less than 120 indicate partial trust and values of 120 or - * greater indicate complete trust. Implementations SHOULD emit values - * of 60 for partial trust and 120 for complete trust. + * Signer asserts that the key is not only valid but also trustworthy at the specified level. + * Level 0 has the same meaning as an ordinary validity signature. Level 1 means that the signed + * key is asserted to be a valid, trusted introducer, with the 2nd octet of the body specifying + * the degree of trust. Level 2 means that the signed key is asserted to be trusted to issue + * level 1 trust signatures, i.e., that it is a "meta introducer". Generally, a level n trust + * signature asserts that a key is trusted to issue level n-1 trust signatures. The trust amount + * is in a range from 0-255, interpreted such that values less than 120 indicate partial trust + * and values of 120 or greater indicate complete trust. Implementations SHOULD emit values of + * 60 for partial trust and 120 for complete trust. * * See [Trust Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.13) */ trustSignature(5), /** - * Used in conjunction with trust Signature packets (of level greater 0) to - * limit the scope of trust that is extended. Only signatures by the - * target key on User IDs that match the regular expression in the body - * of this packet have trust extended by the trust Signature subpacket. - * The regular expression uses the same syntax as the Henry Spencer's - * "almost public domain" regular expression [REGEX] package. A - * description of the syntax is found in Section 8 below. + * Used in conjunction with trust Signature packets (of level greater 0) to limit the scope of + * trust that is extended. Only signatures by the target key on User IDs that match the regular + * expression in the body of this packet have trust extended by the trust Signature subpacket. + * The regular expression uses the same syntax as the Henry Spencer's "almost public domain" + * regular expression [REGEX] package. A description of the syntax is found in Section 8 below. * * See [Regular Expression](https://tools.ietf.org/html/rfc4880#section-5.2.3.14) */ regularExpression(6), /** - * Signature's revocability status. The packet body contains a Boolean - * flag indicating whether the signature is revocable. Signatures that - * are not revocable have any later revocation signatures ignored. They - * represent a commitment by the signer that he cannot revoke his - * signature for the life of his key. If this packet is not present, - * the signature is revocable. + * Signature's revocability status. The packet body contains a Boolean flag indicating whether + * the signature is revocable. Signatures that are not revocable have any later revocation + * signatures ignored. They represent a commitment by the signer that he cannot revoke his + * signature for the life of his key. If this packet is not present, the signature is revocable. * * See [Revocable](https://tools.ietf.org/html/rfc4880#section-5.2.3.12) */ revocable(7), /** - * The validity period of the key. This is the number of seconds after - * the key creation time that the key expires. If this is not present - * or has a value of zero, the key never expires. This is found only on - * a self-signature. + * The validity period of the key. This is the number of seconds after the key creation time + * that the key expires. If this is not present or has a value of zero, the key never expires. + * This is found only on a self-signature. * * See [Key Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.6) */ keyExpirationTime(9), - /** - * Placeholder for backwards compatibility. - */ + /** Placeholder for backwards compatibility. */ placeholder(10), /** - * Symmetric algorithm numbers that indicate which algorithms the keyholder - * prefers to use. The subpackets body is an ordered list of - * octets with the most preferred listed first. It is assumed that only - * algorithms listed are supported by the recipient's software. - * This is only found on a self-signature. + * Symmetric algorithm numbers that indicate which algorithms the keyholder prefers to use. The + * subpackets body is an ordered list of octets with the most preferred listed first. It is + * assumed that only algorithms listed are supported by the recipient's software. This is only + * found on a self-signature. * * See [Preferred Symmetric Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.7) */ preferredSymmetricAlgorithms(11), /** - * Authorizes the specified key to issue revocation signatures for this - * key. Class octet must have bit 0x80 set. If the bit 0x40 is set, - * then this means that the revocation information is sensitive. Other - * bits are for future expansion to other kinds of authorizations. This - * is found on a self-signature. + * Authorizes the specified key to issue revocation signatures for this key. Class octet must + * have bit 0x80 set. If the bit 0x40 is set, then this means that the revocation information is + * sensitive. Other bits are for future expansion to other kinds of authorizations. This is + * found on a self-signature. * - * If the "sensitive" flag is set, the keyholder feels this subpacket - * contains private trust information that describes a real-world - * sensitive relationship. If this flag is set, implementations SHOULD - * NOT export this signature to other users except in cases where the - * data needs to be available: when the signature is being sent to the - * designated revoker, or when it is accompanied by a revocation - * signature from that revoker. Note that it may be appropriate to - * isolate this subpacket within a separate signature so that it is not - * combined with other subpackets that need to be exported. + * If the "sensitive" flag is set, the keyholder feels this subpacket contains private trust + * information that describes a real-world sensitive relationship. If this flag is set, + * implementations SHOULD NOT export this signature to other users except in cases where the + * data needs to be available: when the signature is being sent to the designated revoker, or + * when it is accompanied by a revocation signature from that revoker. Note that it may be + * appropriate to isolate this subpacket within a separate signature so that it is not combined + * with other subpackets that need to be exported. * * See [Revocation Key](https://tools.ietf.org/html/rfc4880#section-5.2.3.15) */ @@ -134,54 +121,48 @@ enum class SignatureSubpacket(val code: Int) { issuerKeyId(16), /** - * This subpacket describes a "notation" on the signature that the - * issuer wishes to make. The notation has a name and a value, each of - * which are strings of octets. There may be more than one notation in - * a signature. Notations can be used for any extension the issuer of - * the signature cares to make. The "flags" field holds four octets of - * flags. + * This subpacket describes a "notation" on the signature that the issuer wishes to make. The + * notation has a name and a value, each of which are strings of octets. There may be more than + * one notation in a signature. Notations can be used for any extension the issuer of the + * signature cares to make. The "flags" field holds four octets of flags. * * See [Notation Data](https://tools.ietf.org/html/rfc4880#section-5.2.3.16) */ notationData(20), /** - * Message digest algorithm numbers that indicate which algorithms the - * keyholder prefers to receive. Like the preferred symmetric - * algorithms, the list is ordered. - * This is only found on a self-signature. + * Message digest algorithm numbers that indicate which algorithms the keyholder prefers to + * receive. Like the preferred symmetric algorithms, the list is ordered. This is only found on + * a self-signature. * * See [Preferred Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.8) */ preferredHashAlgorithms(21), /** - * Compression algorithm numbers that indicate which algorithms the - * keyholder prefers to use. Like the preferred symmetric algorithms, the - * list is ordered. If this subpacket is not included, ZIP is preferred. - * A zero denotes that uncompressed data is preferred; the keyholder's - * software might have no compression software in that implementation. - * This is only found on a self-signature. + * Compression algorithm numbers that indicate which algorithms the keyholder prefers to use. + * Like the preferred symmetric algorithms, the list is ordered. If this subpacket is not + * included, ZIP is preferred. A zero denotes that uncompressed data is preferred; the + * keyholder's software might have no compression software in that implementation. This is only + * found on a self-signature. * * See [Preferred Compressio Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.9) */ preferredCompressionAlgorithms(22), /** - * This is a list of one-bit flags that indicate preferences that the - * keyholder has about how the key is handled on a key server. All - * undefined flags MUST be zero. - * This is found only on a self-signature. + * This is a list of one-bit flags that indicate preferences that the keyholder has about how + * the key is handled on a key server. All undefined flags MUST be zero. This is found only on a + * self-signature. * * See [Key Server Preferences](https://tools.ietf.org/html/rfc4880#section-5.2.3.17) */ keyServerPreferences(23), /** - * This is a URI of a key server that the keyholder prefers be used for - * updates. Note that keys with multiple User IDs can have a preferred - * key server for each User ID. Note also that since this is a URI, the - * key server can actually be a copy of the key retrieved by ftp, http, + * This is a URI of a key server that the keyholder prefers be used for updates. Note that keys + * with multiple User IDs can have a preferred key server for each User ID. Note also that since + * this is a URI, the key server can actually be a copy of the key retrieved by ftp, http, * finger, etc. * * See [Preferred Key Server](https://tools.ietf.org/html/rfc4880#section-5.2.3.18) @@ -189,63 +170,55 @@ enum class SignatureSubpacket(val code: Int) { preferredKeyServers(24), /** - * This is a flag in a User ID's self-signature that states whether this - * User ID is the main User ID for this key. It is reasonable for an - * implementation to resolve ambiguities in preferences, etc. by - * referring to the primary User ID. If this flag is absent, its value - * is zero. If more than one User ID in a key is marked as primary, the - * implementation may resolve the ambiguity in any way it sees fit, but - * it is RECOMMENDED that priority be given to the User ID with the most - * recent self-signature. + * This is a flag in a User ID's self-signature that states whether this User ID is the main + * User ID for this key. It is reasonable for an implementation to resolve ambiguities in + * preferences, etc. by referring to the primary User ID. If this flag is absent, its value is + * zero. If more than one User ID in a key is marked as primary, the implementation may resolve + * the ambiguity in any way it sees fit, but it is RECOMMENDED that priority be given to the + * User ID with the most recent self-signature. * - * When appearing on a self-signature on a User ID packet, this - * subpacket applies only to User ID packets. When appearing on a - * self-signature on a User Attribute packet, this subpacket applies - * only to User Attribute packets. That is to say, there are two - * different and independent "primaries" -- one for User IDs, and one - * for User Attributes. + * When appearing on a self-signature on a User ID packet, this subpacket applies only to User + * ID packets. When appearing on a self-signature on a User Attribute packet, this subpacket + * applies only to User Attribute packets. That is to say, there are two different and + * independent "primaries" -- one for User IDs, and one for User Attributes. * * See [Primary User-ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.19) */ primaryUserId(25), /** - * This subpacket contains a URI of a document that describes the policy - * under which the signature was issued. + * This subpacket contains a URI of a document that describes the policy under which the + * signature was issued. * * See [Policy URL](https://tools.ietf.org/html/rfc4880#section-5.2.3.20) */ policyUrl(26), /** - * This subpacket contains a list of binary flags that hold information - * about a key. It is a string of octets, and an implementation MUST - * NOT assume a fixed size. This is so it can grow over time. If a - * list is shorter than an implementation expects, the unstated flags - * are considered to be zero. + * This subpacket contains a list of binary flags that hold information about a key. It is a + * string of octets, and an implementation MUST NOT assume a fixed size. This is so it can grow + * over time. If a list is shorter than an implementation expects, the unstated flags are + * considered to be zero. * * See [Key Flags](https://tools.ietf.org/html/rfc4880#section-5.2.3.21) */ keyFlags(27), /** - * This subpacket allows a keyholder to state which User ID is - * responsible for the signing. Many keyholders use a single key for - * different purposes, such as business communications as well as - * personal communications. This subpacket allows such a keyholder to - * state which of their roles is making a signature. + * This subpacket allows a keyholder to state which User ID is responsible for the signing. Many + * keyholders use a single key for different purposes, such as business communications as well + * as personal communications. This subpacket allows such a keyholder to state which of their + * roles is making a signature. * * See [Signer's User ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.22) */ signerUserId(28), /** - * This subpacket is used only in key revocation and certification - * revocation signatures. It describes the reason why the key or - * certificate was revoked. + * This subpacket is used only in key revocation and certification revocation signatures. It + * describes the reason why the key or certificate was revoked. * - * The first octet contains a machine-readable code that denotes the - * reason for the revocation: + * The first octet contains a machine-readable code that denotes the reason for the revocation: * * 0 - No reason specified (key revocations or cert revocations) * 1 - Key is superseded (key revocations) @@ -259,117 +232,105 @@ enum class SignatureSubpacket(val code: Int) { revocationReason(29), /** - * The Features subpacket denotes which advanced OpenPGP features a - * user's implementation supports. This is so that as features are - * added to OpenPGP that cannot be backwards-compatible, a user can - * state that they can use that feature. The flags are single bits that - * indicate that a given feature is supported. + * The Features subpacket denotes which advanced OpenPGP features a user's implementation + * supports. This is so that as features are added to OpenPGP that cannot be + * backwards-compatible, a user can state that they can use that feature. The flags are single + * bits that indicate that a given feature is supported. * - * This subpacket is similar to a preferences subpacket, and only - * appears in a self-signature. + * This subpacket is similar to a preferences subpacket, and only appears in a self-signature. * * See [Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24) */ features(30), /** - * This subpacket identifies a specific target signature to which a - * signature refers. For revocation signatures, this subpacket - * provides explicit designation of which signature is being revoked. - * For a third-party or timestamp signature, this designates what - * signature is signed. All arguments are an identifier of that target - * signature. + * This subpacket identifies a specific target signature to which a signature refers. For + * revocation signatures, this subpacket provides explicit designation of which signature is + * being revoked. For a third-party or timestamp signature, this designates what signature is + * signed. All arguments are an identifier of that target signature. * - * The N octets of hash data MUST be the size of the hash of the - * signature. For example, a target signature with a SHA-1 hash MUST - * have 20 octets of hash data. + * The N octets of hash data MUST be the size of the hash of the signature. For example, a + * target signature with a SHA-1 hash MUST have 20 octets of hash data. * * See [Signature Target](https://tools.ietf.org/html/rfc4880#section-5.2.3.25) */ signatureTarget(31), /** - * This subpacket contains a complete Signature packet body as - * specified in Section 5.2 above. It is useful when one signature - * needs to refer to, or be incorporated in, another signature. + * This subpacket contains a complete Signature packet body as specified in Section 5.2 above. + * It is useful when one signature needs to refer to, or be incorporated in, another signature. * * See [Embedded Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.26) */ embeddedSignature(32), /** - * The OpenPGP Key fingerprint of the key issuing the signature. This - * subpacket SHOULD be included in all signatures. If the version of - * the issuing key is 4 and an Issuer subpacket is also included in the - * signature, the key ID of the Issuer subpacket MUST match the low 64 - * bits of the fingerprint. + * The OpenPGP Key fingerprint of the key issuing the signature. This subpacket SHOULD be + * included in all signatures. If the version of the issuing key is 4 and an Issuer subpacket is + * also included in the signature, the key ID of the Issuer subpacket MUST match the low 64 bits + * of the fingerprint. * - * Note that the length N of the fingerprint for a version 4 key is 20 - * octets; for a version 5 key N is 32. + * Note that the length N of the fingerprint for a version 4 key is 20 octets; for a version 5 + * key N is 32. * - * See [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28) + * See + * [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28) */ issuerFingerprint(33), /** - * AEAD algorithm numbers that indicate which AEAD algorithms the - * keyholder prefers to use. The subpackets body is an ordered list of - * octets with the most preferred listed first. It is assumed that only - * algorithms listed are supported by the recipient's software. - * This is only found on a self-signature. - * Note that support for the AEAD Encrypted Data packet in the general - * is indicated by a Feature Flag. + * AEAD algorithm numbers that indicate which AEAD algorithms the keyholder prefers to use. The + * subpackets body is an ordered list of octets with the most preferred listed first. It is + * assumed that only algorithms listed are supported by the recipient's software. This is only + * found on a self-signature. Note that support for the AEAD Encrypted Data packet in the + * general is indicated by a Feature Flag. * - * See [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8) + * See + * [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8) */ preferredAEADAlgorithms(39), /** - * The OpenPGP Key fingerprint of the intended recipient primary key. - * If one or more subpackets of this type are included in a signature, - * it SHOULD be considered valid only in an encrypted context, where the - * key it was encrypted to is one of the indicated primary keys, or one - * of their subkeys. This can be used to prevent forwarding a signature - * outside its intended, encrypted context. + * The OpenPGP Key fingerprint of the intended recipient primary key. If one or more subpackets + * of this type are included in a signature, it SHOULD be considered valid only in an encrypted + * context, where the key it was encrypted to is one of the indicated primary keys, or one of + * their subkeys. This can be used to prevent forwarding a signature outside its intended, + * encrypted context. * - * Note that the length N of the fingerprint for a version 4 key is 20 - * octets; for a version 5 key N is 32. + * Note that the length N of the fingerprint for a version 4 key is 20 octets; for a version 5 + * key N is 32. * - * See [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29) + * See + * [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29) */ intendedRecipientFingerprint(35), /** - * This subpacket MUST only appear as a hashed subpacket of an - * Attestation Key Signature. It has no meaning in any other signature - * type. It is used by the primary key to attest to a set of third- - * party certifications over the associated User ID or User Attribute. - * This enables the holder of an OpenPGP primary key to mark specific - * third-party certifications as re-distributable with the rest of the - * Transferable Public Key (see the "No-modify" flag in "Key Server - * Preferences", above). Implementations MUST include exactly one - * Attested Certification subpacket in any generated Attestation Key - * Signature. + * This subpacket MUST only appear as a hashed subpacket of an Attestation Key Signature. It has + * no meaning in any other signature type. It is used by the primary key to attest to a set of + * third- party certifications over the associated User ID or User Attribute. This enables the + * holder of an OpenPGP primary key to mark specific third-party certifications as + * re-distributable with the rest of the Transferable Public Key (see the "No-modify" flag in + * "Key Server Preferences", above). Implementations MUST include exactly one Attested + * Certification subpacket in any generated Attestation Key Signature. * - * See [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30) + * See + * [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30) */ - attestedCertification(37) - ; - + attestedCertification(37); + companion object { - + /** - * Return the [SignatureSubpacket] that corresponds to the provided id. - * If an unmatched code is presented, return null. + * Return the [SignatureSubpacket] that corresponds to the provided id. If an unmatched code + * is presented, return null. * * @param code id * @return signature subpacket */ @JvmStatic fun fromCode(code: Int): SignatureSubpacket? { - return values().firstOrNull { - it.code == code - } + return values().firstOrNull { it.code == code } } /** @@ -381,21 +342,20 @@ enum class SignatureSubpacket(val code: Int) { */ @JvmStatic fun requireFromCode(code: Int): SignatureSubpacket { - return fromCode(code) ?: - throw NoSuchElementException("No SignatureSubpacket tag found with code $code") + return fromCode(code) + ?: throw NoSuchElementException("No SignatureSubpacket tag found with code $code") } /** - * Convert an array of signature subpacket tags into a list of [SignatureSubpacket SignatureSubpackets]. + * Convert an array of signature subpacket tags into a list of + * [SignatureSubpacket SignatureSubpackets]. * * @param codes array of codes * @return list of subpackets */ @JvmStatic fun fromCodes(vararg codes: Int): List { - return codes.toList().mapNotNull { - fromCode(it) - } + return codes.toList().mapNotNull { fromCode(it) } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureType.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureType.kt index 1b708a03..865549b8 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureType.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SignatureType.kt @@ -7,150 +7,120 @@ package org.pgpainless.algorithm import org.bouncycastle.openpgp.PGPSignature /** - * Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1 - * See [PGPSignature] for comparison. + * Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1 See [PGPSignature] for + * comparison. * * See [rfc4880 §5.2.1. Signature Types](https://tools.ietf.org/html/rfc4880#section-5.11) */ enum class SignatureType(val code: Int) { /** - * Signature of a binary document. - * This means the signer owns it, created it, or certifies that it - * has not been modified. + * Signature of a binary document. This means the signer owns it, created it, or certifies that + * it has not been modified. */ BINARY_DOCUMENT(0x00), /** - * Signature of a canonical text document. - * This means the signer owns it, created it, or certifies that it - * has not been modified. The signature is calculated over the text - * data with its line endings converted to {@code }. + * Signature of a canonical text document. This means the signer owns it, created it, or + * certifies that it has not been modified. The signature is calculated over the text data with + * its line endings converted to {@code }. */ CANONICAL_TEXT_DOCUMENT(0x01), /** - * Standalone signature. - * This signature is a signature of only its own subpacket contents. - * It is calculated identically to a signature over a zero-length - * binary document. Note that it doesn't make sense to have a V3 - * standalone signature. + * Standalone signature. This signature is a signature of only its own subpacket contents. It is + * calculated identically to a signature over a zero-length binary document. Note that it + * doesn't make sense to have a V3 standalone signature. */ STANDALONE(0x02), /** - * Generic certification of a User ID and Public-Key packet. - * The issuer of this certification does not make any particular - * assertion as to how well the certifier has checked that the owner - * of the key is in fact the person described by the User ID. + * Generic certification of a User ID and Public-Key packet. The issuer of this certification + * does not make any particular assertion as to how well the certifier has checked that the + * owner of the key is in fact the person described by the User ID. */ GENERIC_CERTIFICATION(0x10), /** - * Persona certification of a User ID and Public-Key packet. - * The issuer of this certification has not done any verification of - * the claim that the owner of this key is the User ID specified. + * Persona certification of a User ID and Public-Key packet. The issuer of this certification + * has not done any verification of the claim that the owner of this key is the User ID + * specified. */ NO_CERTIFICATION(0x11), /** - * Casual certification of a User ID and Public-Key packet. - * The issuer of this certification has done some casual - * verification of the claim of identity. + * Casual certification of a User ID and Public-Key packet. The issuer of this certification has + * done some casual verification of the claim of identity. */ CASUAL_CERTIFICATION(0x12), /** - * Positive certification of a User ID and Public-Key packet. - * The issuer of this certification has done substantial - * verification of the claim of identity. + * Positive certification of a User ID and Public-Key packet. The issuer of this certification + * has done substantial verification of the claim of identity. */ POSITIVE_CERTIFICATION(0x13), /** - * Subkey Binding Signature. - * This signature is a statement by the top-level signing key that - * indicates that it owns the subkey. This signature is calculated - * directly on the primary key and subkey, and not on any User ID or - * other packets. A signature that binds a signing subkey MUST have - * an Embedded Signature subpacket in this binding signature that - * contains a [#PRIMARYKEY_BINDING] signature made by the - * signing subkey on the primary key and subkey. + * Subkey Binding Signature. This signature is a statement by the top-level signing key that + * indicates that it owns the subkey. This signature is calculated directly on the primary key + * and subkey, and not on any User ID or other packets. A signature that binds a signing subkey + * MUST have an Embedded Signature subpacket in this binding signature that contains a + * [#PRIMARYKEY_BINDING] signature made by the signing subkey on the primary key and subkey. */ SUBKEY_BINDING(0x18), /** - * Primary Key Binding Signature - * This signature is a statement by a signing subkey, indicating - * that it is owned by the primary key and subkey. This signature - * is calculated the same way as a [#SUBKEY_BINDING] signature: - * directly on the primary key and subkey, and not on any User ID or - * other packets. + * Primary Key Binding Signature This signature is a statement by a signing subkey, indicating + * that it is owned by the primary key and subkey. This signature is calculated the same way as + * a [#SUBKEY_BINDING] signature: directly on the primary key and subkey, and not on any User ID + * or other packets. */ PRIMARYKEY_BINDING(0x19), /** - * Signature directly on a key - * This signature is calculated directly on a key. It binds the - * information in the Signature subpackets to the key, and is - * appropriate to be used for subpackets that provide information - * about the key, such as the Revocation Key subpacket. It is also - * appropriate for statements that non-self certifiers want to make - * about the key itself, rather than the binding between a key and a - * name. + * Signature directly on a key This signature is calculated directly on a key. It binds the + * information in the Signature subpackets to the key, and is appropriate to be used for + * subpackets that provide information about the key, such as the Revocation Key subpacket. It + * is also appropriate for statements that non-self certifiers want to make about the key + * itself, rather than the binding between a key and a name. */ DIRECT_KEY(0x1f), /** - * Key revocation signature - * The signature is calculated directly on the key being revoked. A - * revoked key is not to be used. Only revocation signatures by the - * key being revoked, or by an authorized revocation key, should be - * considered valid revocation signatures. + * Key revocation signature The signature is calculated directly on the key being revoked. A + * revoked key is not to be used. Only revocation signatures by the key being revoked, or by an + * authorized revocation key, should be considered valid revocation signatures. */ KEY_REVOCATION(0x20), /** - * Subkey revocation signature - * The signature is calculated directly on the subkey being revoked. - * A revoked subkey is not to be used. Only revocation signatures - * by the top-level signature key that is bound to this subkey, or - * by an authorized revocation key, should be considered valid + * Subkey revocation signature The signature is calculated directly on the subkey being revoked. + * A revoked subkey is not to be used. Only revocation signatures by the top-level signature key + * that is bound to this subkey, or by an authorized revocation key, should be considered valid * revocation signatures. */ SUBKEY_REVOCATION(0x28), /** - * Certification revocation signature - * This signature revokes an earlier User ID certification signature - * (signature class 0x10 through 0x13) or signature [#DIRECT_KEY]. - * It should be issued by the same key that issued the - * revoked signature or an authorized revocation key. The signature - * is computed over the same data as the certificate that it - * revokes, and should have a later creation date than that - * certificate. + * Certification revocation signature This signature revokes an earlier User ID certification + * signature (signature class 0x10 through 0x13) or signature [#DIRECT_KEY]. It should be issued + * by the same key that issued the revoked signature or an authorized revocation key. The + * signature is computed over the same data as the certificate that it revokes, and should have + * a later creation date than that certificate. */ CERTIFICATION_REVOCATION(0x30), - /** - * Timestamp signature. - * This signature is only meaningful for the timestamp contained in - * it. - */ + /** Timestamp signature. This signature is only meaningful for the timestamp contained in it. */ TIMESTAMP(0x40), /** - * Third-Party Confirmation signature. - * This signature is a signature over some other OpenPGP Signature - * packet(s). It is analogous to a notary seal on the signed data. - * A third-party signature SHOULD include Signature Target - * subpacket(s) to give easy identification. Note that we really do - * mean SHOULD. There are plausible uses for this (such as a blind - * party that only sees the signature, not the key or source - * document) that cannot include a target subpacket. + * Third-Party Confirmation signature. This signature is a signature over some other OpenPGP + * Signature packet(s). It is analogous to a notary seal on the signed data. A third-party + * signature SHOULD include Signature Target subpacket(s) to give easy identification. Note that + * we really do mean SHOULD. There are plausible uses for this (such as a blind party that only + * sees the signature, not the key or source document) that cannot include a target subpacket. */ - THIRD_PARTY_CONFIRMATION(0x50) - ; + THIRD_PARTY_CONFIRMATION(0x50); companion object { @@ -162,9 +132,7 @@ enum class SignatureType(val code: Int) { */ @JvmStatic fun fromCode(code: Int): SignatureType? { - return values().firstOrNull { - it.code == code - } + return values().firstOrNull { it.code == code } } /** @@ -176,8 +144,9 @@ enum class SignatureType(val code: Int) { */ @JvmStatic fun requireFromCode(code: Int): SignatureType { - return fromCode(code) ?: - throw NoSuchElementException("Signature type 0x${Integer.toHexString(code)} appears to be invalid.") + return fromCode(code) + ?: throw NoSuchElementException( + "Signature type 0x${Integer.toHexString(code)} appears to be invalid.") } /** @@ -188,8 +157,7 @@ enum class SignatureType(val code: Int) { * @throws IllegalArgumentException in case of an unmatched signature type code */ @JvmStatic - @Deprecated("Deprecated in favor of requireFromCode", - ReplaceWith("requireFromCode")) + @Deprecated("Deprecated in favor of requireFromCode", ReplaceWith("requireFromCode")) fun valueOf(code: Int): SignatureType { try { return requireFromCode(code) @@ -197,12 +165,12 @@ enum class SignatureType(val code: Int) { throw IllegalArgumentException(e.message) } } - + @JvmStatic fun isRevocationSignature(signatureType: Int): Boolean { return isRevocationSignature(valueOf(signatureType)) } - + @JvmStatic fun isRevocationSignature(signatureType: SignatureType): Boolean { return when (signatureType) { @@ -218,11 +186,11 @@ enum class SignatureType(val code: Int) { DIRECT_KEY, TIMESTAMP, THIRD_PARTY_CONFIRMATION -> false - KEY_REVOCATION, - SUBKEY_REVOCATION, + KEY_REVOCATION, + SUBKEY_REVOCATION, CERTIFICATION_REVOCATION -> true else -> throw IllegalArgumentException("Unknown signature type: $signatureType") } } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/StreamEncoding.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/StreamEncoding.kt index 391797b1..74b2a56b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/StreamEncoding.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/StreamEncoding.kt @@ -11,61 +11,52 @@ package org.pgpainless.algorithm */ enum class StreamEncoding(val code: Char) { - /** - * The Literal packet contains binary data. - */ + /** The Literal packet contains binary data. */ BINARY('b'), /** - * The Literal packet contains text data, and thus may need line ends converted to local form, or other - * text-mode changes. + * The Literal packet contains text data, and thus may need line ends converted to local form, + * or other text-mode changes. */ TEXT('t'), - /** - * Indication that the implementation believes that the literal data contains UTF-8 text. - */ + /** Indication that the implementation believes that the literal data contains UTF-8 text. */ UTF8('u'), /** - * Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions. - * RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one). - * Both of these local modes are deprecated. + * Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local + * conversions. RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral + * one). Both of these local modes are deprecated. */ - @Deprecated("LOCAL is deprecated.") - LOCAL('l'), + @Deprecated("LOCAL is deprecated.") LOCAL('l'), ; - companion object { /** - * Return the [StreamEncoding] corresponding to the provided code identifier. - * If no matching encoding is found, return null. + * Return the [StreamEncoding] corresponding to the provided code identifier. If no matching + * encoding is found, return null. * * @param code identifier * @return encoding enum */ @JvmStatic fun fromCode(code: Int): StreamEncoding? { - return values().firstOrNull { - it.code == code.toChar() - } ?: if (code == 1) return LOCAL else null + return values().firstOrNull { it.code == code.toChar() } + ?: if (code == 1) return LOCAL else null } /** - * Return the [StreamEncoding] corresponding to the provided code identifier. - * If no matching encoding is found, throw a [NoSuchElementException]. + * Return the [StreamEncoding] corresponding to the provided code identifier. If no matching + * encoding is found, throw a [NoSuchElementException]. * * @param code identifier * @return encoding enum - * * @throws NoSuchElementException in case of an unmatched identifier */ @JvmStatic fun requireFromCode(code: Int): StreamEncoding { - return fromCode(code) ?: - throw NoSuchElementException("No StreamEncoding found for code $code") + return fromCode(code) + ?: throw NoSuchElementException("No StreamEncoding found for code $code") } } - -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt index ae72ff05..bfd32343 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/SymmetricKeyAlgorithm.kt @@ -11,110 +11,79 @@ package org.pgpainless.algorithm */ enum class SymmetricKeyAlgorithm(val algorithmId: Int) { - /** - * Plaintext or unencrypted data. - */ - NULL (0), + /** Plaintext or unencrypted data. */ + NULL(0), /** * IDEA is deprecated. + * * @deprecated use a different algorithm. */ - @Deprecated("IDEA is deprecated.") - IDEA (1), + @Deprecated("IDEA is deprecated.") IDEA(1), - /** - * TripleDES (DES-EDE - 168 bit key derived from 192). - */ - TRIPLE_DES (2), + /** TripleDES (DES-EDE - 168 bit key derived from 192). */ + TRIPLE_DES(2), - /** - * CAST5 (128-bit key, as per RFC2144). - */ - CAST5 (3), + /** CAST5 (128-bit key, as per RFC2144). */ + CAST5(3), - /** - * Blowfish (128-bit key, 16 rounds). - */ - BLOWFISH (4), + /** Blowfish (128-bit key, 16 rounds). */ + BLOWFISH(4), - /** - * Reserved in RFC4880. - * SAFER-SK128 (13 rounds) - */ - SAFER (5), + /** Reserved in RFC4880. SAFER-SK128 (13 rounds) */ + SAFER(5), - /** - * Reserved in RFC4880. - * Reserved for DES/SK - */ - DES (6), + /** Reserved in RFC4880. Reserved for DES/SK */ + DES(6), - /** - * AES with 128-bit key. - */ - AES_128 (7), + /** AES with 128-bit key. */ + AES_128(7), - /** - * AES with 192-bit key. - */ - AES_192 (8), + /** AES with 192-bit key. */ + AES_192(8), - /** - * AES with 256-bit key. - */ - AES_256 (9), + /** AES with 256-bit key. */ + AES_256(9), - /** - * Twofish with 256-bit key. - */ - TWOFISH (10), + /** Twofish with 256-bit key. */ + TWOFISH(10), - /** - * Reserved for Camellia with 128-bit key. - */ - CAMELLIA_128 (11), + /** Reserved for Camellia with 128-bit key. */ + CAMELLIA_128(11), - /** - * Reserved for Camellia with 192-bit key. - */ - CAMELLIA_192 (12), + /** Reserved for Camellia with 192-bit key. */ + CAMELLIA_192(12), - /** - * Reserved for Camellia with 256-bit key. - */ - CAMELLIA_256 (13), + /** Reserved for Camellia with 256-bit key. */ + CAMELLIA_256(13), ; companion object { /** - * Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. - * If an invalid id is provided, null is returned. + * Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If + * an invalid id is provided, null is returned. * * @param id numeric algorithm id * @return symmetric key algorithm enum */ @JvmStatic fun fromId(id: Int): SymmetricKeyAlgorithm? { - return values().firstOrNull { - it.algorithmId == id - } + return values().firstOrNull { it.algorithmId == id } } /** - * Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. - * If an invalid id is provided, throw a [NoSuchElementException]. + * Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If + * an invalid id is provided, throw a [NoSuchElementException]. * * @param id numeric algorithm id * @return symmetric key algorithm enum - * * @throws NoSuchElementException if an unmatched id is provided */ @JvmStatic fun requireFromId(id: Int): SymmetricKeyAlgorithm { - return fromId(id) ?: - throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id") + return fromId(id) + ?: throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id") } } -} \ No newline at end of file +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt index 695632a2..5b9f4c40 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/Trustworthiness.kt @@ -5,26 +5,25 @@ package org.pgpainless.algorithm /** - * Facade class for [org.bouncycastle.bcpg.sig.TrustSignature]. - * A trust signature subpacket marks the trustworthiness of a certificate and defines its capabilities to act - * as a trusted introducer. + * Facade class for [org.bouncycastle.bcpg.sig.TrustSignature]. A trust signature subpacket marks + * the trustworthiness of a certificate and defines its capabilities to act as a trusted introducer. */ class Trustworthiness(amount: Int, depth: Int) { val depth = capDepth(depth) val amount = capAmount(amount) /** - * Returns true, if the trust amount is equal to 0. - * This means the key is not trusted. + * Returns true, if the trust amount is equal to 0. This means the key is not trusted. * * Otherwise return false + * * @return true if untrusted */ fun isNotTrusted() = amount == NOT_TRUSTED /** - * Return true if the certificate is at least marginally trusted. - * That is the case, if the trust amount is greater than 0. + * Return true if the certificate is at least marginally trusted. That is the case, if the trust + * amount is greater than 0. * * @return true if the cert is at least marginally trusted */ @@ -46,7 +45,8 @@ class Trustworthiness(amount: Int, depth: Int) { fun isIntroducer() = depth >= 1 /** - * Return true, if the certified cert can introduce certificates with trust depth of