From 3cc0dd8a4790a92acfcc1ee33c5f0c6f277d8d9e Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Thu, 7 Dec 2023 16:37:20 +0100 Subject: [PATCH] Add abstract base class OHttpCrypto to share code (#8) Motivation: We can share code between OHttpCryptoSender and OHttpCryptoReceiver by introducing an abstract base class. This also allows us to merge the OHttpCryptoUtils into this abstract base class. Modifications: - Introduce abstract base class and extend it - Add javadocs Result: Share code and add docs --- .../codec/ohttp/OHttpClientCodec.java | 1 - .../incubator/codec/ohttp/OHttpCrypto.java | 99 +++++++++++++++++++ .../codec/ohttp/OHttpCryptoReceiver.java | 41 ++++---- .../codec/ohttp/OHttpCryptoSender.java | 52 ++++++---- .../codec/ohttp/OHttpCryptoUtils.java | 53 ---------- .../codec/ohttp/OHttpServerCodec.java | 1 - 6 files changed, 156 insertions(+), 91 deletions(-) create mode 100644 codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCrypto.java delete mode 100644 codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoUtils.java diff --git a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpClientCodec.java b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpClientCodec.java index 17ca4d8..ce1c444 100644 --- a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpClientCodec.java +++ b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpClientCodec.java @@ -240,7 +240,6 @@ protected void encode(ChannelHandlerContext ctx, HttpObject msg, List ou HttpRequest innerRequest = (HttpRequest) msg; EncapsulationParameters encapsulation = encapsulationFunc.apply(innerRequest); if (encapsulation != null) { - OHttpClientRequestResponseContext oHttpContext = new OHttpClientRequestResponseContext(encapsulation, encryption); HttpHeaders outerHeaders = encapsulation.outerRequestHeaders(); diff --git a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCrypto.java b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCrypto.java new file mode 100644 index 0000000..149fa2d --- /dev/null +++ b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCrypto.java @@ -0,0 +1,99 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.incubator.codec.ohttp; + +import io.netty.buffer.ByteBuf; +import io.netty.incubator.codec.hpke.CryptoException; +import io.netty.incubator.codec.hpke.CryptoOperations; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +/** + * Abstract base class for doing OHTTP related crypto operations. + */ +public abstract class OHttpCrypto { + + private static final byte[] AAD_NONE = new byte[0]; + private static final byte[] AAD_FINAL = "final".getBytes(StandardCharsets.US_ASCII); + + // Package-private + OHttpCrypto() { } + + private static ByteBuffer aad(boolean isFinal) { + // The caller might update the position of the ByteBuffer, so we create a new one each time. + return ByteBuffer.wrap(isFinal ? AAD_FINAL : AAD_NONE); + } + + /** + * Return readable part of the given {@link ByteBuf}. + * This method might need to do a copy or not depending on the given {@link ByteBuf}. + *

+ * Important: The returned {@link ByteBuffer} must be used directly and only in the scope + * of the method that is calling it. This is needed as the returned {@link ByteBuffer} is not "stable", which means + * it might be changed at any time. + * + * @param buf the {@link ByteBuf} + * @param length the length of the readable part. + * @return the {@link ByteBuffer} that contains the readable part. + */ + private static ByteBuffer readableTemporaryBuffer(ByteBuf buf, int length) { + if (buf.nioBufferCount() == 1) { + return buf.internalNioBuffer(buf.readerIndex(), length); + } + return buf.nioBuffer(buf.readerIndex(), length); + } + + protected abstract CryptoOperations encryptCrypto(); + + protected abstract CryptoOperations decryptCrypto(); + + protected abstract OHttpCryptoConfiguration configuration(); + + /** + * Encrypt a message of a given length and write the encrypted data to a buffer. + * + * @param message the message + * @param messageLength the length to encrypt + * @param isFinal {@code true} if this is the final message. + * @param out {@link ByteBuf} into which the encrypted data is written. + * @throws CryptoException thrown when an error happens. + */ + public final void encrypt(ByteBuf message, int messageLength, boolean isFinal, ByteBuf out) throws CryptoException { + final ByteBuffer encrypted = encryptCrypto().seal( + aad(isFinal && configuration().useFinalAad()), + readableTemporaryBuffer(message, messageLength)); + message.skipBytes(messageLength); + out.writeBytes(encrypted); + } + + /** + * Decrypt a message of a given length and write the decrypted data to a buffer. + * + * @param message the message + * @param messageLength the length to decrypt + * @param isFinal {@code true} if this is the final message. + * @param out {@link ByteBuf} into which the decrypted data is written. + * @throws CryptoException thrown when an error happens. + */ + public final void decrypt(ByteBuf message, int messageLength, boolean isFinal, ByteBuf out) throws CryptoException { + final ByteBuffer decrypted = decryptCrypto().open( + aad(isFinal && configuration().useFinalAad()), + readableTemporaryBuffer(message, messageLength)); + message.skipBytes(messageLength); + out.writeBytes(decrypted); + } +} diff --git a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoReceiver.java b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoReceiver.java index 724bac7..56f6f71 100644 --- a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoReceiver.java +++ b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoReceiver.java @@ -16,7 +16,6 @@ package io.netty.incubator.codec.ohttp; import io.netty.incubator.codec.hpke.AsymmetricCipherKeyPair; -import io.netty.incubator.codec.hpke.CryptoException; import io.netty.incubator.codec.hpke.CryptoOperations; import io.netty.incubator.codec.hpke.HPKE; import io.netty.incubator.codec.hpke.HPKEContext; @@ -24,16 +23,12 @@ import io.netty.handler.codec.DecoderException; import io.netty.incubator.codec.hpke.HybridPublicKeyEncryption; -import java.nio.ByteBuffer; - -import static io.netty.incubator.codec.ohttp.OHttpCryptoUtils.aad; -import static io.netty.incubator.codec.ohttp.OHttpCryptoUtils.readableTemporaryBuffer; import static java.util.Objects.requireNonNull; /** * {@link OHttpCryptoReceiver} handles all the server-side crypto for an OHTTP request/response. */ -public final class OHttpCryptoReceiver { +public final class OHttpCryptoReceiver extends OHttpCrypto { private final OHttpCryptoConfiguration configuration; private final HPKEContext context; private final byte[] responseNonce; @@ -85,6 +80,12 @@ private Builder() { } } + + /** + * Return a new {@link Builder} that can be used to build a {@link OHttpCryptoReceiver} instance. + * + * @return a builder. + */ public static Builder newBuilder() { return new Builder(); } @@ -111,23 +112,27 @@ private OHttpCryptoReceiver(Builder builder) { this.aead = builder.ciphersuite.createResponseAead(builder.encryption, this.context, enc, this.responseNonce, configuration); } + /** + * Write the response nonce to the given {@link ByteBuf}. + * + * @param out the buffer into which the nonce will be written. + */ public void writeResponseNonce(ByteBuf out) { out.writeBytes(responseNonce); } - public void decrypt(ByteBuf message, int messageLength, boolean isFinal, ByteBuf out) throws CryptoException { - final ByteBuffer decrypted = this.context.open( - aad(isFinal && configuration.useFinalAad()), - readableTemporaryBuffer(message, messageLength)); - message.skipBytes(messageLength); - out.writeBytes(decrypted); + @Override + protected CryptoOperations encryptCrypto() { + return this.aead; + } + + @Override + protected CryptoOperations decryptCrypto() { + return this.context; } - public void encrypt(ByteBuf message, int messageLength, boolean isFinal, ByteBuf out) throws CryptoException { - final ByteBuffer encrypted = this.aead.seal( - aad(isFinal && configuration.useFinalAad()), - readableTemporaryBuffer(message, messageLength)); - message.skipBytes(messageLength); - out.writeBytes(encrypted); + @Override + protected OHttpCryptoConfiguration configuration() { + return configuration; } } diff --git a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoSender.java b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoSender.java index ddb6da2..0c46dcd 100644 --- a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoSender.java +++ b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoSender.java @@ -17,24 +17,19 @@ import io.netty.incubator.codec.hpke.AsymmetricCipherKeyPair; import io.netty.incubator.codec.hpke.AsymmetricKeyParameter; -import io.netty.incubator.codec.hpke.CryptoException; import io.netty.incubator.codec.hpke.CryptoOperations; import io.netty.incubator.codec.hpke.HPKE; import io.netty.incubator.codec.hpke.HPKEContextWithEncapsulation; import io.netty.buffer.ByteBuf; import io.netty.incubator.codec.hpke.HybridPublicKeyEncryption; -import java.nio.ByteBuffer; - -import static io.netty.incubator.codec.ohttp.OHttpCryptoUtils.aad; -import static io.netty.incubator.codec.ohttp.OHttpCryptoUtils.readableTemporaryBuffer; import static java.util.Objects.requireNonNull; /** * {@link OHttpCryptoSender} handles all the client-side crypto for an OHTTP request/response. */ -public final class OHttpCryptoSender { +public final class OHttpCryptoSender extends OHttpCrypto { private final OHttpCryptoConfiguration configuration; private final OHttpCiphersuite ciphersuite; private final HybridPublicKeyEncryption encryption; @@ -81,6 +76,11 @@ private Builder() { } } + /** + * Return a new {@link Builder} that can be used to build a {@link OHttpCryptoSender} instance. + * + * @return a builder. + */ public static Builder newBuilder() { return new Builder(); } @@ -101,15 +101,32 @@ private OHttpCryptoSender(Builder builder) { } } + /** + * Returns the used {@link OHttpCiphersuite}. + * + * @return used ciphersuite. + */ public OHttpCiphersuite ciphersuite() { return this.ciphersuite; } + /** + * Write the header into the given {@link ByteBuf}. + * + * @param out the buffer into which the writes are done. + */ public void writeHeader(ByteBuf out) { this.ciphersuite.encode(out); out.writeBytes(this.context.encapsulation()); } + /** + * Read the response nonce if possible and return {@code true}. If not enough bytes + * are readable it will return {@code false}. + * + * @param in the buffer from which we read. + * @return {@code true} if there were enough bytes to read the nounce, {@code false} otherwise. + */ public boolean readResponseNonce(ByteBuf in) { if (in.readableBytes() < ciphersuite().responseNonceLength()) { return false; @@ -121,19 +138,18 @@ public boolean readResponseNonce(ByteBuf in) { return true; } - public void encrypt(ByteBuf message, int messageLength, boolean isFinal, ByteBuf out) throws CryptoException { - final ByteBuffer encrypted = this.context.seal( - aad(isFinal && configuration.useFinalAad()), - readableTemporaryBuffer(message, messageLength)); - message.skipBytes(messageLength); - out.writeBytes(encrypted); + @Override + protected CryptoOperations encryptCrypto() { + return context; + } + + @Override + protected CryptoOperations decryptCrypto() { + return aead; } - public void decrypt(ByteBuf message, int messageLength, boolean isFinal, ByteBuf out) throws CryptoException { - final ByteBuffer decrypted = this.aead.open( - aad(isFinal && configuration.useFinalAad()), - readableTemporaryBuffer(message, messageLength)); - message.skipBytes(messageLength); - out.writeBytes(decrypted); + @Override + protected OHttpCryptoConfiguration configuration() { + return configuration; } } diff --git a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoUtils.java b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoUtils.java deleted file mode 100644 index 8f8a98c..0000000 --- a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpCryptoUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2023 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.incubator.codec.ohttp; - -import io.netty.buffer.ByteBuf; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -final class OHttpCryptoUtils { - - private OHttpCryptoUtils() { } - - private static final byte[] AAD_NONE = new byte[0]; - private static final byte[] AAD_FINAL = "final".getBytes(StandardCharsets.US_ASCII); - - static ByteBuffer aad(boolean isFinal) { - // The caller might update the position of the ByteBuffer, so we create a new one each time. - return ByteBuffer.wrap(isFinal ? AAD_FINAL : AAD_NONE); - } - - /** - * Return readable part of the given {@link ByteBuf}. - * This method might need to do a copy or not depending on the given {@link ByteBuf}. - *

- * Important: The returned {@link ByteBuffer} must be used directly and only in the scope - * of the method that is calling it. This is needed as the returned {@link ByteBuffer} is not "stable", which means - * it might be changed at any time. - * - * @param buf the {@link ByteBuf} - * @param length the length of the readable part. - * @return the {@link ByteBuffer} that contains the readable part. - */ - static ByteBuffer readableTemporaryBuffer(ByteBuf buf, int length) { - if (buf.nioBufferCount() == 1) { - return buf.internalNioBuffer(buf.readerIndex(), length); - } - return buf.nioBuffer(buf.readerIndex(), length); - } -} diff --git a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpServerCodec.java b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpServerCodec.java index 7121777..16f8b37 100644 --- a/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpServerCodec.java +++ b/codec-ohttp/src/main/java/io/netty/incubator/codec/ohttp/OHttpServerCodec.java @@ -228,7 +228,6 @@ private static final class OHttpServerRequestResponseContext extends OHttpReques private final HybridPublicKeyEncryption encryption; private final OHttpServerKeys keys; - private OHttpCryptoReceiver receiver; public OHttpServerRequestResponseContext(