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 27ff45e commit 263831d
Show file tree
Hide file tree
Showing 26 changed files with 2,304 additions and 12 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/ci-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ jobs:
key: verify-cache-m2-repository-${{ hashFiles('**/pom.xml') }}
restore-keys: |
verify-cache-m2-repository-
- name: Install tools / libraries
run: sudo apt-get update && sudo apt-get -y install autoconf automake libtool make tar cmake perl ninja-build git

- name: Verify with Maven
run: ./mvnw -B --file pom.xml verify -DskipTests=true -DskipH3Spec=true
run: ./mvnw -B --file pom.xml verify -DskipTests=true

build:
runs-on: ubuntu-latest
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
language: [ 'cpp', 'java' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
Expand Down Expand Up @@ -63,8 +63,11 @@ jobs:
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main

- name: Install tools / libraries
run: sudo apt-get update && sudo apt-get -y install autoconf automake libtool make tar cmake perl ninja-build git

- name: Build project
run: ./mvnw clean package -DskipTests=true -DskipH3Spec=true
run: ./mvnw clean package -DskipTests=true

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@
import io.netty.buffer.ByteBuf;
import io.netty.incubator.codec.hpke.AEADContext;
import io.netty.incubator.codec.hpke.CryptoException;
import io.netty.incubator.codec.hpke.CryptoContext;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.hpke.AEAD;

final class BouncyCastleAEADCryptoContext implements AEADContext {

private final BouncyCastleCryptoOperation open;
private final BouncyCastleCryptoOperation seal;

private boolean closed;

BouncyCastleAEADCryptoContext(AEAD aead) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@
*/
package io.netty.incubator.codec.hpke.bouncycastle;

import io.netty.buffer.ByteBuf;
import io.netty.incubator.codec.hpke.CryptoException;
import io.netty.incubator.codec.hpke.HPKEContext;
import org.bouncycastle.crypto.InvalidCipherTextException;

import java.nio.ByteBuffer;

abstract class BouncyCastleHPKEContext implements HPKEContext {
protected final org.bouncycastle.crypto.hpke.HPKEContext context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public final class BouncyCastleOHttpCryptoProvider implements OHttpCryptoProvider {

private static final List<AEAD> SUPPORTED_AEAD_LIST = Collections.unmodifiableList(Arrays.asList(AEAD.values()));
private static final List<Mode> SUPPORTED_MODE_LIST = Collections.unmodifiableList(Arrays.asList(Mode.values()));
private static final List<KEM> SUPPORTED_KEM_LIST = Collections.unmodifiableList(Arrays.asList(KEM.values()));
private static final List<KDF> SUPPORTED_KDF_LIST = Collections.unmodifiableList(Arrays.asList(KDF.values()));

public static final BouncyCastleOHttpCryptoProvider INSTANCE = new BouncyCastleOHttpCryptoProvider();

private BouncyCastleOHttpCryptoProvider() { }
Expand Down Expand Up @@ -180,4 +188,24 @@ private static ECDomainParameters ecDomainParameters(KEM kem) {
throw new IllegalArgumentException("invalid kem: " + kem);
}
}

@Override
public List<AEAD> supportedAEAD() {
return SUPPORTED_AEAD_LIST;
}

@Override
public List<KEM> supportedKEM() {
return SUPPORTED_KEM_LIST;
}

@Override
public List<KDF> supportedKDF() {
return SUPPORTED_KDF_LIST;
}

@Override
public List<Mode> supportedMode() {
return SUPPORTED_MODE_LIST;
}
}
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);
}
}
}


Loading

0 comments on commit 263831d

Please sign in to comment.