net.luminis.tls.handshake.TlsEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of agent15 Show documentation
Show all versions of agent15 Show documentation
A (partial) TLS 1.3 implementation in Java, suitable and intended for use in a QUIC implementation.
/*
* Copyright © 2020, 2021, 2022, 2023 Peter Doornbosch
*
* This file is part of Agent15, an implementation of TLS 1.3 in Java.
*
* Agent15 is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Agent15 is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package net.luminis.tls.handshake;
import net.luminis.tls.TlsConstants;
import net.luminis.tls.TlsState;
import net.luminis.tls.TrafficSecrets;
import net.luminis.tls.alert.ErrorAlert;
import net.luminis.tls.alert.HandshakeFailureAlert;
import net.luminis.tls.alert.InternalErrorAlert;
import net.luminis.tls.env.AlgorithmMapping;
import net.luminis.tls.env.PlatformMapping;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.NamedParameterSpec;
import java.security.spec.PSSParameterSpec;
import static net.luminis.tls.TlsConstants.CipherSuite.TLS_AES_256_GCM_SHA384;
import static net.luminis.tls.TlsConstants.NamedGroup.*;
import static net.luminis.tls.TlsConstants.SignatureScheme.*;
public abstract class TlsEngine implements MessageProcessor, TrafficSecrets {
protected PublicKey publicKey;
protected PrivateKey privateKey;
protected TlsState state;
protected AlgorithmMapping algorithmMapping;
public TlsEngine() {
algorithmMapping = PlatformMapping.algorithmMapping();
}
public abstract TlsConstants.CipherSuite getSelectedCipher();
public static int hashLength(TlsConstants.CipherSuite cipher) {
switch (cipher) {
case TLS_AES_128_GCM_SHA256:
return 256 / 8;
case TLS_AES_256_GCM_SHA384:
return 384 / 8;
case TLS_CHACHA20_POLY1305_SHA256:
return 256 / 8;
case TLS_AES_128_CCM_SHA256:
return 256 / 8;
case TLS_AES_128_CCM_8_SHA256:
return 256 / 8;
default:
// Impossible, as all enum values are covered
throw new RuntimeException();
}
}
public static int keyLength(TlsConstants.CipherSuite cipher) {
switch (cipher) {
case TLS_AES_128_GCM_SHA256:
return 128 / 8;
case TLS_AES_256_GCM_SHA384:
return 256 / 8;
case TLS_CHACHA20_POLY1305_SHA256:
return 256 / 8;
case TLS_AES_128_CCM_SHA256:
return 128 / 8;
case TLS_AES_128_CCM_8_SHA256:
return 128 / 8;
default:
// Impossible, as all enum values are covered
throw new RuntimeException();
}
}
protected void generateKeys(TlsConstants.NamedGroup namedGroup) {
try {
KeyPairGenerator keyPairGenerator;
if (namedGroup == secp256r1 || namedGroup == secp384r1 || namedGroup == secp521r1) {
keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(new ECGenParameterSpec(namedGroup.toString()));
}
else if (namedGroup == x25519 || namedGroup == x448) {
keyPairGenerator = KeyPairGenerator.getInstance("XDH");
NamedParameterSpec paramSpec = new NamedParameterSpec(namedGroup.toString().toUpperCase()); // x25519 => X25519
keyPairGenerator.initialize(paramSpec);
}
else {
throw new RuntimeException("unsupported group " + namedGroup);
}
KeyPair keyPair = keyPairGenerator.genKeyPair();
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
} catch (NoSuchAlgorithmException e) {
// Invalid runtime
throw new RuntimeException("missing key pair generator algorithm EC");
} catch (InvalidAlgorithmParameterException e) {
// Impossible, would be programming error
throw new RuntimeException();
}
}
/**
* Compute the signature used in certificate verify message to proof possession of private key.
* @param content the content to be signed (transcript hash)
* @param certificatePrivateKey the private key associated with the certificate
* @param signatureScheme
* @param client whether the signature must be computed
* @return
*/
protected byte[] computeSignature(byte[] content, PrivateKey certificatePrivateKey, TlsConstants.SignatureScheme signatureScheme, boolean client) throws ErrorAlert {
// https://tools.ietf.org/html/rfc8446#section-4.4.3
// The digital signature is then computed over the concatenation of:
// - A string that consists of octet 32 (0x20) repeated 64 times
// - The context string
// - A single 0 byte which serves as the separator
// - The content to be signed"
ByteArrayOutputStream signatureInput = new ByteArrayOutputStream();
try {
signatureInput.write(new String(new byte[] { 0x20 }).repeat(64).getBytes(StandardCharsets.US_ASCII));
String contextString = "TLS 1.3, " + (client? "client": "server") + " CertificateVerify";
signatureInput.write(contextString.getBytes(StandardCharsets.US_ASCII));
signatureInput.write(0x00);
signatureInput.write(content);
} catch (IOException e) {
// Impossible
throw new RuntimeException();
}
try {
Signature signatureAlgorithm = getSignatureAlgorithm(signatureScheme);
signatureAlgorithm.initSign(certificatePrivateKey);
signatureAlgorithm.update(signatureInput.toByteArray());
byte[] digitalSignature = signatureAlgorithm.sign();
return digitalSignature;
}
catch (SignatureException e) {
// sign() throws SignatureException: if this signature object is not initialized properly or if this
// signature algorithm is unable to process the input data provided.
throw new RuntimeException();
} catch (InvalidKeyException e) {
throw new InternalErrorAlert("invalid private key");
}
}
// https://tools.ietf.org/html/rfc8446#section-4.4.4
protected byte[] computeFinishedVerifyData(byte[] transcriptHash, byte[] baseKey) {
short hashLength = state.getHashLength();
byte[] finishedKey = state.hkdfExpandLabel(baseKey, "finished", "", hashLength);
String macAlgorithmName = "HmacSHA" + (hashLength * 8);
SecretKeySpec hmacKey = new SecretKeySpec(finishedKey, macAlgorithmName);
try {
Mac hmacAlgorithm = Mac.getInstance(macAlgorithmName);
hmacAlgorithm.init(hmacKey);
hmacAlgorithm.update(transcriptHash);
byte[] hmac = hmacAlgorithm.doFinal();
return hmac;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Missing " + macAlgorithmName + " support");
} catch (InvalidKeyException e) {
throw new RuntimeException();
}
}
protected Signature getSignatureAlgorithm(TlsConstants.SignatureScheme signatureScheme) throws HandshakeFailureAlert {
Signature signatureAlgorithm = null;
// https://tools.ietf.org/html/rfc8446#section-9.1
// "A TLS-compliant application MUST support digital signatures with rsa_pkcs1_sha256 (for certificates),
// rsa_pss_rsae_sha256 (for CertificateVerify and certificates), and ecdsa_secp256r1_sha256."
if (signatureScheme.equals(rsa_pss_rsae_sha256)) {
try {
signatureAlgorithm = Signature.getInstance(algorithmMapping.get("RSASSA-PSS"));
signatureAlgorithm.setParameter(new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 32, 1));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Missing RSASSA-PSS support");
} catch (InvalidAlgorithmParameterException e) {
// Fairly impossible (because the parameters is hard coded)
throw new RuntimeException(e);
}
}
else if (signatureScheme.equals(rsa_pss_rsae_sha384)) {
try {
signatureAlgorithm = Signature.getInstance(algorithmMapping.get("RSASSA-PSS"));
signatureAlgorithm.setParameter(new PSSParameterSpec("SHA-384", "MGF1", new MGF1ParameterSpec("SHA-384"), 48, 1));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Missing RSASSA-PSS support");
} catch (InvalidAlgorithmParameterException e) {
// Fairly impossible (because the parameters is hard coded)
throw new RuntimeException(e);
}
}
else if (signatureScheme.equals(rsa_pss_rsae_sha512)) {
try {
signatureAlgorithm = Signature.getInstance(algorithmMapping.get("RSASSA-PSS"));
signatureAlgorithm.setParameter(new PSSParameterSpec("SHA-512", "MGF1", new MGF1ParameterSpec("SHA-512"), 64, 1));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Missing RSASSA-PSS support");
} catch (InvalidAlgorithmParameterException e) {
// Fairly impossible (because the parameters is hard coded)
throw new RuntimeException(e);
}
}
else if (signatureScheme.equals(ecdsa_secp256r1_sha256)) {
try {
signatureAlgorithm = Signature.getInstance("SHA256withECDSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Missing SHA256withECDSA support");
}
}
else {
// Bad luck, not (yet) supported.
throw new HandshakeFailureAlert("Signature algorithm not supported " + signatureScheme);
}
return signatureAlgorithm;
}
@Override
public byte[] getClientEarlyTrafficSecret() {
if (state != null) {
return state.getClientEarlyTrafficSecret();
}
else {
throw new IllegalStateException("Traffic secret not yet available");
}
}
@Override
public byte[] getClientHandshakeTrafficSecret() {
if (state != null) {
return state.getClientHandshakeTrafficSecret();
}
else {
throw new IllegalStateException("Traffic secret not yet available");
}
}
@Override
public byte[] getServerHandshakeTrafficSecret() {
if (state != null) {
return state.getServerHandshakeTrafficSecret();
}
else {
throw new IllegalStateException("Traffic secret not yet available");
}
}
@Override
public byte[] getClientApplicationTrafficSecret() {
if (state != null) {
return state.getClientApplicationTrafficSecret();
}
else {
throw new IllegalStateException("Traffic secret not yet available");
}
}
@Override
public byte[] getServerApplicationTrafficSecret() {
if (state != null) {
return state.getServerApplicationTrafficSecret();
}
else {
throw new IllegalStateException("Traffic secret not yet available");
}
}
}