1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.subtle; 18 19 import com.google.crypto.tink.AccessesPartialKey; 20 import com.google.crypto.tink.InsecureSecretKeyAccess; 21 import com.google.crypto.tink.Mac; 22 import com.google.crypto.tink.mac.AesCmacKey; 23 import com.google.crypto.tink.mac.AesCmacParameters.Variant; 24 import com.google.crypto.tink.mac.HmacKey; 25 import com.google.crypto.tink.mac.HmacParameters; 26 import com.google.crypto.tink.prf.Prf; 27 import com.google.errorprone.annotations.Immutable; 28 import java.security.GeneralSecurityException; 29 import java.security.InvalidAlgorithmParameterException; 30 import java.util.Arrays; 31 import javax.crypto.spec.SecretKeySpec; 32 33 /** 34 * Class that provides the functionality expressed by the Mac primitive using a Prf implementation. 35 */ 36 @Immutable 37 @AccessesPartialKey 38 public class PrfMac implements Mac { 39 // A single byte to be added to the plaintext for the legacy key type. 40 private static final byte[] FORMAT_VERSION = new byte[] {0}; 41 static final int MIN_TAG_SIZE_IN_BYTES = 10; 42 43 private final Prf wrappedPrf; 44 private final int tagSize; 45 46 @SuppressWarnings("Immutable") 47 private final byte[] outputPrefix; 48 // A field that regulates whether we add a zero-byte to the plaintext or not (because of 49 // the LEGACY variant). 50 @SuppressWarnings("Immutable") 51 private final byte[] plaintextLegacySuffix; 52 53 /** Wrap {@code wrappedPrf } in a Mac primitive with the specified {@code tagSize} */ PrfMac(Prf wrappedPrf, int tagSize)54 public PrfMac(Prf wrappedPrf, int tagSize) throws GeneralSecurityException { 55 this.wrappedPrf = wrappedPrf; 56 this.tagSize = tagSize; 57 this.outputPrefix = new byte[0]; 58 this.plaintextLegacySuffix = new byte[0]; 59 60 // The output length is restricted by the HMAC spec. Check that first. 61 if (tagSize < MIN_TAG_SIZE_IN_BYTES) { 62 throw new InvalidAlgorithmParameterException( 63 "tag size too small, need at least " + MIN_TAG_SIZE_IN_BYTES + " bytes"); 64 } 65 66 // Some Prf implementations have restrictions on maximum tag length. These throw on compute(). 67 // Check for those restrictions on tag length here by doing a compute() pass. 68 Object unused = wrappedPrf.compute(new byte[0], tagSize); 69 } 70 PrfMac(AesCmacKey key)71 private PrfMac(AesCmacKey key) throws GeneralSecurityException { 72 wrappedPrf = new PrfAesCmac(key.getAesKey().toByteArray(InsecureSecretKeyAccess.get())); 73 // Due to the correctness checks during AesCmacKey creation, there is no need to perform 74 // additional tag size checks here. 75 tagSize = key.getParameters().getCryptographicTagSizeBytes(); 76 outputPrefix = key.getOutputPrefix().toByteArray(); 77 if (key.getParameters().getVariant().equals(Variant.LEGACY)) { 78 plaintextLegacySuffix = Arrays.copyOf(FORMAT_VERSION, FORMAT_VERSION.length); 79 } else { 80 plaintextLegacySuffix = new byte[0]; 81 } 82 } 83 PrfMac(HmacKey key)84 private PrfMac(HmacKey key) throws GeneralSecurityException { 85 // The use of toString() in this code leverages the fact that the constructor will not work if 86 // the algorithm name is incorrect. 87 wrappedPrf = 88 new PrfHmacJce( 89 "HMAC" + key.getParameters().getHashType(), 90 new SecretKeySpec( 91 key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get()), "HMAC")); 92 // Due to the correctness checks during AesCmacKey creation, there is no need to perform 93 // additional tag size checks here. 94 tagSize = key.getParameters().getCryptographicTagSizeBytes(); 95 outputPrefix = key.getOutputPrefix().toByteArray(); 96 if (key.getParameters().getVariant().equals(HmacParameters.Variant.LEGACY)) { 97 plaintextLegacySuffix = Arrays.copyOf(FORMAT_VERSION, FORMAT_VERSION.length); 98 } else { 99 plaintextLegacySuffix = new byte[0]; 100 } 101 } 102 103 /** Creates an object implementing the {@link Mac} interface using an AesCmac underneath. */ create(AesCmacKey key)104 public static Mac create(AesCmacKey key) throws GeneralSecurityException { 105 return new PrfMac(key); 106 } 107 108 /** Creates an object implementing the {@link Mac} interface using an Hmac underneath. */ create(HmacKey key)109 public static Mac create(HmacKey key) throws GeneralSecurityException { 110 return new PrfMac(key); 111 } 112 113 @Override computeMac(byte[] data)114 public byte[] computeMac(byte[] data) throws GeneralSecurityException { 115 if (plaintextLegacySuffix.length > 0) { 116 return Bytes.concat( 117 outputPrefix, wrappedPrf.compute(Bytes.concat(data, plaintextLegacySuffix), tagSize)); 118 } 119 return Bytes.concat(outputPrefix, wrappedPrf.compute(data, tagSize)); 120 } 121 122 @Override verifyMac(byte[] mac, byte[] data)123 public void verifyMac(byte[] mac, byte[] data) throws GeneralSecurityException { 124 if (!Bytes.equal(computeMac(data), mac)) { 125 throw new GeneralSecurityException("invalid MAC"); 126 } 127 } 128 } 129