From 603f07d014dc7574ec26a1f1e06b113a55141dbe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 24 Aug 2023 16:33:06 +0200 Subject: [PATCH] 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