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