1 /*
2  * Copyright (c) 2018, 2019, 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 Mac hmacObj;
48     private final int hmacLen;
49 
50     /**
51      * Create an HDKF object, specifying the underlying message digest
52      * algorithm.
53      *
54      * @param hashAlg a standard name corresponding to a supported message
55      * digest algorithm.
56      *
57      * @throws NoSuchAlgorithmException if that message digest algorithm does
58      * not have an HMAC variant supported on any available provider.
59      */
HKDF(String hashAlg)60     HKDF(String hashAlg) throws NoSuchAlgorithmException {
61         Objects.requireNonNull(hashAlg,
62                 "Must provide underlying HKDF Digest algorithm.");
63         String hmacAlg = "Hmac" + hashAlg.replace("-", "");
64         hmacObj = Mac.getInstance(hmacAlg);
65         hmacLen = hmacObj.getMacLength();
66     }
67 
68     /**
69      * Perform the HMAC-Extract derivation.
70      *
71      * @param salt a salt value, implemented as a {@code SecretKey}.  A
72      * {@code null} value is allowed, which will internally use an array of
73      * zero bytes the same size as the underlying hash output length.
74      * @param inputKey the input keying material provided as a
75      * {@code SecretKey}.
76      * @param keyAlg the algorithm name assigned to the resulting
77      * {@code SecretKey} object.
78      *
79      * @return a {@code SecretKey} that is the result of the HKDF extract
80      * operation.
81      *
82      * @throws InvalidKeyException if the {@code salt} parameter cannot be
83      * used to initialize the underlying HMAC.
84      */
extract(SecretKey salt, SecretKey inputKey, String keyAlg)85     SecretKey extract(SecretKey salt, SecretKey inputKey, String keyAlg)
86             throws InvalidKeyException {
87         if (salt == null) {
88             salt = new SecretKeySpec(new byte[hmacLen], "HKDF-Salt");
89         }
90         hmacObj.init(salt);
91 
92         return new SecretKeySpec(hmacObj.doFinal(inputKey.getEncoded()),
93                 keyAlg);
94     }
95 
96     /**
97      * Perform the HMAC-Extract derivation.
98      *
99      * @param salt a salt value as cleartext bytes.  A {@code null} value is
100      * allowed, which will internally use an array of zero bytes the same
101      * size as the underlying hash output length.
102      * @param inputKey the input keying material provided as a
103      * {@code SecretKey}.
104      * @param keyAlg the algorithm name assigned to the resulting
105      * {@code SecretKey} object.
106      *
107      * @return a {@code SecretKey} that is the result of the HKDF extract
108      * operation.
109      *
110      * @throws InvalidKeyException if the {@code salt} parameter cannot be
111      * used to initialize the underlying HMAC.
112      */
extract(byte[] salt, SecretKey inputKey, String keyAlg)113     SecretKey extract(byte[] salt, SecretKey inputKey, String keyAlg)
114             throws InvalidKeyException {
115         if (salt == null) {
116             salt = new byte[hmacLen];
117         }
118         return extract(new SecretKeySpec(salt, "HKDF-Salt"), inputKey, keyAlg);
119     }
120 
121     /**
122      * Perform the HKDF-Expand derivation for a single-key output.
123      *
124      * @param pseudoRandKey the pseudo random key (PRK).
125      * @param info optional context-specific info.  A {@code null} value is
126      * allowed in which case a zero-length byte array will be used.
127      * @param outLen the length of the resulting {@code SecretKey}
128      * @param keyAlg the algorithm name applied to the resulting
129      * {@code SecretKey}
130      *
131      * @return the resulting key derivation as a {@code SecretKey} object
132      *
133      * @throws InvalidKeyException if the underlying HMAC operation cannot
134      * be initialized using the provided {@code pseudoRandKey} object.
135      */
expand(SecretKey pseudoRandKey, byte[] info, int outLen, String keyAlg)136     SecretKey expand(SecretKey pseudoRandKey, byte[] info, int outLen,
137             String keyAlg) throws InvalidKeyException {
138         byte[] kdfOutput;
139 
140         // Calculate the number of rounds of HMAC that are needed to
141         // meet the requested data.  Then set up the buffers we will need.
142         Objects.requireNonNull(pseudoRandKey, "A null PRK is not allowed.");
143 
144         // Output from the expand operation must be <= 255 * hmac length
145         if (outLen > 255 * hmacLen) {
146             throw new IllegalArgumentException("Requested output length " +
147                     "exceeds maximum length allowed for HKDF expansion");
148         }
149         hmacObj.init(pseudoRandKey);
150         if (info == null) {
151             info = new byte[0];
152         }
153         int rounds = (outLen + hmacLen - 1) / hmacLen;
154         kdfOutput = new byte[rounds * hmacLen];
155         int offset = 0;
156         int tLength = 0;
157 
158         for (int i = 0; i < rounds ; i++) {
159 
160             // Calculate this round
161             try {
162                  // Add T(i).  This will be an empty string on the first
163                  // iteration since tLength starts at zero.  After the first
164                  // iteration, tLength is changed to the HMAC length for the
165                  // rest of the loop.
166                 hmacObj.update(kdfOutput,
167                         Math.max(0, offset - hmacLen), tLength);
168                 hmacObj.update(info);                       // Add info
169                 hmacObj.update((byte)(i + 1));              // Add round number
170                 hmacObj.doFinal(kdfOutput, offset);
171 
172                 tLength = hmacLen;
173                 offset += hmacLen;                       // For next iteration
174             } catch (ShortBufferException sbe) {
175                 // This really shouldn't happen given that we've
176                 // sized the buffers to their largest possible size up-front,
177                 // but just in case...
178                 throw new RuntimeException(sbe);
179             }
180         }
181 
182         return new SecretKeySpec(kdfOutput, 0, outLen, keyAlg);
183     }
184 }
185 
186