1 /* 2 * Copyright 2019 The Netty Project 3 * 4 * The Netty Project licenses this file to you under the Apache License, 5 * version 2.0 (the "License"); you may not use this file except in compliance 6 * with the License. You may obtain a copy of the License at: 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package io.netty5.handler.ssl; 17 18 import io.netty5.util.internal.EmptyArrays; 19 20 import javax.crypto.Mac; 21 import javax.crypto.spec.SecretKeySpec; 22 import java.security.GeneralSecurityException; 23 import java.util.Arrays; 24 25 import static io.netty5.util.internal.ObjectUtil.checkPositiveOrZero; 26 27 /** 28 * This pseudorandom function (PRF) takes as input a secret, a seed, and 29 * an identifying label and produces an output of arbitrary length. 30 * 31 * This is used by the TLS RFC to construct/deconstruct an array of bytes into 32 * composite secrets. 33 * 34 * {@link <a href="https://tools.ietf.org/html/rfc5246">rfc5246</a>} 35 */ 36 final class PseudoRandomFunction { 37 38 /** 39 * Constructor never to be called. 40 */ 41 private PseudoRandomFunction() { 42 } 43 44 /** 45 * Use a single hash function to expand a secret and seed into an 46 * arbitrary quantity of output. 47 * 48 * P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + 49 * HMAC_hash(secret, A(2) + seed) + 50 * HMAC_hash(secret, A(3) + seed) + ... 51 * where + indicates concatenation. 52 * A() is defined as: 53 * A(0) = seed 54 * A(i) = HMAC_hash(secret, A(i-1)) 55 * @param secret The starting secret to use for expansion 56 * @param label An ascii string without a length byte or trailing null character. 57 * @param seed The seed of the hash 58 * @param length The number of bytes to return 59 * @param algo the hmac algorithm to use 60 * @return The expanded secrets 61 * @throws IllegalArgumentException if the algo could not be found. 62 */ 63 static byte[] hash(byte[] secret, byte[] label, byte[] seed, int length, String algo) { 64 checkPositiveOrZero(length, "length"); 65 try { 66 Mac hmac = Mac.getInstance(algo); 67 hmac.init(new SecretKeySpec(secret, algo)); 68 /* 69 * P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + 70 * HMAC_hash(secret, A(2) + seed) + HMAC_hash(secret, A(3) + seed) + ... 71 * where + indicates concatenation. A() is defined as: A(0) = seed, A(i) 72 * = HMAC_hash(secret, A(i-1)) 73 */ 74 75 int iterations = (int) Math.ceil(length / (double) hmac.getMacLength()); 76 byte[] expansion = EmptyArrays.EMPTY_BYTES; 77 byte[] data = concat(label, seed); 78 byte[] A = data; 79 for (int i = 0; i < iterations; i++) { 80 A = hmac.doFinal(A); 81 expansion = concat(expansion, hmac.doFinal(concat(A, data))); 82 } 83 return Arrays.copyOf(expansion, length); 84 } catch (GeneralSecurityException e) { 85 throw new IllegalArgumentException("Could not find algo: " + algo, e); 86 } 87 } 88 89 private static byte[] concat(byte[] first, byte[] second) { 90 byte[] result = Arrays.copyOf(first, first.length + second.length); 91 System.arraycopy(second, 0, result, first.length, second.length); 92 return result; 93 } 94 }