Skip to content

Commit

Permalink
More TLS features
Browse files Browse the repository at this point in the history
  • Loading branch information
pull-vert committed Oct 28, 2024
1 parent 4a955e3 commit 333029d
Show file tree
Hide file tree
Showing 20 changed files with 1,348 additions and 12 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ jobs:
Gradle-Build:
strategy:
matrix:
os: [ 'macos-latest', 'ubuntu-latest' ] # Windows tests are not working for now : 'windows-latest'
distribution: [ 'oracle', 'temurin', 'zulu' ]
java: [ '21', '22' ] # replace '22' with '23' when JMH & Kotlin support it
runs-on: ${{ matrix.os }}
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v4

Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

# Jayo

A fast synchronous I/O library for the JVM that relies on Java 21+ virtual threads, see
A fast synchronous I/O and TLS library for the JVM that shines when used in Java 21+ virtual threads, see
[Project Loom](https://openjdk.org/projects/loom/).

Synchronous APIs are easier to work with than asynchronous and non-blocking APIs; the code is easier to write, easier to
read, and easier to debug (with stack traces that make sense!). \
Virtual threads allow to run as many threads as we need without requiring thread pools or event-loop. With synchronous
IO that runs inside virtual threads, Jayo offers great performances and scalability.
I/O that runs inside virtual threads, Jayo offers great performances and scalability.

Jayo is available on Maven Central.

Expand Down Expand Up @@ -55,7 +55,7 @@ try (var serverSocket = new ServerSocket(freePortNumber)) {
}
```

Jayo is written in Java without any external dependencies, to stay as light as possible.
Jayo is written in Java without any external dependencies, to be as light as possible.

We also love Kotlin ! Jayo is fully usable and optimized from Kotlin code thanks to `@NonNull` annotations, Kotlin
friendly method naming (`get*` and `set*`) and a lot of Kotlin extension functions are natively included in this project.
Expand All @@ -75,12 +75,15 @@ Java 21 is required to use Jayo.

[Jayo](./core) offers solid I/O foundations by providing the tools we need for binary data manipulation
* `Buffer` is a mutable sequence of bytes one can easily write to and read from.
* `ByteString` is an immutable and serializable sequence of bytes that stores a String related binary content
* `ByteString` is an immutable and serializable sequence of bytes that stores a String related binary content as-is.
* `Utf8` is a `ByteString` that contains UTF-8 encoded bytes only.
* `RawReader` and `RawWriter` (and their buffered versions `Reader` and `Writer`) offer great improvements over
`InputStream` and `OutputStream`.

It also provides some useful tools for TLS
* `TlsEndpoint` is an easy-to-use streaming API based on Jayo's reader and writer, that allows to secure JVM
applications with minimal added complexity.
* `JssePlatform` eases access to platform-specific Java Secure Socket Extension (JSSE) features.

You can also read [concepts](CONCEPT.md) and [draft ideas](DRAFT_IDEAS.md).

Expand Down
2 changes: 1 addition & 1 deletion build-logic/src/main/kotlin/jayo-commons.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ val javaVersion = catalogVersion("java").toInt()
val isCI = providers.gradleProperty("isCI")

val koverage = mapOf(
"jayo" to if (isCI.isPresent) 83 else 85,
"jayo" to if (isCI.isPresent) 81 else 83,
"jayo-3p-kotlinx-serialization" to 55,
)

Expand Down
2 changes: 2 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,7 @@ it behind an easy-to-use streaming API based on Jayo's reader and writer, that a
minimal added complexity.
It is based on the awesome *TLS Channel library* [1].

`JssePlatform` eases access to platform-specific Java Secure Socket Extension (JSSE) features.

_[1] : [TLS Channel](https://square.github.io/okio/) is a library that implements a `ByteChannel` interface over a
[TLS](https://tools.ietf.org/html/rfc5246) (Transport Layer Security) connection._
24 changes: 23 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import kotlin.jvm.optionals.getOrNull
import org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION as KOTLIN_VERSION

println("Using Gradle version: ${gradle.gradleVersion}")
Expand All @@ -7,12 +8,33 @@ println("Using Java compiler version: ${JavaVersion.current()}")
plugins {
id("jayo-commons")
id("jayo.build.optional-dependencies")
`java-test-fixtures`
}

val versionCatalog: VersionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")

fun catalogVersion(lib: String) =
versionCatalog.findVersion(lib).getOrNull()?.requiredVersion
?: throw GradleException("Version '$lib' is not specified in the toml version catalog")

dependencies {
optional("org.jetbrains.kotlin:kotlin-stdlib")

testImplementation("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
// These compileOnly dependencies must also be listed in the OSGi configuration above (todo).
compileOnly("org.bouncycastle:bctls-jdk18on:${catalogVersion("bouncycastle")}")
compileOnly("org.conscrypt:conscrypt-openjdk-uber:${catalogVersion("conscrypt")}")

testFixturesImplementation(platform("org.junit:junit-bom:${catalogVersion("junit")}"))

testFixturesApi("org.junit.jupiter:junit-jupiter-api")
testFixturesApi("org.junit.jupiter:junit-jupiter-params")
testFixturesApi("org.hamcrest:hamcrest:${catalogVersion("hamcrest")}")
testFixturesApi("org.bouncycastle:bcprov-jdk18on:${catalogVersion("bouncycastle")}")
testFixturesImplementation("org.bouncycastle:bcpkix-jdk18on:${catalogVersion("bouncycastle")}")
testFixturesImplementation("org.bouncycastle:bctls-jdk18on:${catalogVersion("bouncycastle")}")
testFixturesApi("org.conscrypt:conscrypt-openjdk-uber:${catalogVersion("conscrypt")}")
// classifier for AmazonCorrettoCryptoProvider = linux-x86_64
testFixturesApi("software.amazon.cryptools:AmazonCorrettoCryptoProvider:${catalogVersion("amazonCorretto")}:linux-x86_64")
}

tasks {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2024-present, pull-vert and Jayo contributors.
* Use of this source code is governed by the Apache 2.0 license.
*
* Forked from OkHttp (https://github.com/square/okhttp), original copyright is below
*
* Copyright (C) 2013 Square, Inc.
*
* Licensed 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 jayo.internal.platform;

import jayo.tls.AlpnProtocol;
import org.bouncycastle.jsse.BCSSLEngine;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.security.*;
import java.util.Arrays;
import java.util.List;

import static jayo.tls.JssePlatform.alpnProtocolNames;

/**
* Platform using BouncyCastle if installed as the first Security Provider.
* <p>
* Requires org.bouncycastle:bctls-jdk18on on the classpath.
*/
public final class BouncyCastleJssePlatform extends JdkJssePlatform {
public static boolean isSupported() {
try {
// Trigger an early exception over a fatal error, prefer a RuntimeException over Error.
Class.forName("org.bouncycastle.jsse.provider.BouncyCastleJsseProvider", false,
BouncyCastleJssePlatform.class.getClassLoader());
return true;
} catch (ClassNotFoundException ignored) {
return false;
}
}

public static @Nullable BouncyCastleJssePlatform buildIfSupported(boolean isFips) {
return isSupported() ? new BouncyCastleJssePlatform(isFips) : null;
}

private final @NonNull Provider provider;

private BouncyCastleJssePlatform(boolean isFips) {
provider = isFips ? new BouncyCastleJsseProvider("fips:BCFIPS") : new BouncyCastleJsseProvider();
}

@Override
public @NonNull SSLContext newSSLContext() throws NoSuchAlgorithmException {
return SSLContext.getInstance("TLS", provider);
}

@Override
public @NonNull X509TrustManager platformTrustManager() throws NoSuchAlgorithmException, KeyStoreException,
NoSuchProviderException {
final var factory = TrustManagerFactory.getInstance("PKIX", BouncyCastleJsseProvider.PROVIDER_NAME);
factory.init((KeyStore) null);
final var trustManagers = factory.getTrustManagers();
if (trustManagers.length != 1
|| !(trustManagers[0] instanceof X509TrustManager x509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers: " + Arrays.toString(trustManagers));
}
return x509TrustManager;
}

@Override
public void configureTlsExtensions(
final @NonNull SSLEngine sslEngine,
final @Nullable String hostname,
final @NonNull List<AlpnProtocol> protocols) {
assert sslEngine != null;
assert protocols != null;

if (sslEngine instanceof BCSSLEngine bouncyCastleSSLEngine) {
final var sslParameters = bouncyCastleSSLEngine.getParameters();

// Enable ALPN.
final var names = alpnProtocolNames(protocols);
sslParameters.setApplicationProtocols(names.toArray(String[]::new));
bouncyCastleSSLEngine.setParameters(sslParameters);
} else {
super.configureTlsExtensions(sslEngine, hostname, protocols);
}
}

@Override
public @Nullable String getSelectedProtocol(final @NonNull SSLEngine sslEngine) {
assert sslEngine != null;

if (sslEngine instanceof BCSSLEngine bouncyCastleSSLEngine) {
final var protocol = bouncyCastleSSLEngine.getApplicationProtocol();
// Handles both un-configured and none selected.
return switch (protocol) {
case null -> null;
case "" -> null;
default -> protocol;
};
}
// else
return super.getSelectedProtocol(sslEngine);
}
}
152 changes: 152 additions & 0 deletions core/src/main/java/jayo/internal/platform/ConscryptJssePlatform.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright (c) 2024-present, pull-vert and Jayo contributors.
* Use of this source code is governed by the Apache 2.0 license.
*
* Forked from OkHttp (https://github.com/square/okhttp), original copyright is below
*
* Copyright (C) 2013 Square, Inc.
*
* Licensed 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 jayo.internal.platform;

import jayo.tls.AlpnProtocol;
import org.conscrypt.Conscrypt;
import org.conscrypt.ConscryptHostnameVerifier;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.cert.X509Certificate;
import java.util.List;

import static jayo.tls.JssePlatform.alpnProtocolNames;

/**
* Platform using Conscrypt (<a href="https://conscrypt.org">conscrypt</a>) if installed as the first Security Provider.
* <p>
* Requires org.conscrypt:conscrypt-openjdk-uber >= 2.1.0 on the classpath.
*/
public final class ConscryptJssePlatform extends JdkJssePlatform {
public static boolean isSupported() {
try {
// Trigger an early exception over a fatal error, prefer a RuntimeException over Error.
Class.forName("org.conscrypt.Conscrypt$Version", false,
ConscryptJssePlatform.class.getClassLoader());

// Bump this version if we ever have a binary incompatibility
return Conscrypt.isAvailable() && atLeastVersion(2, 1, 0);
} catch (ClassNotFoundException | NoClassDefFoundError ignored) {
return false;
}
}

public static @Nullable ConscryptJssePlatform buildIfSupported() {
return isSupported() ? new ConscryptJssePlatform() : null;
}

static boolean atLeastVersion(int major, int minor, int patch) {
final var conscryptVersion = Conscrypt.version();

if (conscryptVersion.major() != major) {
return conscryptVersion.major() > major;
}

if (conscryptVersion.minor() != minor) {
return conscryptVersion.minor() > minor;
}

return conscryptVersion.patch() >= patch;
}

private final @NonNull Provider provider;

private ConscryptJssePlatform() {
provider = Conscrypt.newProvider();
}

// See release notes https://groups.google.com/forum/#!forum/conscrypt for version differences
@Override
public @NonNull SSLContext newSSLContext() throws NoSuchAlgorithmException {
// supports TLSv1.3 by default (version api is >= 1.4.0)
return SSLContext.getInstance("TLS", provider);
}

@Override
public @NonNull X509TrustManager platformTrustManager() throws NoSuchAlgorithmException, KeyStoreException,
NoSuchProviderException {
final var x509TrustManager = super.platformTrustManager();
// Disabled because Jayo will run anyway
Conscrypt.setHostnameVerifier(x509TrustManager, DisabledHostnameVerifier.getInstance());
return x509TrustManager;
}

@Override
public void configureTlsExtensions(
final @NonNull SSLEngine sslEngine,
final @Nullable String hostname,
final @NonNull List<AlpnProtocol> protocols) {
assert sslEngine != null;
assert protocols != null;

if (Conscrypt.isConscrypt(sslEngine)) {
// Enable session tickets.
Conscrypt.setUseSessionTickets(sslEngine, true);

// Enable ALPN.
final var names = alpnProtocolNames(protocols);
Conscrypt.setApplicationProtocols(sslEngine, names.toArray(String[]::new));
} else {
super.configureTlsExtensions(sslEngine, hostname, protocols);
}
}

@Override
public @Nullable String getSelectedProtocol(final @NonNull SSLEngine sslEngine) {
assert sslEngine != null;

if (Conscrypt.isConscrypt(sslEngine)) {
return Conscrypt.getApplicationProtocol(sslEngine);
}
// else
return super.getSelectedProtocol(sslEngine);
}

private static final class DisabledHostnameVerifier implements ConscryptHostnameVerifier {
private static ConscryptHostnameVerifier INSTANCE;

private DisabledHostnameVerifier() {
}

private static ConscryptHostnameVerifier getInstance() {
if (INSTANCE == null) {
INSTANCE = new DisabledHostnameVerifier();
}

return INSTANCE;
}

@Override
public boolean verify(X509Certificate[] x509Certificates, String s, SSLSession sslSession) {
return true;
}
}
}
Loading

0 comments on commit 333029d

Please sign in to comment.