• 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.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertThrows;
21 
22 import com.google.crypto.tink.PublicKeyVerify;
23 import com.google.crypto.tink.internal.Util;
24 import com.google.crypto.tink.signature.RsaSsaPssParameters;
25 import com.google.crypto.tink.signature.RsaSsaPssPublicKey;
26 import com.google.crypto.tink.signature.internal.testing.RsaSsaPssTestUtil;
27 import com.google.crypto.tink.signature.internal.testing.SignatureTestVector;
28 import com.google.crypto.tink.subtle.Enums.HashType;
29 import com.google.crypto.tink.testing.WycheproofTestUtil;
30 import com.google.gson.JsonArray;
31 import com.google.gson.JsonObject;
32 import java.math.BigInteger;
33 import java.security.GeneralSecurityException;
34 import java.security.KeyFactory;
35 import java.security.Provider;
36 import java.security.Security;
37 import java.security.interfaces.RSAPublicKey;
38 import java.security.spec.RSAPublicKeySpec;
39 import java.util.Arrays;
40 import org.conscrypt.Conscrypt;
41 import org.junit.Assume;
42 import org.junit.Test;
43 import org.junit.experimental.theories.DataPoints;
44 import org.junit.experimental.theories.FromDataPoints;
45 import org.junit.experimental.theories.Theories;
46 import org.junit.experimental.theories.Theory;
47 import org.junit.runner.RunWith;
48 
49 /** Unit tests for RsaSsaPssVerifyJce. */
50 @RunWith(Theories.class)
51 public class RsaSsaPssVerifyJceTest {
52 
53   @DataPoints("testVectors")
54   public static final SignatureTestVector[] TEST_VECTORS =
55       RsaSsaPssTestUtil.createRsaPssTestVectors();
56 
57   @Theory
verifySignatureInTestVector_works( @romDataPoints"testVectors") SignatureTestVector testVector)58   public void verifySignatureInTestVector_works(
59       @FromDataPoints("testVectors") SignatureTestVector testVector) throws Exception {
60     PublicKeyVerify verifier =
61         RsaSsaPssVerifyJce.create((RsaSsaPssPublicKey) testVector.getPrivateKey().getPublicKey());
62     verifier.verify(testVector.getSignature(), testVector.getMessage());
63 
64     // Test that verify fails when message is modified.
65     byte[] modifiedMessage = Bytes.concat(testVector.getMessage(), new byte[] {1});
66     assertThrows(
67         GeneralSecurityException.class,
68         () -> verifier.verify(testVector.getSignature(), modifiedMessage));
69   }
70 
toEnumHashType(RsaSsaPssParameters.HashType hash)71   private static HashType toEnumHashType(RsaSsaPssParameters.HashType hash) {
72     if (hash == RsaSsaPssParameters.HashType.SHA256) {
73       return HashType.SHA256;
74     } else if (hash == RsaSsaPssParameters.HashType.SHA384) {
75       return HashType.SHA384;
76     } else if (hash == RsaSsaPssParameters.HashType.SHA512) {
77       return HashType.SHA512;
78     } else {
79       throw new IllegalArgumentException("Unsupported hash: " + hash);
80     }
81   }
82 
83   @Theory
constructor_verifySignatureInTestVector_works( @romDataPoints"testVectors") SignatureTestVector testVector)84   public void constructor_verifySignatureInTestVector_works(
85       @FromDataPoints("testVectors") SignatureTestVector testVector) throws Exception {
86     RsaSsaPssPublicKey testPublicKey =
87         (RsaSsaPssPublicKey) testVector.getPrivateKey().getPublicKey();
88     if (!testPublicKey.getParameters().getVariant().equals(RsaSsaPssParameters.Variant.NO_PREFIX)) {
89       // Constructor doesn't support output prefix.
90       return;
91     }
92     KeyFactory keyFactory = EngineFactory.KEY_FACTORY.getInstance("RSA");
93     RSAPublicKey rsaPublicKey =
94         (RSAPublicKey)
95             keyFactory.generatePublic(
96                 new RSAPublicKeySpec(
97                     testPublicKey.getModulus(), testPublicKey.getParameters().getPublicExponent()));
98 
99     RsaSsaPssVerifyJce verify =
100         new RsaSsaPssVerifyJce(
101             rsaPublicKey,
102             toEnumHashType(testPublicKey.getParameters().getSigHashType()),
103             toEnumHashType(testPublicKey.getParameters().getMgf1HashType()),
104             testPublicKey.getParameters().getSaltLengthBytes());
105     verify.verify(testVector.getSignature(), testVector.getMessage());
106 
107     // Test that verify fails when message is modified.
108     byte[] modifiedMessage = Bytes.concat(testVector.getMessage(), new byte[] {1});
109     assertThrows(
110         GeneralSecurityException.class,
111         () -> verify.verify(testVector.getSignature(), modifiedMessage));
112   }
113 
114   @Test
constructorValidatesHashType()115   public void constructorValidatesHashType() throws Exception {
116     SignatureTestVector testVector = TEST_VECTORS[0];
117     RsaSsaPssPublicKey testPublicKey =
118         (RsaSsaPssPublicKey) testVector.getPrivateKey().getPublicKey();
119     KeyFactory keyFactory = EngineFactory.KEY_FACTORY.getInstance("RSA");
120     RSAPublicKey rsaPublicKey =
121         (RSAPublicKey)
122             keyFactory.generatePublic(
123                 new RSAPublicKeySpec(
124                     testPublicKey.getModulus(), testPublicKey.getParameters().getPublicExponent()));
125 
126     assertThrows(
127         GeneralSecurityException.class,
128         () -> new RsaSsaPssVerifyJce(rsaPublicKey, HashType.SHA1, HashType.SHA1, 20));
129     assertThrows(
130         GeneralSecurityException.class,
131         () -> new RsaSsaPssVerifyJce(rsaPublicKey, HashType.SHA256, HashType.SHA1, 32));
132     assertThrows(
133         GeneralSecurityException.class,
134         () -> new RsaSsaPssVerifyJce(rsaPublicKey, HashType.SHA256, HashType.SHA384, 32));
135   }
136 
137   @Theory
modifiedOutputPrefix_throws( @romDataPoints"testVectors") SignatureTestVector testVector)138   public void modifiedOutputPrefix_throws(
139       @FromDataPoints("testVectors") SignatureTestVector testVector) throws Exception {
140     RsaSsaPssPublicKey testPublicKey =
141         (RsaSsaPssPublicKey) testVector.getPrivateKey().getPublicKey();
142     if (testPublicKey.getOutputPrefix().size() == 0) {
143       return;
144     }
145     byte[] modifiedSignature = testVector.getSignature();
146     modifiedSignature[1] ^= 0x01;
147     PublicKeyVerify verifier = RsaSsaPssVerifyJce.create(testPublicKey);
148     assertThrows(
149         GeneralSecurityException.class,
150         () ->
151             verifier.verify(
152                 Arrays.copyOf(modifiedSignature, modifiedSignature.length),
153                 testVector.getMessage()));
154   }
155 
getHashType(String sha)156   private static RsaSsaPssParameters.HashType getHashType(String sha) {
157     switch (sha) {
158       case "SHA-256":
159         return RsaSsaPssParameters.HashType.SHA256;
160       case "SHA-512":
161         return RsaSsaPssParameters.HashType.SHA512;
162       default:
163         throw new IllegalArgumentException("Unsupported hash: " + sha);
164     }
165   }
166 
167   @DataPoints("wycheproofTestVectorPaths")
168   public static final String[] WYCHEPROOF_TEST_VECTORS_PATHS =
169       new String[] {
170         "../wycheproof/testvectors/rsa_pss_2048_sha256_mgf1_0_test.json",
171         "../wycheproof/testvectors/rsa_pss_2048_sha256_mgf1_32_test.json",
172         "../wycheproof/testvectors/rsa_pss_3072_sha256_mgf1_32_test.json",
173         "../wycheproof/testvectors/rsa_pss_4096_sha256_mgf1_32_test.json",
174         "../wycheproof/testvectors/rsa_pss_4096_sha512_mgf1_32_test.json"
175       };
176 
177   @Theory
wycheproofVectors(@romDataPoints"wycheproofTestVectorPaths") String path)178   public void wycheproofVectors(@FromDataPoints("wycheproofTestVectorPaths") String path)
179       throws Exception {
180     JsonObject jsonObj = WycheproofTestUtil.readJson(path);
181 
182     int errors = 0;
183     JsonArray testGroups = jsonObj.getAsJsonArray("testGroups");
184     for (int i = 0; i < testGroups.size(); i++) {
185       JsonObject group = testGroups.get(i).getAsJsonObject();
186       BigInteger modulus = new BigInteger(group.get("n").getAsString(), 16);
187       BigInteger exponent = new BigInteger(group.get("e").getAsString(), 16);
188       RsaSsaPssParameters.HashType hashType = getHashType(group.get("sha").getAsString());
189       RsaSsaPssParameters.HashType mgf1HashType = getHashType(group.get("mgfSha").getAsString());
190       int saltLength = group.get("sLen").getAsInt();
191 
192       JsonArray tests = group.getAsJsonArray("tests");
193       for (int j = 0; j < tests.size(); j++) {
194         JsonObject testcase = tests.get(j).getAsJsonObject();
195         String tcId =
196             String.format(
197                 "testcase %d (%s)",
198                 testcase.get("tcId").getAsInt(), testcase.get("comment").getAsString());
199         RsaSsaPssParameters parameters =
200             RsaSsaPssParameters.builder()
201                 .setModulusSizeBits(modulus.bitLength())
202                 .setPublicExponent(exponent)
203                 .setSigHashType(hashType)
204                 .setMgf1HashType(mgf1HashType)
205                 .setSaltLengthBytes(saltLength)
206                 .setVariant(RsaSsaPssParameters.Variant.NO_PREFIX)
207                 .build();
208         RsaSsaPssPublicKey publicKey =
209             RsaSsaPssPublicKey.builder().setParameters(parameters).setModulus(modulus).build();
210         PublicKeyVerify verifier = RsaSsaPssVerifyJce.create(publicKey);
211         byte[] msg = getMessage(testcase);
212         byte[] sig = Hex.decode(testcase.get("sig").getAsString());
213         String result = testcase.get("result").getAsString();
214         try {
215           verifier.verify(sig, msg);
216           if (result.equals("invalid")) {
217             System.out.printf("FAIL %s: accepting invalid signature%n", tcId);
218             errors++;
219           }
220         } catch (GeneralSecurityException ex) {
221           if (result.equals("valid")) {
222             System.out.printf("FAIL %s: rejecting valid signature, exception: %s%n", tcId, ex);
223             errors++;
224           }
225         }
226       }
227     }
228     assertThat(errors).isEqualTo(0);
229   }
230 
getMessage(JsonObject testcase)231   private static byte[] getMessage(JsonObject testcase) {
232     // Previous version of Wycheproof test vectors uses "message" while the new one uses "msg".
233     if (testcase.has("msg")) {
234       return Hex.decode(testcase.get("msg").getAsString());
235     } else {
236       return Hex.decode(testcase.get("message").getAsString());
237     }
238   }
239 
240   @Test
usesConscryptImplementationIfInstalled()241   public void usesConscryptImplementationIfInstalled() throws Exception {
242     Assume.assumeFalse(Util.isAndroid());
243 
244     SignatureTestVector testVector = TEST_VECTORS[0];
245     RsaSsaPssPublicKey testPublicKey =
246         (RsaSsaPssPublicKey) testVector.getPrivateKey().getPublicKey();
247 
248     PublicKeyVerify verifier = RsaSsaPssVerifyJce.create(testPublicKey);
249     assertThat(verifier.getClass().getSimpleName()).isEqualTo("InternalImpl");
250 
251     Provider conscrypt = Conscrypt.newProvider();
252     Security.addProvider(conscrypt);
253 
254     PublicKeyVerify verifier2 = RsaSsaPssVerifyJce.create(testPublicKey);
255     assertThat(verifier2.getClass().getSimpleName()).isEqualTo("RsaSsaPssVerifyConscrypt");
256 
257     Security.removeProvider(conscrypt.getName());
258   }
259 }
260