• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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