• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 Google Inc.
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 static com.google.crypto.tink.internal.Util.isPrefix;
20 
21 import com.google.crypto.tink.AccessesPartialKey;
22 import com.google.crypto.tink.PublicKeyVerify;
23 import com.google.crypto.tink.config.internal.TinkFipsUtil;
24 import com.google.crypto.tink.internal.EnumTypeProtoConverter;
25 import com.google.crypto.tink.signature.RsaSsaPssParameters;
26 import com.google.crypto.tink.signature.RsaSsaPssPublicKey;
27 import com.google.crypto.tink.signature.internal.RsaSsaPssVerifyConscrypt;
28 import com.google.crypto.tink.subtle.Enums.HashType;
29 import com.google.errorprone.annotations.Immutable;
30 import java.math.BigInteger;
31 import java.security.GeneralSecurityException;
32 import java.security.KeyFactory;
33 import java.security.MessageDigest;
34 import java.security.NoSuchProviderException;
35 import java.security.interfaces.RSAPublicKey;
36 import java.security.spec.RSAPublicKeySpec;
37 import java.util.Arrays;
38 
39 /**
40  * RsaSsaPss (i.e. RSA Signature Schemes with Appendix (SSA) using PSS encoding) verifying with JCE.
41  */
42 @Immutable
43 public final class RsaSsaPssVerifyJce implements PublicKeyVerify {
44   public static final TinkFipsUtil.AlgorithmFipsCompatibility FIPS =
45       TinkFipsUtil.AlgorithmFipsCompatibility.ALGORITHM_REQUIRES_BORINGCRYPTO;
46 
47   // This converter is not used with a proto but rather with an ordinary enum type.
48   static final EnumTypeProtoConverter<HashType, RsaSsaPssParameters.HashType> HASH_TYPE_CONVERTER =
49       EnumTypeProtoConverter.<HashType, RsaSsaPssParameters.HashType>builder()
50           .add(HashType.SHA256, RsaSsaPssParameters.HashType.SHA256)
51           .add(HashType.SHA384, RsaSsaPssParameters.HashType.SHA384)
52           .add(HashType.SHA512, RsaSsaPssParameters.HashType.SHA512)
53           .build();
54 
55   private static final byte[] EMPTY = new byte[0];
56   private static final byte[] LEGACY_MESSAGE_SUFFIX = new byte[] {0};
57 
58   /**
59    * InternalImpl is an implementation of the RSA SSA PSS signature signing that only uses the JCE
60    * for raw RSA operations. The rest of the algorithm is implemented in Java. This allows it to be
61    * used on most Java platforms.
62    */
63   private static final class InternalImpl implements PublicKeyVerify {
64 
65     @SuppressWarnings("Immutable")
66     private final RSAPublicKey publicKey;
67 
68     private final HashType sigHash;
69     private final HashType mgf1Hash;
70     private final int saltLength;
71 
72     @SuppressWarnings("Immutable")
73     private final byte[] outputPrefix;
74 
75     @SuppressWarnings("Immutable")
76     private final byte[] messageSuffix;
77 
InternalImpl( final RSAPublicKey pubKey, HashType sigHash, HashType mgf1Hash, int saltLength, byte[] outputPrefix, byte[] messageSuffix)78     private InternalImpl(
79         final RSAPublicKey pubKey,
80         HashType sigHash,
81         HashType mgf1Hash,
82         int saltLength,
83         byte[] outputPrefix,
84         byte[] messageSuffix)
85         throws GeneralSecurityException {
86       if (TinkFipsUtil.useOnlyFips()) {
87         throw new GeneralSecurityException(
88             "Can not use RSA PSS in FIPS-mode, as BoringCrypto module is not available.");
89       }
90 
91       Validators.validateSignatureHash(sigHash);
92       if (!sigHash.equals(mgf1Hash)) {
93         throw new GeneralSecurityException("sigHash and mgf1Hash must be the same");
94       }
95       Validators.validateRsaModulusSize(pubKey.getModulus().bitLength());
96       Validators.validateRsaPublicExponent(pubKey.getPublicExponent());
97       this.publicKey = pubKey;
98       this.sigHash = sigHash;
99       this.mgf1Hash = mgf1Hash;
100       this.saltLength = saltLength;
101       this.outputPrefix = outputPrefix;
102       this.messageSuffix = messageSuffix;
103     }
104 
noPrefixVerify(final byte[] signature, final byte[] data)105     private void noPrefixVerify(final byte[] signature, final byte[] data)
106         throws GeneralSecurityException {
107       // The algorithm is described at (https://tools.ietf.org/html/rfc8017#section-8.1.2). As
108       // signature verification is a public operation,  throwing different exception messages
109       // doesn't
110       // give attacker any useful information.
111       BigInteger e = publicKey.getPublicExponent();
112       BigInteger n = publicKey.getModulus();
113       int nLengthInBytes = (n.bitLength() + 7) / 8;
114       int mLen = (n.bitLength() - 1 + 7) / 8;
115 
116       // Step 1. Length checking.
117       if (nLengthInBytes != signature.length) {
118         throw new GeneralSecurityException("invalid signature's length");
119       }
120 
121       // Step 2. RSA verification.
122       BigInteger s = SubtleUtil.bytes2Integer(signature);
123       if (s.compareTo(n) >= 0) {
124         throw new GeneralSecurityException("signature out of range");
125       }
126       BigInteger m = s.modPow(e, n);
127       byte[] em = SubtleUtil.integer2Bytes(m, mLen);
128 
129       // Step 3. PSS encoding verification.
130       emsaPssVerify(data, em, n.bitLength() - 1);
131     }
132 
133     // https://tools.ietf.org/html/rfc8017#section-9.1.2.
emsaPssVerify(byte[] message, byte[] em, int emBits)134     private void emsaPssVerify(byte[] message, byte[] em, int emBits)
135         throws GeneralSecurityException {
136       // Step 1. Length checking.
137       // This step is unnecessary because Java's byte[] only supports up to 2^31 -1 bytes while the
138       // input limitation for the hash function is far larger (2^61 - 1 for SHA-1).
139 
140       // Step 2. Compute hash.
141       Validators.validateSignatureHash(sigHash);
142       MessageDigest digest =
143           EngineFactory.MESSAGE_DIGEST.getInstance(SubtleUtil.toDigestAlgo(this.sigHash));
144       // M = concat(message, messageSuffix)
145       digest.update(message);
146       if (messageSuffix.length != 0) {
147         digest.update(messageSuffix);
148       }
149       byte[] mHash = digest.digest();
150       int hLen = digest.getDigestLength();
151 
152       int emLen = em.length;
153 
154       // Step 3. Check emLen.
155       if (emLen < hLen + this.saltLength + 2) {
156         throw new GeneralSecurityException("inconsistent");
157       }
158 
159       // Step 4. Check right most byte of EM.
160       if (em[em.length - 1] != (byte) 0xbc) {
161         throw new GeneralSecurityException("inconsistent");
162       }
163 
164       // Step 5. Extract maskedDb and H from EM.
165       byte[] maskedDb = Arrays.copyOf(em, emLen - hLen - 1);
166       byte[] h = Arrays.copyOfRange(em, maskedDb.length, maskedDb.length + hLen);
167 
168       // Step 6. Check whether the leftmost 8 * emLen - emBits bits of the leftmost octet in
169       // maskedDB
170       // are all zeros.
171       for (int i = 0; i < (long) emLen * 8 - emBits; i++) {
172         int bytePos = i / 8;
173         int bitPos = 7 - i % 8;
174         if (((maskedDb[bytePos] >> bitPos) & 1) != 0) {
175           throw new GeneralSecurityException("inconsistent");
176         }
177       }
178 
179       // Step 7. Compute dbMask.
180       byte[] dbMask = SubtleUtil.mgf1(h, emLen - hLen - 1, mgf1Hash);
181 
182       // Step 8. Compute db.
183       byte[] db = new byte[dbMask.length];
184       for (int i = 0; i < db.length; i++) {
185         db[i] = (byte) (dbMask[i] ^ maskedDb[i]);
186       }
187 
188       // Step 9. Set the leftmost 8*emLen - emBits bits of the leftmost octet in DB to zero.
189       for (int i = 0; i <= (long) emLen * 8 - emBits; i++) {
190         int bytePos = i / 8;
191         int bitPos = 7 - i % 8;
192         db[bytePos] = (byte) (db[bytePos] & ~(1 << bitPos));
193       }
194 
195       // Step 10. Check db.
196       for (int i = 0; i < emLen - hLen - this.saltLength - 2; i++) {
197         if (db[i] != 0) {
198           throw new GeneralSecurityException("inconsistent");
199         }
200       }
201       if (db[emLen - hLen - this.saltLength - 2] != (byte) 0x01) {
202         throw new GeneralSecurityException("inconsistent");
203       }
204 
205       // Step 11. Extract salt from db.
206       byte[] salt = Arrays.copyOfRange(db, db.length - this.saltLength, db.length);
207 
208       // Step 12. Generate M'.
209       byte[] mPrime = new byte[8 + hLen + this.saltLength];
210       System.arraycopy(mHash, 0, mPrime, 8, mHash.length);
211       System.arraycopy(salt, 0, mPrime, 8 + hLen, salt.length);
212 
213       // Step 13. Compute H'
214       byte[] hPrime = digest.digest(mPrime);
215       if (!Bytes.equal(hPrime, h)) {
216         throw new GeneralSecurityException("inconsistent");
217       }
218     }
219 
220     @Override
verify(final byte[] signature, final byte[] data)221     public void verify(final byte[] signature, final byte[] data) throws GeneralSecurityException {
222       if (outputPrefix.length == 0) {
223         noPrefixVerify(signature, data);
224         return;
225       }
226       if (!isPrefix(outputPrefix, signature)) {
227         throw new GeneralSecurityException("Invalid signature (output prefix mismatch)");
228       }
229       byte[] signatureNoPrefix =
230           Arrays.copyOfRange(signature, outputPrefix.length, signature.length);
231       noPrefixVerify(signatureNoPrefix, data);
232     }
233   }
234 
235   @SuppressWarnings("Immutable")
236   private final PublicKeyVerify verify;
237 
238   @AccessesPartialKey
create(RsaSsaPssPublicKey key)239   public static PublicKeyVerify create(RsaSsaPssPublicKey key) throws GeneralSecurityException {
240     try {
241       return RsaSsaPssVerifyConscrypt.create(key);
242     } catch (NoSuchProviderException e) {
243       // Ignore, and fall back to the Java implementation.
244     }
245     KeyFactory keyFactory = EngineFactory.KEY_FACTORY.getInstance("RSA");
246     RSAPublicKey publicKey =
247         (RSAPublicKey)
248             keyFactory.generatePublic(
249                 new RSAPublicKeySpec(key.getModulus(), key.getParameters().getPublicExponent()));
250     RsaSsaPssParameters params = key.getParameters();
251     return new InternalImpl(
252         publicKey,
253         HASH_TYPE_CONVERTER.toProtoEnum(params.getSigHashType()),
254         HASH_TYPE_CONVERTER.toProtoEnum(params.getMgf1HashType()),
255         params.getSaltLengthBytes(),
256         key.getOutputPrefix().toByteArray(),
257         key.getParameters().getVariant().equals(RsaSsaPssParameters.Variant.LEGACY)
258             ? LEGACY_MESSAGE_SUFFIX
259             : EMPTY);
260   }
261 
getHashType(HashType hash)262   private static RsaSsaPssParameters.HashType getHashType(HashType hash)
263       throws GeneralSecurityException {
264     switch (hash) {
265       case SHA256:
266         return RsaSsaPssParameters.HashType.SHA256;
267       case SHA384:
268         return RsaSsaPssParameters.HashType.SHA384;
269       case SHA512:
270         return RsaSsaPssParameters.HashType.SHA512;
271       default:
272         throw new GeneralSecurityException("Unsupported hash: " + hash);
273     }
274   }
275 
276   @AccessesPartialKey
convertKey( final RSAPublicKey pubKey, HashType sigHash, HashType mgf1Hash, int saltLength)277   private RsaSsaPssPublicKey convertKey(
278       final RSAPublicKey pubKey, HashType sigHash, HashType mgf1Hash, int saltLength)
279       throws GeneralSecurityException {
280     RsaSsaPssParameters parameters =
281         RsaSsaPssParameters.builder()
282             .setModulusSizeBits(pubKey.getModulus().bitLength())
283             .setPublicExponent(pubKey.getPublicExponent())
284             .setSigHashType(getHashType(sigHash))
285             .setMgf1HashType(getHashType(mgf1Hash))
286             .setSaltLengthBytes(saltLength)
287             .setVariant(RsaSsaPssParameters.Variant.NO_PREFIX)
288             .build();
289     return RsaSsaPssPublicKey.builder()
290         .setParameters(parameters)
291         .setModulus(pubKey.getModulus())
292         .build();
293   }
294 
RsaSsaPssVerifyJce( final RSAPublicKey pubKey, HashType sigHash, HashType mgf1Hash, int saltLength)295   public RsaSsaPssVerifyJce(
296       final RSAPublicKey pubKey, HashType sigHash, HashType mgf1Hash, int saltLength)
297       throws GeneralSecurityException {
298     this.verify = create(convertKey(pubKey, sigHash, mgf1Hash, saltLength));
299   }
300 
301   @Override
verify(final byte[] signature, final byte[] data)302   public void verify(final byte[] signature, final byte[] data) throws GeneralSecurityException {
303     verify.verify(signature, data);
304   }
305 }
306