Skip to content

Commit

Permalink
Add abstract base class OHttpCrypto to share code (#8)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
normanmaurer authored Dec 7, 2023
1 parent a5f9826 commit 3cc0dd8
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> ou
HttpRequest innerRequest = (HttpRequest) msg;
EncapsulationParameters encapsulation = encapsulationFunc.apply(innerRequest);
if (encapsulation != null) {

OHttpClientRequestResponseContext oHttpContext =
new OHttpClientRequestResponseContext(encapsulation, encryption);
HttpHeaders outerHeaders = encapsulation.outerRequestHeaders();
Expand Down
Original file line number Diff line number Diff line change
@@ -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}.
* <p>
* <strong>Important:</strong> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,19 @@
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;
import io.netty.buffer.ByteBuf;
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;
Expand Down Expand Up @@ -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();
}
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand All @@ -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;
Expand All @@ -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;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ private static final class OHttpServerRequestResponseContext extends OHttpReques

private final HybridPublicKeyEncryption encryption;
private final OHttpServerKeys keys;

private OHttpCryptoReceiver receiver;

public OHttpServerRequestResponseContext(
Expand Down

0 comments on commit 3cc0dd8

Please sign in to comment.