org.reaktivity.nukleus.tls.internal.config.TlsBinding Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nukleus-tls Show documentation
Show all versions of nukleus-tls Show documentation
TLS Nukleus Implementation
/**
* Copyright 2016-2021 The Reaktivity Project
*
* The Reaktivity 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:
*
* http://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 org.reaktivity.nukleus.tls.internal.config;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static javax.net.ssl.StandardConstants.SNI_HOST_NAME;
import static org.reaktivity.nukleus.tls.internal.identity.TlsX509ExtendedKeyManager.DISTINGUISHED_NAME_KEY;
import static org.reaktivity.nukleus.tls.internal.types.ProxyInfoType.ALPN;
import static org.reaktivity.nukleus.tls.internal.types.ProxyInfoType.AUTHORITY;
import static org.reaktivity.nukleus.tls.internal.types.ProxyInfoType.SECURE;
import static org.reaktivity.nukleus.tls.internal.types.ProxySecureInfoType.NAME;
import java.security.KeyStore;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.stream.Collectors;
import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.security.auth.x500.X500Principal;
import org.agrona.LangUtil;
import org.reaktivity.nukleus.tls.internal.identity.TlsX509ExtendedKeyManager;
import org.reaktivity.nukleus.tls.internal.types.Array32FW;
import org.reaktivity.nukleus.tls.internal.types.ProxyInfoFW;
import org.reaktivity.nukleus.tls.internal.types.stream.ProxyBeginExFW;
import org.reaktivity.reaktor.config.Binding;
import org.reaktivity.reaktor.config.Role;
import org.reaktivity.reaktor.nukleus.vault.BindingVault;
public final class TlsBinding
{
private static final String TYPE_DEFAULT = "PKCS12";
public final long id;
public final long vaultId;
public final String entry;
public final TlsOptions options;
public final Role kind;
public final List routes;
public final TlsRoute exit;
private SSLContext context;
public TlsBinding(
Binding binding)
{
this.id = binding.id;
this.vaultId = binding.vault != null ? binding.vault.id : 0L;
this.entry = binding.entry;
this.kind = binding.kind;
this.options = TlsOptions.class.cast(binding.options);
this.routes = binding.routes.stream().map(TlsRoute::new).collect(toList());
this.exit = binding.exit != null ? new TlsRoute(binding.exit) : null;
}
public void init(
BindingVault vault,
boolean ignoreEmptyVaultRefs,
String keyManagerAlgorithm,
SecureRandom random)
{
char[] keysPass = "generated".toCharArray();
KeyStore keys = newKeys(vault, ignoreEmptyVaultRefs, keysPass, options.keys, options.signers);
KeyStore trust = newTrust(vault, ignoreEmptyVaultRefs, options.trust, options.trustcacerts && kind == Role.CLIENT);
try
{
KeyManager[] keyManagers = null;
if (keys != null)
{
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgorithm);
keyManagerFactory.init(keys, keysPass);
keyManagers = keyManagerFactory.getKeyManagers();
if (keyManagers != null)
{
for (int i = 0; i < keyManagers.length; i++)
{
if (keyManagers[i] instanceof X509ExtendedKeyManager)
{
X509ExtendedKeyManager keyManager = (X509ExtendedKeyManager) keyManagers[i];
keyManagers[i] = new TlsX509ExtendedKeyManager(keyManager);
}
}
}
}
TrustManager[] trustManagers = null;
if (trust != null)
{
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trust);
trustManagers = trustManagerFactory.getTrustManagers();
}
String version = options.version != null ? options.version : "TLS";
SSLContext context = SSLContext.getInstance(version);
context.init(keyManagers, trustManagers, random);
this.context = context;
}
catch (Exception ex)
{
LangUtil.rethrowUnchecked(ex);
}
}
public TlsRoute resolve(
long authorization,
ProxyBeginExFW beginEx)
{
Array32FW infos = beginEx != null ? beginEx.infos() : null;
ProxyInfoFW authorityInfo = infos != null ? infos.matchFirst(a -> a.kind() == AUTHORITY) : null;
ProxyInfoFW alpnInfo = infos != null ? infos.matchFirst(a -> a.kind() == ALPN) : null;
String authority = authorityInfo != null ? authorityInfo.authority().asString() : null;
String alpn = alpnInfo != null ? alpnInfo.alpn().asString() : null;
return resolve(authorization, authority, alpn);
}
public TlsRoute resolve(
long authorization,
String hostname,
String alpn)
{
TlsRoute resolved = null;
for (TlsRoute route : routes)
{
if (route.when.stream().anyMatch(m -> m.matches(hostname, alpn)))
{
resolved = route;
break;
}
}
if (resolved == null)
{
resolved = exit;
}
return resolved;
}
public SSLEngine newClientEngine(
ProxyBeginExFW beginEx)
{
SSLEngine engine = null;
if (context != null)
{
engine = context.createSSLEngine();
engine.setUseClientMode(true);
List sni = options.sni;
if (sni == null && beginEx != null)
{
ProxyInfoFW info = beginEx.infos().matchFirst(a -> a.kind() == AUTHORITY);
// TODO: support multiple authority info
if (info != null)
{
sni = singletonList(info.authority().asString());
}
}
List alpn = options.alpn;
if (alpn == null && beginEx != null)
{
ProxyInfoFW info = beginEx.infos().matchFirst(a -> a.kind() == ALPN);
// TODO: support multiple alpn info
if (info != null)
{
alpn = singletonList(info.alpn().asString());
}
}
final SSLParameters parameters = engine.getSSLParameters();
parameters.setEndpointIdentificationAlgorithm("HTTPS");
if (sni != null)
{
List serverNames = sni.stream()
.map(SNIHostName::new)
.collect(toList());
parameters.setServerNames(serverNames);
}
if (alpn != null)
{
List alpnNonNull = alpn.stream()
.filter(s -> s != null)
.collect(toList());
parameters.setApplicationProtocols(alpnNonNull.toArray(new String[alpnNonNull.size()]));
}
engine.setSSLParameters(parameters);
if (beginEx != null)
{
ProxyInfoFW info = beginEx.infos().matchFirst(a -> a.kind() == SECURE && a.secure().kind() == NAME);
if (info != null)
{
String commonName = info.secure().name().asString();
if (commonName != null)
{
String distinguishedName = String.format("CN=%s", commonName);
SSLSession session = engine.getSession();
session.putValue(DISTINGUISHED_NAME_KEY, distinguishedName);
}
}
}
}
return engine;
}
public SSLEngine newServerEngine()
{
SSLEngine engine = null;
if (context != null)
{
engine = context.createSSLEngine();
engine.setUseClientMode(false);
TlsMutual mutual = options != null ? options.mutual : null;
if (mutual != null)
{
switch (mutual)
{
case WANTED:
engine.setWantClientAuth(true);
break;
case NEEDED:
engine.setNeedClientAuth(true);
break;
}
}
else
{
engine.setWantClientAuth(false);
}
engine.setHandshakeApplicationProtocolSelector(this::selectAlpn);
}
return engine;
}
private String selectAlpn(
SSLEngine engine,
List protocols)
{
List serverNames = null;
SSLSession session = engine.getHandshakeSession();
if (session instanceof ExtendedSSLSession)
{
ExtendedSSLSession sessionEx = (ExtendedSSLSession) session;
serverNames = sessionEx.getRequestedServerNames();
}
List sni = options != null ? options.sni : null;
List alpn = options != null ? options.alpn : null;
String selected = null;
for (String protocol : protocols)
{
if (alpn != null && alpn.contains(protocol))
{
selected = protocol;
break;
}
}
if (serverNames != null)
{
for (SNIServerName serverName : serverNames)
{
if (serverName.getType() == SNI_HOST_NAME)
{
SNIHostName hostName = (SNIHostName) serverName;
String authority = hostName.getAsciiName();
if (sni != null && !sni.contains(authority))
{
continue;
}
for (TlsRoute route : routes)
{
for (String protocol : protocols)
{
if (alpn != null && !alpn.contains(protocol))
{
continue;
}
if (route.when.stream().anyMatch(m -> m.matches(authority, protocol)))
{
selected = protocol;
break;
}
}
}
}
}
}
else
{
for (TlsRoute route : routes)
{
for (String protocol : protocols)
{
if (alpn != null && !alpn.contains(protocol))
{
continue;
}
if (route.when.stream().anyMatch(m -> m.matches(null, protocol)))
{
selected = protocol;
break;
}
}
}
}
if (selected == null && exit != null)
{
selected = "";
}
return selected;
}
private KeyStore newKeys(
BindingVault vault,
boolean ignoreEmptyNames,
char[] password,
List keyNames,
List signerNames)
{
KeyStore store = null;
try
{
if (ignoreEmptyNames)
{
keyNames = ignoreEmptyNames(keyNames);
signerNames = ignoreEmptyNames(signerNames);
}
if (keyNames != null || signerNames != null)
{
store = KeyStore.getInstance(TYPE_DEFAULT);
store.load(null, password);
}
if (keyNames != null)
{
assert store != null;
for (String keyName : keyNames)
{
KeyStore.PrivateKeyEntry entry = vault.key(keyName);
KeyStore.ProtectionParameter protection = new KeyStore.PasswordProtection(password);
store.setEntry(keyName, entry, protection);
}
}
if (signerNames != null)
{
assert store != null;
for (String signerName : signerNames)
{
KeyStore.PrivateKeyEntry[] entries = vault.keys(signerName);
if (entries != null)
{
for (KeyStore.PrivateKeyEntry entry : entries)
{
KeyStore.ProtectionParameter protection = new KeyStore.PasswordProtection(password);
Certificate certificate = entry.getCertificate();
if (certificate instanceof X509Certificate)
{
X509Certificate x509 = (X509Certificate) certificate;
X500Principal x500 = x509.getSubjectX500Principal();
String alias = String.format("%s %d", x500.getName(), x509.getSerialNumber());
store.setEntry(alias, entry, protection);
}
}
}
}
}
}
catch (Exception ex)
{
LangUtil.rethrowUnchecked(ex);
}
return store;
}
private KeyStore newTrust(
BindingVault vault,
boolean ignoreEmptyNames,
List trustNames,
boolean trustcacerts)
{
KeyStore store = null;
try
{
if (ignoreEmptyNames)
{
trustNames = ignoreEmptyNames(trustNames);
}
if (trustNames != null)
{
store = KeyStore.getInstance(TYPE_DEFAULT);
store.load(null, null);
for (String trustName : trustNames)
{
KeyStore.TrustedCertificateEntry entry = vault.certificate(trustName);
store.setEntry(trustName, entry, null);
}
}
if (trustcacerts)
{
TrustedCertificateEntry[] cacerts = TlsTrust.cacerts();
for (TrustedCertificateEntry cacert : cacerts)
{
X509Certificate trusted = (X509Certificate) cacert.getTrustedCertificate();
X500Principal subject = trusted.getSubjectX500Principal();
String name = subject.getName();
store.setEntry(name, cacert, null);
}
}
}
catch (Exception ex)
{
LangUtil.rethrowUnchecked(ex);
}
return store;
}
private List ignoreEmptyNames(
List names)
{
if (names != null && !names.isEmpty())
{
names = names.stream()
.filter(n -> !n.isEmpty())
.collect(Collectors.toList());
if (names.isEmpty())
{
names = null;
}
}
return names;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy