1 /*
2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.security.ssl;
27 
28 import java.security.NoSuchAlgorithmException;
29 import java.security.InvalidKeyException;
30 import javax.crypto.Mac;
31 import javax.crypto.SecretKey;
32 import javax.crypto.ShortBufferException;
33 import javax.crypto.spec.SecretKeySpec;
34 import java.util.Objects;
35 
36 /**
37  * An implementation of the HKDF key derivation algorithm outlined in RFC 5869,
38  * specific to the needs of TLS 1.3 key derivation in JSSE.  This is not a
39  * general purpose HKDF implementation and is suited only to single-key output
40  * derivations.
41  *
42  * HKDF objects are created by specifying a message digest algorithm.  That
43  * digest algorithm will be used by the HMAC function as part of the HKDF
44  * derivation process.
45  */
46 final class HKDF {
47     private final String hmacAlg;
48     private final Mac hmacObj;
49     private final int hmacLen;
50 
51     /**
52      * Create an HDKF object, specifying the underlying message digest
53      * algorithm.
54      *
55      * @param hashAlg a standard name corresponding to a supported message
56      * digest algorithm.
57      *
58      * @throws NoSuchAlgorithmException if that message digest algorithm does
59      * not have an HMAC variant supported on any available provider.
60      */
HKDF(String hashAlg)61     HKDF(String hashAlg) throws NoSuchAlgorithmException {
62         Objects.requireNonNull(hashAlg,
63                 "Must provide underlying HKDF Digest algorithm.");
64         hmacAlg = "Hmac" + hashAlg.replace("-", "");
65         hmacObj = JsseJce.getMac(hmacAlg);
66         hmacLen = hmacObj.getMacLength();
67     }
68 
69     /**
70      * Perform the HMAC-Extract derivation.
71      *
72      * @param salt a salt value, implemented as a {@code SecretKey}.  A
73      * {@code null} value is allowed, which will internally use an array of
74      * zero bytes the same size as the underlying hash output length.
75      * @param inputKey the input keying material provided as a
76      * {@code SecretKey}.
77      * @param keyAlg the algorithm name assigned to the resulting
78      * {@code SecretKey} object.
79      *
80      * @return a {@code SecretKey} that is the result of the HKDF extract
81      * operation.
82      *
83      * @throws InvalidKeyException if the {@code salt} parameter cannot be
84      * used to initialize the underlying HMAC.
85      */
extract(SecretKey salt, SecretKey inputKey, String keyAlg)86     SecretKey extract(SecretKey salt, SecretKey inputKey, String keyAlg)
87             throws InvalidKeyException {
88         if (salt == null) {
89             salt = new SecretKeySpec(new byte[hmacLen], "HKDF-Salt");
90         }
91         hmacObj.init(salt);
92 
93         return new SecretKeySpec(hmacObj.doFinal(inputKey.getEncoded()),
94                 keyAlg);
95     }
96 
97     /**
98      * Perform the HMAC-Extract derivation.
99      *
100      * @param salt a salt value as cleartext bytes.  A {@code null} value is
101      * allowed, which will internally use an array of zero bytes the same
102      * size as the underlying hash output length.
103      * @param inputKey the input keying material provided as a
104      * {@code SecretKey}.
105      * @param keyAlg the algorithm name assigned to the resulting
106      * {@code SecretKey} object.
107      *
108      * @return a {@code SecretKey} that is the result of the HKDF extract
109      * operation.
110      *
111      * @throws InvalidKeyException if the {@code salt} parameter cannot be
112      * used to initialize the underlying HMAC.
113      */
extract(byte[] salt, SecretKey inputKey, String keyAlg)114     SecretKey extract(byte[] salt, SecretKey inputKey, String keyAlg)
115             throws InvalidKeyException {
116         if (salt == null) {
117             salt = new byte[hmacLen];
118         }
119         return extract(new SecretKeySpec(salt, "HKDF-Salt"), inputKey, keyAlg);
120     }
121 
122     /**
123      * Perform the HKDF-Expand derivation for a single-key output.
124      *
125      * @param pseudoRandKey the pseudo random key (PRK).
126      * @param info optional context-specific info.  A {@code null} value is
127      * allowed in which case a zero-length byte array will be used.
128      * @param outLen the length of the resulting {@code SecretKey}
129      * @param keyAlg the algorithm name applied to the resulting
130      * {@code SecretKey}
131      *
132      * @return the resulting key derivation as a {@code SecretKey} object
133      *
134      * @throws InvalidKeyException if the underlying HMAC operation cannot
135      * be initialized using the provided {@code pseudoRandKey} object.
136      */
expand(SecretKey pseudoRandKey, byte[] info, int outLen, String keyAlg)137     SecretKey expand(SecretKey pseudoRandKey, byte[] info, int outLen,
138             String keyAlg) throws InvalidKeyException {
139         byte[] kdfOutput;
140 
141         // Calculate the number of rounds of HMAC that are needed to
142         // meet the requested data.  Then set up the buffers we will need.
143         Objects.requireNonNull(pseudoRandKey, "A null PRK is not allowed.");
144 
145         // Output from the expand operation must be <= 255 * hmac length
146         if (outLen > 255 * hmacLen) {
147             throw new IllegalArgumentException("Requested output length " +
148                     "exceeds maximum length allowed for HKDF expansion");
149         }
150         hmacObj.init(pseudoRandKey);
151         if (info == null) {
152             info = new byte[0];
153         }
154         int rounds = (outLen + hmacLen - 1) / hmacLen;
155         kdfOutput = new byte[rounds * hmacLen];
156         int offset = 0;
157         int tLength = 0;
158 
159         for (int i = 0; i < rounds ; i++) {
160 
161             // Calculate this round
162             try {
163                  // Add T(i).  This will be an empty string on the first
164                  // iteration since tLength starts at zero.  After the first
165                  // iteration, tLength is changed to the HMAC length for the
166                  // rest of the loop.
167                 hmacObj.update(kdfOutput,
168                         Math.max(0, offset - hmacLen), tLength);
169                 hmacObj.update(info);                       // Add info
170                 hmacObj.update((byte)(i + 1));              // Add round number
171                 hmacObj.doFinal(kdfOutput, offset);
172 
173                 tLength = hmacLen;
174                 offset += hmacLen;                       // For next iteration
175             } catch (ShortBufferException sbe) {
176                 // This really shouldn't happen given that we've
177                 // sized the buffers to their largest possible size up-front,
178                 // but just in case...
179                 throw new RuntimeException(sbe);
180             }
181         }
182 
183         return new SecretKeySpec(kdfOutput, 0, outLen, keyAlg);
184     }
185 }
186 
187