Skip to content

Commit

Permalink
Add BoringSSL based native HPKE implementation
Browse files Browse the repository at this point in the history
Motivation:

While we already have a BouncyCastle based HPKE implementation that we can use we should also offer an implementation which is faster and so reduce the performance overhead.

Modifications:

Add native implementation based on BoringSSL.

Result:

Fixes #5
  • Loading branch information
normanmaurer committed Dec 21, 2023
1 parent a340fb4 commit 107f126
Show file tree
Hide file tree
Showing 23 changed files with 2,256 additions and 7 deletions.
75 changes: 75 additions & 0 deletions codec-ohttp-hpke-classes-boringssl/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.netty.incubator</groupId>
<artifactId>netty-incubator-codec-parent-ohttp</artifactId>
<version>0.0.3.Final-SNAPSHOT</version>
</parent>

<artifactId>netty-incubator-ohttp-hpke-classes-boringssl</artifactId>
<version>0.0.3.Final-SNAPSHOT</version>
<name>Netty/Incubator/Codec/OHTTP/HPKE/Classes/BoringSSL</name>

<packaging>jar</packaging>

<properties>
<javaModuleName>io.netty.incubator.codec.hpke.classes.boringssl</javaModuleName>
</properties>


<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>default-jar</id>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Automatic-Module-Name>${javaModuleName}</Automatic-Module-Name>
</manifestEntries>
<index>true</index>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
</dependency>
<dependency>
<groupId>io.netty.incubator</groupId>
<artifactId>netty-incubator-codec-ohttp-hpke</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
* 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.hpke.boringssl;

import io.netty.buffer.ByteBuf;
import io.netty.util.internal.ClassInitializerUtil;
import io.netty.util.internal.NativeLibraryLoader;
import io.netty.util.internal.PlatformDependent;

import java.nio.ByteBuffer;
import java.util.Arrays;

final class BoringSSL {

static {
// Preload all classes that will be used in the OnLoad(...) function of JNI to eliminate the possiblity of a
// class-loader deadlock. This is a workaround for https://github.com/netty/netty/issues/11209.

// This needs to match all the classes that are loaded via NETTY_JNI_UTIL_LOAD_CLASS or looked up via
// NETTY_JNI_UTIL_FIND_CLASS.
ClassInitializerUtil.tryLoadClasses(BoringSSL.class);

try {
// First, try calling a side-effect free JNI method to see if the library was already
// loaded by the application.
BoringSSLNativeStaticallyReferencedJniMethods.EVP_hpke_x25519_hkdf_sha256();
} catch (UnsatisfiedLinkError ignore) {
// The library was not previously loaded, load it now.
loadNativeLibrary();
}
}

private static void loadNativeLibrary() {
// This needs to be kept in sync with what is defined in netty_incubator_codec_ohttp_hpke_boringssl.c
String libName = "netty_incubator_codec_ohttp_hpke_boringssl";
ClassLoader cl = PlatformDependent.getClassLoader(BoringSSL.class);

if (!PlatformDependent.isAndroid()) {
libName += '_' + PlatformDependent.normalizedOs()
+ '_' + PlatformDependent.normalizedArch();
}

try {
NativeLibraryLoader.load(libName, cl);
} catch (UnsatisfiedLinkError e) {
throw e;
}
}

static final long EVP_hpke_x25519_hkdf_sha256 =
BoringSSLNativeStaticallyReferencedJniMethods.EVP_hpke_x25519_hkdf_sha256();
static final long EVP_hpke_hkdf_sha256 =
BoringSSLNativeStaticallyReferencedJniMethods.EVP_hpke_hkdf_sha256();
static final long EVP_hpke_aes_128_gcm =
BoringSSLNativeStaticallyReferencedJniMethods.EVP_hpke_aes_128_gcm();
static final long EVP_hpke_aes_256_gcm =
BoringSSLNativeStaticallyReferencedJniMethods.EVP_hpke_aes_256_gcm();
static final long EVP_hpke_chacha20_poly1305 =
BoringSSLNativeStaticallyReferencedJniMethods.EVP_hpke_chacha20_poly1305();

static final int EVP_AEAD_DEFAULT_TAG_LENGTH =
BoringSSLNativeStaticallyReferencedJniMethods.EVP_AEAD_DEFAULT_TAG_LENGTH();

static native long EVP_HPKE_CTX_new();
static native long EVP_HPKE_CTX_cleanup(long ctx);
static native long EVP_HPKE_CTX_free(long ctx);

// TODO: Do we also need the auth methods ?
static native byte[] EVP_HPKE_CTX_setup_sender(
long ctx, long kem, long kdf, long aead, byte[] peer_public_key, byte[] info);
static native byte[] EVP_HPKE_CTX_setup_sender_with_seed_for_testing(
long ctx, long kem, long kdf, long aead, byte[] peer_public_key, byte[] info, byte[] seed);
static native int EVP_HPKE_CTX_setup_recipient(
long ctx, long key, long kdf, long aead, byte[] enc, byte[] info);

static native int EVP_HPKE_CTX_open(
long ctx, long out, int max_out_len, long in, int in_len, long ad, int ad_len);
static native int EVP_HPKE_CTX_seal(
long ctx, long out, int max_out_len, long in, int in_len, long ad, int ad_len);
static native int EVP_HPKE_CTX_export(
long ctx, long out, int secret_len, long context, int context_len);

static native int EVP_HPKE_CTX_max_overhead(long ctx);

static native long EVP_HPKE_KEY_new();
static native void EVP_HPKE_KEY_free(long key);
static native void EVP_HPKE_KEY_cleanup(long key);

static native int EVP_HPKE_KEY_init(long key, long kem, byte[] priv_key);
static native byte[] EVP_HPKE_KEY_public_key(long key);
static native byte[] EVP_HPKE_KEY_private_key(long key);

static native int EVP_HPKE_KEM_public_key_len(long kem);

static native long memory_address(ByteBuffer buffer);

static native int EVP_AEAD_key_length(long aead);
static native int EVP_AEAD_nonce_length(long aead);
static native int EVP_AEAD_max_overhead(long aead);
static native int EVP_AEAD_max_tag_len(long aead);

static native long EVP_AEAD_CTX_new(long aead, byte[] key, int tag_len);
static native void EVP_AEAD_CTX_cleanup(long ctx);
static native void EVP_AEAD_CTX_free(long ctx);

static native int EVP_AEAD_CTX_seal(
long ctx, long out, int max_out_len, long nonce, int nonce_len, long in, int in_len, long ad, int ad_len);

static native int EVP_AEAD_CTX_open(
long ctx, long out, int max_out_len, long nonce, int nonce_len, long in, int in_len, long ad, int ad_len);

static long memory_address(ByteBuf buffer) {
if (buffer.hasMemoryAddress()) {
return buffer.memoryAddress();
}
return memory_address(buffer.internalNioBuffer(0, buffer.capacity()));
}

static long EVP_HPKE_CTX_new_or_throw() {
long ctx = BoringSSL.EVP_HPKE_CTX_new();
if (ctx == -1) {
throw new IllegalStateException("Unable to allocate EVP_HPKE_CTX");
}
return ctx;
}

static void EVP_HPKE_CTX_cleanup_and_free(long ctx) {
if (ctx != -1) {
BoringSSL.EVP_HPKE_CTX_cleanup(ctx);
BoringSSL.EVP_HPKE_CTX_free(ctx);
}
}

static long EVP_HPKE_KEY_new_or_throw() {
long key = BoringSSL.EVP_HPKE_KEY_new();
if (key == -1) {
throw new IllegalStateException("Unable to allocate EVP_HPKE_KEY");
}
return key;
}

static long EVP_HPKE_KEY_new_and_init_or_throw(long kem, byte[] privateKeyBytes) {
long key = EVP_HPKE_KEY_new_or_throw();
EVP_HPKE_KEY_init_or_throw(key, kem, privateKeyBytes);
return key;
}

static void EVP_HPKE_KEY_init_or_throw(long key, long kem, byte[] privateKeyBytes) {
if (BoringSSL.EVP_HPKE_KEY_init(key, kem, privateKeyBytes) != 1) {
throw new IllegalArgumentException(
"privateKeyBytes does not contain a valid private key: " + Arrays.toString(privateKeyBytes));
}
}

static void EVP_HPKE_KEY_cleanup_and_free(long key) {
if (key != -1) {
BoringSSL.EVP_HPKE_KEY_cleanup(key);
BoringSSL.EVP_HPKE_KEY_free(key);
}
}

static long EVP_AEAD_CTX_new_or_throw(long aead, byte[] key, int tagLen) {
long ctx = BoringSSL.EVP_AEAD_CTX_new(aead, key, tagLen);
if (ctx == -1) {
throw new IllegalStateException("Unable to allocate EVP_AEAD_CTX");
}
return ctx;
}

static void EVP_AEAD_CTX_cleanup_and_free(long ctx) {
if (ctx != -1) {
BoringSSL.EVP_AEAD_CTX_cleanup(ctx);
BoringSSL.EVP_AEAD_CTX_free(ctx);
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.hpke.boringssl;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.incubator.codec.hpke.AEADContext;
import io.netty.incubator.codec.hpke.CryptoException;

final class BoringSSLAEADContext extends BoringSSLCryptoContext implements AEADContext {

private final ByteBuf baseNonce;
private final long baseNonceAddress;
private final int baseNonceLen;
private final int AEAD_max_overhead;

private final BoringSSLCryptoOperation seal = new BoringSSLCryptoOperation() {
@Override
int maxOutLen(long ctx, int inReadable) {
return AEAD_max_overhead + inReadable;
}

@Override
int execute(long ctx, long ad, int adLen, long in, int inLen, long out, int outLen) {
return BoringSSL.EVP_AEAD_CTX_seal(ctx, out, outLen, baseNonceAddress, baseNonceLen, in, inLen, ad, adLen);
}
};

private final BoringSSLCryptoOperation open = new BoringSSLCryptoOperation() {
@Override
int maxOutLen(long ctx, int inReadable) {
return inReadable;
}

@Override
int execute(long ctx, long ad, int adLen, long in, int inLen, long out, int outLen) {
return BoringSSL.EVP_AEAD_CTX_open(ctx, out, outLen, baseNonceAddress, baseNonceLen, in, inLen, ad, adLen);
}
};

BoringSSLAEADContext(long ctx, int AEAD_max_overhead, byte[] baseNonce) {
super(ctx);
this.baseNonce = Unpooled.directBuffer(baseNonce.length).writeBytes(baseNonce);
this.baseNonceAddress = BoringSSL.memory_address(this.baseNonce);
this.baseNonceLen = this.baseNonce.readableBytes();
this.AEAD_max_overhead = AEAD_max_overhead;
}

@Override
protected void destroyCtx(long ctx) {
baseNonce.release();
BoringSSL.EVP_AEAD_CTX_cleanup_and_free(ctx);
}

@Override
public void open(ByteBuf aad, ByteBuf ct, ByteBuf out) throws CryptoException {
if (!open.execute(checkClosedAndReturnCtx(), aad, ct, out)) {
throw new CryptoException("open(...) failed");
}
}

@Override
public void seal(ByteBuf aad, ByteBuf pt, ByteBuf out) throws CryptoException {
if (!seal.execute(checkClosedAndReturnCtx(), aad, pt, out)) {
throw new CryptoException("seal(...) failed");
}
}
}
Loading

0 comments on commit 107f126

Please sign in to comment.