1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package libcore.java.security.spec; 18 19 import java.io.IOException; 20 import java.security.AlgorithmParameters; 21 import java.security.spec.AlgorithmParameterSpec; 22 import java.security.spec.InvalidParameterSpecException; 23 import java.security.spec.MGF1ParameterSpec; 24 import java.security.spec.PSSParameterSpec; 25 import java.util.Arrays; 26 import java.util.Locale; 27 import java.util.Map; 28 import java.util.TreeMap; 29 30 import junit.framework.TestCase; 31 32 import libcore.util.HexEncoding; 33 34 public class AlgorithmParametersPSSTest extends TestCase { 35 36 // ASN.1 DER-encoded forms of DEFAULT_SPEC, WEIRD_SPEC, and WEIRD2_SPEC were generated using 37 // Bouncy Castle 1.52 AlgorithmParameters of type "PSS" and checked for correctness using ASN.1 38 // DER decoder. 39 private static final PSSParameterSpec DEFAULT_SPEC = PSSParameterSpec.DEFAULT; 40 private static final byte[] DEFAULT_SPEC_DER_ENCODED = HexEncoding.decode("3000"); 41 42 private static final PSSParameterSpec WEIRD_SPEC = 43 new PSSParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA384, 27, 3); 44 private static final byte[] WEIRD_SPEC_DER_ENCODED = 45 HexEncoding.decode( 46 "3039a00f300d06096086480165030402030500a11c301a06092a864886f70d010108300d060960" 47 + "86480165030402020500a20302011ba303020103"); 48 49 private static final PSSParameterSpec WEIRD2_SPEC = 50 new PSSParameterSpec("SHA-224", "MGF1", MGF1ParameterSpec.SHA256, 32, 1); 51 private static final byte[] WEIRD2_SPEC_DER_ENCODED = 52 HexEncoding.decode( 53 "3034a00f300d06096086480165030402040500a11c301a06092a864886f70d010108300d060960" 54 + "86480165030402010500a203020120"); 55 56 /** Truncated SEQUENCE (one more byte needed at the end) */ 57 private static final byte[] BROKEN_SPEC1_DER_ENCODED = 58 HexEncoding.decode( 59 "303aa00f300d06096086480165030402030500a11c301a06092a864886f70d010108300d060960" 60 + "86480165030402020500a20302011ba303020103"); 61 62 /** Payload of SEQUENCE extends beyond the SEQUENCE. */ 63 private static final byte[] BROKEN_SPEC2_DER_ENCODED = 64 HexEncoding.decode( 65 "3037a00f300d06096086480165030402030500a11c301a06092a864886f70d010108300d060960" 66 + "86480165030402020500a20302011ba303020103"); 67 68 public void testGetInstance() throws Exception { 69 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 70 assertNotNull(params); 71 } 72 73 public void testGetAlgorithm() throws Exception { 74 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 75 assertEquals("PSS", params.getAlgorithm()); 76 } 77 78 public void testGetProvider() throws Exception { 79 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 80 params.init(DEFAULT_SPEC); 81 assertNotNull(params.getProvider()); 82 } 83 84 public void testInitFailsWhenAlreadyInitialized() throws Exception { 85 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 86 params.init(DEFAULT_SPEC); 87 try { 88 params.init(DEFAULT_SPEC); 89 fail(); 90 } catch (InvalidParameterSpecException expected) {} 91 try { 92 params.init(DEFAULT_SPEC_DER_ENCODED); 93 fail(); 94 } catch (IOException expected) {} 95 try { 96 params.init(DEFAULT_SPEC_DER_ENCODED, "ASN.1"); 97 fail(); 98 } catch (IOException expected) {} 99 } 100 101 public void testInitWithPSSParameterSpec() throws Exception { 102 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 103 params.init(WEIRD_SPEC); 104 assertPSSParameterSpecEquals(WEIRD_SPEC, params.getParameterSpec(PSSParameterSpec.class)); 105 } 106 107 public void testInitWithDerEncoded() throws Exception { 108 assertInitWithDerEncoded(WEIRD_SPEC_DER_ENCODED, WEIRD_SPEC); 109 assertInitWithDerEncoded(DEFAULT_SPEC_DER_ENCODED, DEFAULT_SPEC); 110 assertInitWithDerEncoded(WEIRD2_SPEC_DER_ENCODED, WEIRD2_SPEC); 111 } 112 113 private void assertInitWithDerEncoded( 114 byte[] encoded, PSSParameterSpec expected) throws Exception { 115 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 116 params.init(encoded); 117 assertPSSParameterSpecEquals(expected, params.getParameterSpec(PSSParameterSpec.class)); 118 119 params = AlgorithmParameters.getInstance("PSS"); 120 params.init(encoded, "ASN.1"); 121 assertPSSParameterSpecEquals(expected, params.getParameterSpec(PSSParameterSpec.class)); 122 } 123 124 public void testGetEncodedThrowsWhenNotInitialized() throws Exception { 125 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 126 try { 127 params.getEncoded(); 128 fail(); 129 } catch (IOException expected) {} 130 try { 131 params.getEncoded("ASN.1"); 132 fail(); 133 } catch (IOException expected) {} 134 } 135 136 public void testGetEncoded() throws Exception { 137 assertGetEncoded(WEIRD_SPEC, WEIRD_SPEC_DER_ENCODED); 138 assertGetEncoded(DEFAULT_SPEC, DEFAULT_SPEC_DER_ENCODED); 139 assertGetEncoded(WEIRD2_SPEC, WEIRD2_SPEC_DER_ENCODED); 140 } 141 142 private void assertGetEncoded(PSSParameterSpec spec, byte[] expectedEncoded) throws Exception { 143 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 144 params.init(spec); 145 byte[] encoded = params.getEncoded("ASN.1"); 146 assertTrue(Arrays.equals(expectedEncoded, encoded)); 147 // Assert that getEncoded() returns ASN.1 form. 148 assertTrue(Arrays.equals(encoded, params.getEncoded())); 149 } 150 151 public void testGetEncodedWithBrokenInput() throws Exception { 152 AlgorithmParameters params = AlgorithmParameters.getInstance("PSS"); 153 try { 154 params.init(BROKEN_SPEC1_DER_ENCODED); 155 fail(); 156 } catch (IOException expected) { 157 } catch (IllegalArgumentException expected) { 158 // Bouncy Castle incorrectly throws an IllegalArgumentException instead of IOException. 159 if (!"BC".equals(params.getProvider().getName())) { 160 throw new RuntimeException( 161 "Unexpected exception. Provider: " + params.getProvider(), 162 expected); 163 } 164 } 165 166 params = AlgorithmParameters.getInstance("PSS"); 167 try { 168 params.init(BROKEN_SPEC2_DER_ENCODED); 169 fail(); 170 } catch (IOException expected) { 171 } catch (IllegalArgumentException expected) { 172 // Bouncy Castle incorrectly throws an IllegalArgumentException instead of IOException. 173 if (!"BC".equals(params.getProvider().getName())) { 174 throw new RuntimeException( 175 "Unexpected exception. Provider: " + params.getProvider(), 176 expected); 177 } 178 } 179 } 180 181 private static void assertPSSParameterSpecEquals( 182 PSSParameterSpec spec1, PSSParameterSpec spec2) { 183 assertEquals( 184 getDigestAlgorithmCanonicalName(spec1.getDigestAlgorithm()), 185 getDigestAlgorithmCanonicalName(spec2.getDigestAlgorithm())); 186 assertEquals( 187 getMGFAlgorithmCanonicalName(spec1.getMGFAlgorithm()), 188 getMGFAlgorithmCanonicalName(spec2.getMGFAlgorithm())); 189 190 AlgorithmParameterSpec spec1MgfParams = spec1.getMGFParameters(); 191 assertNotNull(spec1MgfParams); 192 if (!(spec1MgfParams instanceof MGF1ParameterSpec)) { 193 fail("Unexpected type of MGF parameters: " + spec1MgfParams.getClass().getName()); 194 } 195 MGF1ParameterSpec spec1Mgf1Params = (MGF1ParameterSpec) spec1MgfParams; 196 AlgorithmParameterSpec spec2MgfParams = spec2.getMGFParameters(); 197 assertNotNull(spec2MgfParams); 198 if (!(spec2MgfParams instanceof MGF1ParameterSpec)) { 199 fail("Unexpected type of MGF parameters: " + spec2MgfParams.getClass().getName()); 200 } 201 MGF1ParameterSpec spec2Mgf1Params = (MGF1ParameterSpec) spec2MgfParams; 202 203 assertEquals( 204 getDigestAlgorithmCanonicalName(spec1Mgf1Params.getDigestAlgorithm()), 205 getDigestAlgorithmCanonicalName(spec2Mgf1Params.getDigestAlgorithm())); 206 assertEquals(spec1.getSaltLength(), spec2.getSaltLength()); 207 assertEquals(spec1.getTrailerField(), spec2.getTrailerField()); 208 } 209 210 // All the craziness with supporting OIDs is needed because Bouncy Castle, when parsing from 211 // ASN.1 form, returns PSSParameterSpec instances which use OIDs instead of JCA standard names 212 // for digest algorithms and MGF algorithms. 213 private static final Map<String, String> DIGEST_OID_TO_NAME = new TreeMap<String, String>( 214 String.CASE_INSENSITIVE_ORDER); 215 private static final Map<String, String> DIGEST_NAME_TO_OID = new TreeMap<String, String>( 216 String.CASE_INSENSITIVE_ORDER); 217 218 private static void addDigestOid(String algorithm, String oid) { 219 DIGEST_OID_TO_NAME.put(oid, algorithm); 220 DIGEST_NAME_TO_OID.put(algorithm, oid); 221 } 222 223 static { 224 addDigestOid("SHA-1", "1.3.14.3.2.26"); 225 addDigestOid("SHA-224", "2.16.840.1.101.3.4.2.4"); 226 addDigestOid("SHA-256", "2.16.840.1.101.3.4.2.1"); 227 addDigestOid("SHA-384", "2.16.840.1.101.3.4.2.2"); 228 addDigestOid("SHA-512", "2.16.840.1.101.3.4.2.3"); 229 } 230 231 private static String getDigestAlgorithmCanonicalName(String algorithm) { 232 if (DIGEST_NAME_TO_OID.containsKey(algorithm)) { 233 return algorithm.toUpperCase(Locale.US); 234 } 235 236 String nameByOid = DIGEST_OID_TO_NAME.get(algorithm); 237 if (nameByOid != null) { 238 return nameByOid; 239 } 240 241 return algorithm; 242 } 243 244 private static String getMGFAlgorithmCanonicalName(String name) { 245 if ("MGF1".equalsIgnoreCase(name)) { 246 return name.toUpperCase(Locale.US); 247 } else if ("1.2.840.113549.1.1.8".equals(name)) { 248 return "MGF1"; 249 } else { 250 return name; 251 } 252 } 253 } 254