/
wstartsev
/
jdk
Обзор
Документация
Войти
/
wstartsev
/
jdk
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/java.base/share/classes/sun/security/ssl/QuicCipher.java
699 строк
28 KB
Daniel Fuchs
8349910: Implement JEP 517: HTTP/3 for the HTTP Client API
22 сен 2025, 13:12
22 сен 2025, 13:12
e8db14f
Код
Авторство
О чём код?
/* * Copyright (c) 2022, 2025, 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.ssl; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.GeneralSecurityException; import java.security.Security; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import javax.crypto.AEADBadTagException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; import javax.crypto.spec.ChaCha20ParameterSpec; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; import jdk.internal.net.quic.QuicTransportErrors; import jdk.internal.net.quic.QuicTransportException; import sun.security.util.KeyUtil; import static jdk.internal.net.quic.QuicTLSEngine.KeySpace.ONE_RTT; import static sun.security.ssl.QuicTLSEngineImpl.BASE_CRYPTO_ERROR; abstract class QuicCipher { private static final String SEC_PROP_QUIC_TLS_KEY_LIMITS = "jdk.quic.tls.keyLimits"; private static final Map<String, Long> KEY_LIMITS; static { final String propVal = Security.getProperty( SEC_PROP_QUIC_TLS_KEY_LIMITS); if (propVal == null) { KEY_LIMITS = Map.of(); // no specific limits } else { final Map<String, Long> limits = new HashMap<>(); for (final String entry : propVal.split(",")) { // each entry is of the form <cipher> <limit> // example: // AES/GCM/NoPadding 2^23 // ChaCha20-Poly1305 -1 final String[] parts = entry.trim().split(" "); if (parts.length != 2) { // TODO: exception type throw new RuntimeException("invalid value for " + SEC_PROP_QUIC_TLS_KEY_LIMITS + " security property"); } final String cipher = parts[0]; if (limits.containsKey(cipher)) { throw new RuntimeException( "key limit defined more than once for cipher " + cipher); } final String limitVal = parts[1]; final long limit; final int index = limitVal.indexOf("^"); if (index >= 0) { // of the form x^y (example: 2^23) limit = (long) Math.pow( Integer.parseInt(limitVal.substring(0, index)), Integer.parseInt(limitVal.substring(index + 1))); } else { limit = Long.parseLong(limitVal); } if (limit == 0 || limit < -1) { // we allow -1 to imply no limits, but any other zero // or negative value is invalid // TODO: exception type throw new RuntimeException("invalid value for " + SEC_PROP_QUIC_TLS_KEY_LIMITS + " security property"); } limits.put(cipher, limit); } KEY_LIMITS = Collections.unmodifiableMap(limits); } } private final CipherSuite cipherSuite; private final QuicHeaderProtectionCipher hpCipher; private final SecretKey baseSecret; private final int keyPhase; protected QuicCipher(final CipherSuite cipherSuite, final SecretKey baseSecret, final QuicHeaderProtectionCipher hpCipher, final int keyPhase) { assert keyPhase == 0 || keyPhase == 1 : "invalid key phase: " + keyPhase; this.cipherSuite = cipherSuite; this.baseSecret = baseSecret; this.hpCipher = hpCipher; this.keyPhase = keyPhase; } final SecretKey getBaseSecret() { return this.baseSecret; } final CipherSuite getCipherSuite() { return this.cipherSuite; } final SecretKey getHeaderProtectionKey() { return this.hpCipher.headerProtectionKey; } final ByteBuffer computeHeaderProtectionMask(ByteBuffer sample) throws QuicTransportException { return hpCipher.computeHeaderProtectionMask(sample); } final int getKeyPhase() { return this.keyPhase; } final void discard(boolean destroyHP) { safeDiscard(this.baseSecret); if (destroyHP) { this.hpCipher.discard(); } this.doDiscard(); } protected abstract void doDiscard(); static QuicReadCipher createReadCipher(final CipherSuite cipherSuite, final SecretKey baseSecret, final SecretKey key, final byte[] iv, final SecretKey hp, final int keyPhase) throws GeneralSecurityException { return switch (cipherSuite) { case TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384 -> new T13GCMReadCipher( cipherSuite, baseSecret, key, iv, hp, keyPhase); case TLS_CHACHA20_POLY1305_SHA256 -> new T13CC20P1305ReadCipher( cipherSuite, baseSecret, key, iv, hp, keyPhase); default -> throw new IllegalArgumentException("Cipher suite " + cipherSuite + " not supported"); }; } static QuicWriteCipher createWriteCipher(final CipherSuite cipherSuite, final SecretKey baseSecret, final SecretKey key, final byte[] iv, final SecretKey hp, final int keyPhase) throws GeneralSecurityException { return switch (cipherSuite) { case TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384 -> new T13GCMWriteCipher(cipherSuite, baseSecret, key, iv, hp, keyPhase); case TLS_CHACHA20_POLY1305_SHA256 -> new T13CC20P1305WriteCipher(cipherSuite, baseSecret, key, iv, hp, keyPhase); default -> throw new IllegalArgumentException("Cipher suite " + cipherSuite + " not supported"); }; } static void safeDiscard(final SecretKey secretKey) { KeyUtil.destroySecretKeys(secretKey); } abstract static class QuicReadCipher extends QuicCipher { private final AtomicLong lowestDecryptedPktNum = new AtomicLong(-1); QuicReadCipher(CipherSuite cipherSuite, SecretKey baseSecret, QuicHeaderProtectionCipher hpCipher, int keyPhase) { super(cipherSuite, baseSecret, hpCipher, keyPhase); } final void decryptPacket(long packetNumber, ByteBuffer packet, int headerLength, ByteBuffer output) throws AEADBadTagException, ShortBufferException, QuicTransportException { doDecrypt(packetNumber, packet, headerLength, output); boolean updated; do { final long current = lowestDecryptedPktNum.get(); assert packetNumber >= 0 : "unexpected packet number: " + packetNumber; final long newLowest = current == -1 ? packetNumber : Math.min(current, packetNumber); updated = lowestDecryptedPktNum.compareAndSet(current, newLowest); } while (!updated); } protected abstract void doDecrypt(long packetNumber, ByteBuffer packet, int headerLength, ByteBuffer output) throws AEADBadTagException, ShortBufferException, QuicTransportException; /** * Returns the maximum limit on the number of packets that fail * decryption, across all key (updates), using this * {@code QuicReadCipher}. This method must not return a value less * than 0. * * @return the limit */ // RFC-9001, section 6.6 abstract long integrityLimit(); /** * {@return the lowest packet number that this {@code QuicReadCipher} * has decrypted. If no packets have yet been decrypted by this * instance, then this method returns -1} */ final long lowestDecryptedPktNum() { return this.lowestDecryptedPktNum.get(); } /** * {@return true if this {@code QuicReadCipher} has successfully * decrypted any packet sent by the peer, else returns false} */ final boolean hasDecryptedAny() { return this.lowestDecryptedPktNum.get() != -1; } } abstract static class QuicWriteCipher extends QuicCipher { private final AtomicLong numPacketsEncrypted = new AtomicLong(); private final AtomicLong lowestEncryptedPktNum = new AtomicLong(-1); QuicWriteCipher(CipherSuite cipherSuite, SecretKey baseSecret, QuicHeaderProtectionCipher hpCipher, int keyPhase) { super(cipherSuite, baseSecret, hpCipher, keyPhase); } final void encryptPacket(final long packetNumber, final ByteBuffer packetHeader, final ByteBuffer packetPayload, final ByteBuffer output) throws QuicTransportException, ShortBufferException { final long confidentialityLimit = confidentialityLimit(); final long numEncrypted = this.numPacketsEncrypted.get(); if (confidentialityLimit > 0 && numEncrypted > confidentialityLimit) { // the OneRttKeyManager is responsible for detecting and // initiating a key update before this limit is hit. The fact // that we hit this limit indicates that either the key // update wasn't initiated or the key update failed. In // either case we just throw an exception which // should lead to the connection being closed as required by // RFC-9001, section 6.6: // If a key update is not possible or integrity limits are // reached, the endpoint MUST stop using the connection and // only send stateless resets in response to receiving // packets. It is RECOMMENDED that endpoints immediately // close the connection with a connection error of type // AEAD_LIMIT_REACHED before reaching a state where key // updates are not possible. throw new QuicTransportException("confidentiality limit " + "reached", ONE_RTT, 0, QuicTransportErrors.AEAD_LIMIT_REACHED); } this.numPacketsEncrypted.incrementAndGet(); doEncryptPacket(packetNumber, packetHeader, packetPayload, output); boolean updated; do { final long current = lowestEncryptedPktNum.get(); assert packetNumber >= 0 : "unexpected packet number: " + packetNumber; final long newLowest = current == -1 ? packetNumber : Math.min(current, packetNumber); updated = lowestEncryptedPktNum.compareAndSet(current, newLowest); } while (!updated); } /** * {@return the lowest packet number that this {@code QuicWriteCipher} * has encrypted. If no packets have yet been encrypted by this * instance, then this method returns -1} */ final long lowestEncryptedPktNum() { return this.lowestEncryptedPktNum.get(); } /** * {@return true if this {@code QuicWriteCipher} has successfully * encrypted any packet to send to the peer, else returns false} */ final boolean hasEncryptedAny() { // rely on the lowestEncryptedPktNum field instead of the // numPacketsEncrypted field. this avoids a race where the // lowestEncryptedPktNum() might return a value contradicting // the return value of this method. return this.lowestEncryptedPktNum.get() != -1; } /** * {@return the number of packets encrypted by this {@code * QuicWriteCipher}} */ final long getNumEncrypted() { return this.numPacketsEncrypted.get(); } abstract void doEncryptPacket(long packetNumber, ByteBuffer packetHeader, ByteBuffer packetPayload, ByteBuffer output) throws ShortBufferException, QuicTransportException; /** * Returns the maximum limit on the number of packets that are allowed * to be encrypted with this instance of {@code QuicWriteCipher}. A * value less than 0 implies that there's no limit. * * @return the limit or -1 */ // RFC-9001, section 6.6: The confidentiality limit applies to the // number of // packets encrypted with a given key. abstract long confidentialityLimit(); } abstract static class QuicHeaderProtectionCipher { protected final SecretKey headerProtectionKey; protected QuicHeaderProtectionCipher( final SecretKey headerProtectionKey) { this.headerProtectionKey = headerProtectionKey; } int getHeaderProtectionSampleSize() { return 16; } abstract ByteBuffer computeHeaderProtectionMask(ByteBuffer sample) throws QuicTransportException; final void discard() { safeDiscard(this.headerProtectionKey); } } static final class T13GCMReadCipher extends QuicReadCipher { // RFC-9001, section 6.6: For AEAD_AES_128_GCM and AEAD_AES_256_GCM, // the integrity limit is 2^52 invalid packets private static final long INTEGRITY_LIMIT = 1L << 52; private final Cipher cipher; private final SecretKey key; private final byte[] iv; T13GCMReadCipher(final CipherSuite cipherSuite, final SecretKey baseSecret, final SecretKey key, final byte[] iv, final SecretKey hp, final int keyPhase) throws GeneralSecurityException { super(cipherSuite, baseSecret, new T13AESHPCipher(hp), keyPhase); this.key = key; this.iv = iv; this.cipher = Cipher.getInstance("AES/GCM/NoPadding"); } @Override protected void doDecrypt(long packetNumber, ByteBuffer packet, int headerLength, ByteBuffer output) throws AEADBadTagException, ShortBufferException, QuicTransportException { byte[] iv = this.iv.clone(); // apply packet number to IV int i = 11; while (packetNumber > 0) { iv[i] ^= (byte) (packetNumber & 0xFF); packetNumber = packetNumber >>> 8; i--; } final GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv); synchronized (cipher) { try { cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); int limit = packet.limit(); packet.limit(packet.position() + headerLength); cipher.updateAAD(packet); packet.limit(limit); cipher.doFinal(packet, output); } catch (AEADBadTagException | ShortBufferException e) { throw e; } catch (Exception e) { throw new QuicTransportException("Decryption failed", null, 0, BASE_CRYPTO_ERROR + Alert.INTERNAL_ERROR.id, e); } } } @Override long integrityLimit() { return INTEGRITY_LIMIT; } @Override protected final void doDiscard() { safeDiscard(this.key); } } static final class T13GCMWriteCipher extends QuicWriteCipher { private static final String CIPHER_ALGORITHM_NAME = "AES/GCM/NoPadding"; private static final long CONFIDENTIALITY_LIMIT; static { // RFC-9001, section 6.6: For AEAD_AES_128_GCM and AEAD_AES_256_GCM, // the confidentiality limit is 2^23 encrypted packets final long defaultVal = 1 << 23; long limit = KEY_LIMITS.getOrDefault(CIPHER_ALGORITHM_NAME, defaultVal); // don't allow the configuration to increase the confidentiality // limit, but only let it lower the limit limit = limit > defaultVal ? defaultVal : limit; CONFIDENTIALITY_LIMIT = limit; } private final SecretKey key; private final Cipher cipher; private final byte[] iv; T13GCMWriteCipher(final CipherSuite cipherSuite, final SecretKey baseSecret, final SecretKey key, final byte[] iv, final SecretKey hp, final int keyPhase) throws GeneralSecurityException { super(cipherSuite, baseSecret, new T13AESHPCipher(hp), keyPhase); this.key = key; this.iv = iv; this.cipher = Cipher.getInstance(CIPHER_ALGORITHM_NAME); } @Override void doEncryptPacket(long packetNumber, ByteBuffer packetHeader, ByteBuffer packetPayload, ByteBuffer output) throws ShortBufferException, QuicTransportException { byte[] iv = this.iv.clone(); // apply packet number to IV int i = 11; while (packetNumber > 0) { iv[i] ^= (byte) (packetNumber & 0xFF); packetNumber = packetNumber >>> 8; i--; } final GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv); synchronized (cipher) { try { cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); cipher.updateAAD(packetHeader); cipher.doFinal(packetPayload, output); } catch (ShortBufferException e) { throw e; } catch (Exception e) { throw new QuicTransportException("Encryption failed", null, 0, BASE_CRYPTO_ERROR + Alert.INTERNAL_ERROR.id, e); } } } @Override long confidentialityLimit() { return CONFIDENTIALITY_LIMIT; } @Override protected final void doDiscard() { safeDiscard(this.key); } } static final class T13AESHPCipher extends QuicHeaderProtectionCipher { private final Cipher cipher; T13AESHPCipher(SecretKey hp) throws GeneralSecurityException { super(hp); cipher = Cipher.getInstance("AES/ECB/NoPadding"); } @Override public ByteBuffer computeHeaderProtectionMask(ByteBuffer sample) throws QuicTransportException { if (sample.remaining() != getHeaderProtectionSampleSize()) { throw new IllegalArgumentException("Invalid sample size"); } ByteBuffer output = ByteBuffer.allocate(sample.remaining()); try { synchronized (cipher) { // Some providers (Jipher) don't re-initialize the cipher // after doFinal, and need init every time. cipher.init(Cipher.ENCRYPT_MODE, headerProtectionKey); cipher.doFinal(sample, output); } output.flip(); assert output.remaining() >= 5; return output; } catch (Exception e) { throw new QuicTransportException("Encryption failed", null, 0, BASE_CRYPTO_ERROR + Alert.INTERNAL_ERROR.id, e); } } } static final class T13CC20P1305ReadCipher extends QuicReadCipher { // RFC-9001, section 6.6: For AEAD_CHACHA20_POLY1305, // the integrity limit is 2^36 invalid packets private static final long INTEGRITY_LIMIT = 1L << 36; private final SecretKey key; private final Cipher cipher; private final byte[] iv; T13CC20P1305ReadCipher(final CipherSuite cipherSuite, final SecretKey baseSecret, final SecretKey key, final byte[] iv, final SecretKey hp, final int keyPhase) throws GeneralSecurityException { super(cipherSuite, baseSecret, new T13CC20HPCipher(hp), keyPhase); this.key = key; this.iv = iv; this.cipher = Cipher.getInstance("ChaCha20-Poly1305"); } @Override protected void doDecrypt(long packetNumber, ByteBuffer packet, int headerLength, ByteBuffer output) throws AEADBadTagException, ShortBufferException, QuicTransportException { byte[] iv = this.iv.clone(); // apply packet number to IV int i = 11; while (packetNumber > 0) { iv[i] ^= (byte) (packetNumber & 0xFF); packetNumber = packetNumber >>> 8; i--; } final IvParameterSpec ivSpec = new IvParameterSpec(iv); synchronized (cipher) { try { cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); int limit = packet.limit(); packet.limit(packet.position() + headerLength); cipher.updateAAD(packet); packet.limit(limit); cipher.doFinal(packet, output); } catch (AEADBadTagException | ShortBufferException e) { throw e; } catch (Exception e) { throw new QuicTransportException("Decryption failed", null, 0, BASE_CRYPTO_ERROR + Alert.INTERNAL_ERROR.id, e); } } } @Override long integrityLimit() { return INTEGRITY_LIMIT; } @Override protected final void doDiscard() { safeDiscard(this.key); } } static final class T13CC20P1305WriteCipher extends QuicWriteCipher { private static final String CIPHER_ALGORITHM_NAME = "ChaCha20-Poly1305"; private static final long CONFIDENTIALITY_LIMIT; static { // RFC-9001, section 6.6: For AEAD_CHACHA20_POLY1305, the // confidentiality limit is greater than the number of possible // packets (2^62) and so can be disregarded. final long defaultVal = -1; // no limit long limit = KEY_LIMITS.getOrDefault(CIPHER_ALGORITHM_NAME, defaultVal); limit = limit < 0 ? -1 /* no limit */ : limit; CONFIDENTIALITY_LIMIT = limit; } private final SecretKey key; private final Cipher cipher; private final byte[] iv; T13CC20P1305WriteCipher(final CipherSuite cipherSuite, final SecretKey baseSecret, final SecretKey key, final byte[] iv, final SecretKey hp, final int keyPhase) throws GeneralSecurityException { super(cipherSuite, baseSecret, new T13CC20HPCipher(hp), keyPhase); this.key = key; this.iv = iv; this.cipher = Cipher.getInstance(CIPHER_ALGORITHM_NAME); } @Override void doEncryptPacket(final long packetNumber, final ByteBuffer packetHeader, final ByteBuffer packetPayload, final ByteBuffer output) throws ShortBufferException, QuicTransportException { byte[] iv = this.iv.clone(); // apply packet number to IV int i = 11; long pn = packetNumber; while (pn > 0) { iv[i] ^= (byte) (pn & 0xFF); pn = pn >>> 8; i--; } final IvParameterSpec ivSpec = new IvParameterSpec(iv); synchronized (cipher) { try { cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); cipher.updateAAD(packetHeader); cipher.doFinal(packetPayload, output); } catch (ShortBufferException e) { throw e; } catch (Exception e) { throw new QuicTransportException("Encryption failed", null, 0, BASE_CRYPTO_ERROR + Alert.INTERNAL_ERROR.id, e); } } } @Override long confidentialityLimit() { return CONFIDENTIALITY_LIMIT; } @Override protected final void doDiscard() { safeDiscard(this.key); } } static final class T13CC20HPCipher extends QuicHeaderProtectionCipher { private final Cipher cipher; T13CC20HPCipher(final SecretKey hp) throws GeneralSecurityException { super(hp); cipher = Cipher.getInstance("ChaCha20"); } @Override public ByteBuffer computeHeaderProtectionMask(ByteBuffer sample) throws QuicTransportException { if (sample.remaining() != getHeaderProtectionSampleSize()) { throw new IllegalArgumentException("Invalid sample size"); } try { // RFC 7539: [counter is a] 32-bit block count parameter, // treated as a 32-bit little-endian integer // RFC 9001: // counter = sample[0..3] // nonce = sample[4..15] // mask = ChaCha20(hp_key, counter, nonce, {0,0,0,0,0}) sample.order(ByteOrder.LITTLE_ENDIAN); byte[] nonce = new byte[12]; int counter = sample.getInt(); sample.get(nonce); ChaCha20ParameterSpec ivSpec = new ChaCha20ParameterSpec(nonce, counter); byte[] output = new byte[5]; synchronized (cipher) { // DECRYPT produces the same output as ENCRYPT, but does // not throw when the same IV is used repeatedly cipher.init(Cipher.DECRYPT_MODE, headerProtectionKey, ivSpec); int numBytes = cipher.doFinal(output, 0, 5, output); assert numBytes == 5; } return ByteBuffer.wrap(output); } catch (Exception e) { throw new QuicTransportException("Encryption failed", null, 0, BASE_CRYPTO_ERROR + Alert.INTERNAL_ERROR.id, e); } } } }