Home | History | Annotate | Download | only in spec
      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