/* * Copyright (c) 2018, 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.security.NoSuchAlgorithmException; import java.security.InvalidKeyException; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; import javax.crypto.spec.SecretKeySpec; import java.util.Objects; /** * An implementation of the HKDF key derivation algorithm outlined in RFC 5869, * specific to the needs of TLS 1.3 key derivation in JSSE. This is not a * general purpose HKDF implementation and is suited only to single-key output * derivations. * * HKDF objects are created by specifying a message digest algorithm. That * digest algorithm will be used by the HMAC function as part of the HKDF * derivation process. */ class HKDF { private final String hmacAlg; private final Mac hmacObj; private final int hmacLen; /** * Create an HDKF object, specifying the underlying message digest * algorithm. * * @param hashAlg a standard name corresponding to a supported message * digest algorithm. * * @throws NoSuchAlgorithmException if that message digest algorithm does * not have an HMAC variant supported on any available provider. */ HKDF(String hashAlg) throws NoSuchAlgorithmException { Objects.requireNonNull(hashAlg, "Must provide underlying HKDF Digest algorithm."); hmacAlg = "Hmac" + hashAlg.replace("-", ""); hmacObj = Mac.getInstance(hmacAlg); hmacLen = hmacObj.getMacLength(); } /** * Perform the HMAC-Extract derivation. * * @param salt a salt value, implemented as a {@code SecretKey}. A * {@code null} value is allowed, which will internally use an array of * zero bytes the same size as the underlying hash output length. * @param inputKey the input keying material provided as a * {@code SecretKey}. * @param keyAlg the algorithm name assigned to the resulting * {@code SecretKey} object. * * @return a {@code SecretKey} that is the result of the HKDF extract * operation. * * @throws InvalidKeyException if the {@code salt} parameter cannot be * used to initialize the underlying HMAC. */ SecretKey extract(SecretKey salt, SecretKey inputKey, String keyAlg) throws InvalidKeyException { if (salt == null) { salt = new SecretKeySpec(new byte[hmacLen], "HKDF-Salt"); } hmacObj.init(salt); return new SecretKeySpec(hmacObj.doFinal(inputKey.getEncoded()), keyAlg); } /** * Perform the HMAC-Extract derivation. * * @param salt a salt value as cleartext bytes. A {@code null} value is * allowed, which will internally use an array of zero bytes the same * size as the underlying hash output length. * @param inputKey the input keying material provided as a * {@code SecretKey}. * @param keyAlg the algorithm name assigned to the resulting * {@code SecretKey} object. * * @return a {@code SecretKey} that is the result of the HKDF extract * operation. * * @throws InvalidKeyException if the {@code salt} parameter cannot be * used to initialize the underlying HMAC. */ SecretKey extract(byte[] salt, SecretKey inputKey, String keyAlg) throws InvalidKeyException { if (salt == null) { salt = new byte[hmacLen]; } return extract(new SecretKeySpec(salt, "HKDF-Salt"), inputKey, keyAlg); } /** * Perform the HKDF-Expand derivation for a single-key output. * * @param pseudoRandKey the pseudo random key (PRK). * @param info optional context-specific info. A {@code null} value is * allowed in which case a zero-length byte array will be used. * @param outLen the length of the resulting {@code SecretKey} * @param keyAlg the algorithm name applied to the resulting * {@code SecretKey} * * @return the resulting key derivation as a {@code SecretKey} object * * @throws InvalidKeyException if the underlying HMAC operation cannot * be initialized using the provided {@code pseudoRandKey} object. */ SecretKey expand(SecretKey pseudoRandKey, byte[] info, int outLen, String keyAlg) throws InvalidKeyException { byte[] kdfOutput; // Calculate the number of rounds of HMAC that are needed to // meet the requested data. Then set up the buffers we will need. Objects.requireNonNull(pseudoRandKey, "A null PRK is not allowed."); hmacObj.init(pseudoRandKey); if (info == null) { info = new byte[0]; } int rounds = (outLen + hmacLen - 1) / hmacLen; kdfOutput = new byte[rounds * hmacLen]; int offset = 0; int tLength = 0; for (int i = 0; i < rounds ; i++) { // Calculate this round try { // Add T(i). This will be an empty string on the first // iteration since tLength starts at zero. After the first // iteration, tLength is changed to the HMAC length for the // rest of the loop. hmacObj.update(kdfOutput, Math.max(0, offset - hmacLen), tLength); hmacObj.update(info); // Add info hmacObj.update((byte)(i + 1)); // Add round number hmacObj.doFinal(kdfOutput, offset); tLength = hmacLen; offset += hmacLen; // For next iteration } catch (ShortBufferException sbe) { // This really shouldn't happen given that we've // sized the buffers to their largest possible size up-front, // but just in case... throw new RuntimeException(sbe); } } return new SecretKeySpec(kdfOutput, 0, outLen, keyAlg); } /** * Perform the HKDF Extract-then-Expand operation. * * @param inputKey the input keying material provided as a * {@code SecretKey}. * @param salt a salt value, implemented as a {@code SecretKey}. A * {@code null} value is allowed, which will internally use an array of * zero bytes the same size as the underlying hash output length. * @param info optional context-specific info. A {@code null} value is * allowed in which case a zero-length byte array will be used. * @param outLen the length of the resulting {@code SecretKey} * @param keyAlg the algorithm name applied to the resulting * {@code SecretKey} * * @return the resulting derivation stored in a {@code SecretKey} object. * * @throws InvalidKeyException if initialization of the underlying HMAC * process fails with the salt during the extract phase, or with the * resulting PRK during the expand phase. */ SecretKey extractExpand(SecretKey inputKey, SecretKey salt, byte[] info, int outLen, String keyAlg) throws InvalidKeyException { SecretKey prk = extract(salt, inputKey, "HKDF-PRK"); return expand(prk, info, outLen, keyAlg); } /** * Perform the HKDF Extract-then-Expand operation. * * @param inputKey the input keying material provided as a * {@code SecretKey}. * @param salt a salt value as cleartext bytes. A {@code null} value is * allowed, which will internally use an array of zero bytes the same * size as the underlying hash output length. * @param info optional context-specific info. A {@code null} value is * allowed in which case a zero-length byte array will be used. * @param outLen the length of the resulting {@code SecretKey} * @param keyAlg the algorithm name applied to the resulting * {@code SecretKey} * * @return the resulting derivation stored in a {@code SecretKey} object. * * @throws InvalidKeyException if initialization of the underlying HMAC * process fails with the salt during the extract phase, or with the * resulting PRK during the expand phase. */ SecretKey extractExpand(SecretKey inputKey, byte[] salt, byte[] info, int outLen, String keyAlg) throws InvalidKeyException { byte[] saltBytes = (salt != null) ? salt : new byte[hmacLen]; return extractExpand(inputKey, new SecretKeySpec(saltBytes, "HKDF-PRK"), info, outLen, keyAlg); } }