diff --git a/src/java.base/share/classes/sun/security/ssl/DHasKEM.java b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java index 763013f280c..ef5c5b82f06 100644 --- a/src/java.base/share/classes/sun/security/ssl/DHasKEM.java +++ b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -101,7 +101,18 @@ public KEM.Encapsulated engineEncapsulate(int from, int to, return new KEM.Encapsulated( sub(dh, from, to), pkEm, null); + + } catch (IllegalArgumentException e) { + // ECDH validation failure + // all-zero shared secret + throw e; + } catch (InvalidKeyException e) { + // Invalid peer public key + // Convert InvalidKeyException to an unchecked exception + throw new IllegalArgumentException("Invalid peer public key", + e); } catch (Exception e) { + // Unexpected internal failure throw new ProviderException("internal error", e); } } @@ -126,6 +137,11 @@ public SecretKey engineDecapsulate(byte[] encapsulation, int from, PublicKey pkE = params.DeserializePublicKey(encapsulation); SecretKey dh = params.DH(algorithm, skR, pkE); return sub(dh, from, to); + + } catch (IllegalArgumentException e) { + // ECDH validation failure + // all-zero shared secret + throw e; } catch (IOException | InvalidKeyException e) { throw new DecapsulateException("Cannot decapsulate", e); } catch (Exception e) { @@ -248,7 +264,24 @@ private SecretKey DH(String alg, PrivateKey skE, PublicKey pkR) KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); ka.init(skE); ka.doPhase(pkR, true); - return ka.generateSecret(alg); + SecretKey secret = ka.generateSecret(alg); + + // RFC 8446 section 7.4.2: checks for all-zero + // X25519/X448 shared secret. + if (kaAlgorithm.equals("X25519") || + kaAlgorithm.equals("X448")) { + byte[] s = secret.getEncoded(); + for (byte b : s) { + if (b != 0) { + return secret; + } + } + // Trigger ILLEGAL_PARAMETER alert + throw new IllegalArgumentException( + "All-zero shared secret"); + } + + return secret; } } } diff --git a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java index 39e82b50435..dea86351cc8 100644 --- a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ import sun.security.util.RawKeySpec; +import javax.crypto.DecapsulateException; import javax.crypto.KDF; import javax.crypto.KEM; import javax.crypto.KeyAgreement; @@ -35,6 +36,7 @@ import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.Provider; @@ -173,6 +175,9 @@ KEM.Encapsulated encapsulate(String algorithm, SecureRandom random) "encapsulation"); } + // All exceptions thrown during KEM encapsulation are mapped + // to TLS fatal alerts: + // illegal_parameter alert or internal_error alert. try { KeyFactory kf = (provider != null) ? KeyFactory.getInstance(algorithmName, provider) : @@ -189,8 +194,18 @@ KEM.Encapsulated encapsulate(String algorithm, SecureRandom random) SecretKey derived = deriveHandshakeSecret(algorithm, sharedSecret); return new KEM.Encapsulated(derived, enc.encapsulation(), null); - } catch (GeneralSecurityException gse) { - throw new SSLHandshakeException("Could not generate secret", gse); + } catch (IllegalArgumentException | InvalidKeyException e) { + // Peer validation failure + // ECDH all-zero shared secret (RFC 8446 section 7.4.2), + // ML-KEM encapsulation key check failure (FIPS-203 section 7.2) + throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, e); + } catch (GeneralSecurityException e) { + // Cryptographic failure, + // deriveHandshakeSecret failure. + throw context.conContext.fatal(Alert.INTERNAL_ERROR, e); + } catch (RuntimeException e) { + // unexpected provider/runtime failure + throw context.conContext.fatal(Alert.INTERNAL_ERROR, e); } finally { KeyUtil.destroySecretKeys(sharedSecret); } @@ -208,13 +223,30 @@ private SecretKey t13DeriveKey(String type) // Using KEM: called by the client after receiving the KEM // ciphertext (keyshare) from the server in ServerHello. // The client decapsulates it using its private key. - KEM kem = (provider != null) - ? KEM.getInstance(algorithmName, provider) - : KEM.getInstance(algorithmName); - var decapsulator = kem.newDecapsulator(localPrivateKey); - sharedSecret = decapsulator.decapsulate( - keyshare, 0, decapsulator.secretSize(), - "TlsPremasterSecret"); + + // All exceptions thrown during KEM decapsulation are mapped + // to TLS fatal alerts: + // illegal_parameter alert or internal_error alert. + try { + KEM kem = (provider != null) + ? KEM.getInstance(algorithmName, provider) + : KEM.getInstance(algorithmName); + var decapsulator = kem.newDecapsulator(localPrivateKey); + sharedSecret = decapsulator.decapsulate( + keyshare, 0, decapsulator.secretSize(), + "TlsPremasterSecret"); + } catch (IllegalArgumentException | InvalidKeyException | + DecapsulateException e) { + // Peer validation failure + // ECDH all-zero shared secret (RFC 8446 section 7.4.2) + throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, e); + } catch (GeneralSecurityException e) { + // cryptographic failure + throw context.conContext.fatal(Alert.INTERNAL_ERROR, e); + } catch (RuntimeException e) { + // unexpected provider/runtime failure + throw context.conContext.fatal(Alert.INTERNAL_ERROR, e); + } } else { // Using traditional DH-style Key Agreement KeyAgreement ka = KeyAgreement.getInstance(algorithmName); @@ -225,6 +257,7 @@ private SecretKey t13DeriveKey(String type) return deriveHandshakeSecret(type, sharedSecret); } catch (GeneralSecurityException gse) { + // deriveHandshakeSecret() failure throw new SSLHandshakeException("Could not generate secret", gse); } finally { KeyUtil.destroySecretKeys(sharedSecret);