1/*
2 * Copyright 2017 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
17package org.conscrypt;
18
19import java.io.IOException;
20import java.security.AlgorithmParametersSpi;
21import java.security.spec.AlgorithmParameterSpec;
22import java.security.spec.InvalidParameterSpecException;
23import java.security.spec.MGF1ParameterSpec;
24import java.util.HashMap;
25import java.util.Map;
26import javax.crypto.spec.OAEPParameterSpec;
27import javax.crypto.spec.PSource;
28
29/**
30 * AlgorithmParameters implementation for OAEP.  The only supported encoding format is ASN.1,
31 * as specified in RFC 4055 section 4.1.
32 *
33 * @hide
34 */
35@Internal
36public class OAEPParameters extends AlgorithmParametersSpi {
37
38    private static final Map<String, String> OID_TO_NAME = new HashMap<String, String>();
39    private static final Map<String, String> NAME_TO_OID = new HashMap<String, String>();
40    static {
41        OID_TO_NAME.put("1.3.14.3.2.26", "SHA-1");
42        OID_TO_NAME.put("2.16.840.1.101.3.4.2.4", "SHA-224");
43        OID_TO_NAME.put("2.16.840.1.101.3.4.2.1", "SHA-256");
44        OID_TO_NAME.put("2.16.840.1.101.3.4.2.2", "SHA-384");
45        OID_TO_NAME.put("2.16.840.1.101.3.4.2.3", "SHA-512");
46        for (Map.Entry<String, String> entry : OID_TO_NAME.entrySet()) {
47            NAME_TO_OID.put(entry.getValue(), entry.getKey());
48        }
49    }
50    private static final String MGF1_OID = "1.2.840.113549.1.1.8";
51    private static final String PSPECIFIED_OID = "1.2.840.113549.1.1.9";
52
53    private OAEPParameterSpec spec = OAEPParameterSpec.DEFAULT;
54
55    @Override
56    protected void engineInit(AlgorithmParameterSpec algorithmParameterSpec)
57            throws InvalidParameterSpecException {
58        if (algorithmParameterSpec instanceof OAEPParameterSpec) {
59            this.spec = (OAEPParameterSpec) algorithmParameterSpec;
60        } else {
61            throw new InvalidParameterSpecException("Only OAEPParameterSpec is supported");
62        }
63    }
64
65    @Override
66    protected void engineInit(byte[] bytes) throws IOException {
67        long readRef = 0;
68        long seqRef = 0;
69        try {
70            readRef = NativeCrypto.asn1_read_init(bytes);
71            seqRef = NativeCrypto.asn1_read_sequence(readRef);
72            String hash = "SHA-1";
73            String mgfHash = "SHA-1";
74            PSource.PSpecified pSpecified = PSource.PSpecified.DEFAULT;
75            if (NativeCrypto.asn1_read_next_tag_is(seqRef, 0)) {
76                long hashRef = 0;
77                try {
78                    hashRef = NativeCrypto.asn1_read_tagged(seqRef);
79                    hash = getHashName(hashRef);
80                } finally {
81                    NativeCrypto.asn1_read_free(hashRef);
82                }
83            }
84            if (NativeCrypto.asn1_read_next_tag_is(seqRef, 1)) {
85                long mgfRef = 0;
86                long mgfSeqRef = 0;
87                try {
88                    mgfRef = NativeCrypto.asn1_read_tagged(seqRef);
89                    mgfSeqRef = NativeCrypto.asn1_read_sequence(mgfRef);
90                    String mgfOid = NativeCrypto.asn1_read_oid(mgfSeqRef);
91                    if (!mgfOid.equals(MGF1_OID)) {
92                        throw new IOException("Error reading ASN.1 encoding");
93                    }
94                    mgfHash = getHashName(mgfSeqRef);
95                    if (!NativeCrypto.asn1_read_is_empty(mgfSeqRef)) {
96                        throw new IOException("Error reading ASN.1 encoding");
97                    }
98                } finally {
99                    NativeCrypto.asn1_read_free(mgfSeqRef);
100                    NativeCrypto.asn1_read_free(mgfRef);
101                }
102            }
103            if (NativeCrypto.asn1_read_next_tag_is(seqRef, 2)) {
104                long pSourceRef = 0;
105                long pSourceSeqRef = 0;
106                try {
107                    pSourceRef = NativeCrypto.asn1_read_tagged(seqRef);
108                    pSourceSeqRef = NativeCrypto.asn1_read_sequence(pSourceRef);
109                    String pSourceOid = NativeCrypto.asn1_read_oid(pSourceSeqRef);
110                    if (!pSourceOid.equals(PSPECIFIED_OID)) {
111                        throw new IOException("Error reading ASN.1 encoding");
112                    }
113                    pSpecified = new PSource.PSpecified(
114                            NativeCrypto.asn1_read_octetstring(pSourceSeqRef));
115                    if (!NativeCrypto.asn1_read_is_empty(pSourceSeqRef)) {
116                        throw new IOException("Error reading ASN.1 encoding");
117                    }
118                } finally {
119                    NativeCrypto.asn1_read_free(pSourceSeqRef);
120                    NativeCrypto.asn1_read_free(pSourceRef);
121                }
122            }
123
124            if (!NativeCrypto.asn1_read_is_empty(seqRef)
125                    || !NativeCrypto.asn1_read_is_empty(readRef)) {
126                throw new IOException("Error reading ASN.1 encoding");
127            }
128            this.spec = new OAEPParameterSpec(hash, "MGF1", new MGF1ParameterSpec(mgfHash),
129                    pSpecified);
130        } finally {
131            NativeCrypto.asn1_read_free(seqRef);
132            NativeCrypto.asn1_read_free(readRef);
133        }
134    }
135
136    @Override
137    protected void engineInit(byte[] bytes, String format) throws IOException {
138        if ((format == null) || format.equals("ASN.1")) {
139            engineInit(bytes);
140        } else {
141            throw new IOException("Unsupported format: " + format);
142        }
143    }
144
145    private static String getHashName(long hashRef) throws IOException {
146        long hashSeqRef = 0;
147        try {
148            hashSeqRef = NativeCrypto.asn1_read_sequence(hashRef);
149            String hashOid = NativeCrypto.asn1_read_oid(hashSeqRef);
150            if (!NativeCrypto.asn1_read_is_empty(hashSeqRef)) {
151                NativeCrypto.asn1_read_null(hashSeqRef);
152            }
153            if (!NativeCrypto.asn1_read_is_empty(hashSeqRef)
154                    || !OID_TO_NAME.containsKey(hashOid)) {
155                throw new IOException("Error reading ASN.1 encoding");
156            }
157            return OID_TO_NAME.get(hashOid);
158        } finally {
159            NativeCrypto.asn1_read_free(hashSeqRef);
160        }
161    }
162
163    @Override
164    @SuppressWarnings("unchecked")
165    protected <T extends AlgorithmParameterSpec> T engineGetParameterSpec(Class<T> aClass)
166            throws InvalidParameterSpecException {
167        if ((aClass != null) && aClass == OAEPParameterSpec.class) {
168            return (T) spec;
169        } else {
170            throw new InvalidParameterSpecException("Unsupported class: " + aClass);
171        }
172    }
173
174    @Override
175    protected byte[] engineGetEncoded() throws IOException {
176        long cbbRef = 0;
177        long seqRef = 0;
178        try {
179            cbbRef = NativeCrypto.asn1_write_init();
180            seqRef = NativeCrypto.asn1_write_sequence(cbbRef);
181            // Implementations are prohibited from writing the default value for any of the fields
182            if (!spec.getDigestAlgorithm().equals("SHA-1")) {
183                long hashRef = 0;
184                long hashParamsRef = 0;
185                try {
186                    hashRef = NativeCrypto.asn1_write_tag(seqRef, 0);
187                    hashParamsRef = writeAlgorithmIdentifier(
188                            hashRef, NAME_TO_OID.get(spec.getDigestAlgorithm()));
189                    NativeCrypto.asn1_write_null(hashParamsRef);
190                } finally {
191                    NativeCrypto.asn1_write_flush(seqRef);
192                    NativeCrypto.asn1_write_free(hashParamsRef);
193                    NativeCrypto.asn1_write_free(hashRef);
194                }
195            }
196            MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) spec.getMGFParameters();
197            if (!mgfSpec.getDigestAlgorithm().equals("SHA-1")) {
198                long mgfRef = 0;
199                long mgfParamsRef = 0;
200                long hashParamsRef = 0;
201                try {
202                    mgfRef = NativeCrypto.asn1_write_tag(seqRef, 1);
203                    mgfParamsRef = writeAlgorithmIdentifier(mgfRef, MGF1_OID);
204                    hashParamsRef = writeAlgorithmIdentifier(
205                            mgfParamsRef, NAME_TO_OID.get(mgfSpec.getDigestAlgorithm()));
206                    NativeCrypto.asn1_write_null(hashParamsRef);
207                } finally {
208                    NativeCrypto.asn1_write_flush(seqRef);
209                    NativeCrypto.asn1_write_free(hashParamsRef);
210                    NativeCrypto.asn1_write_free(mgfParamsRef);
211                    NativeCrypto.asn1_write_free(mgfRef);
212                }
213            }
214            PSource.PSpecified pSource = (PSource.PSpecified) spec.getPSource();
215            if (pSource.getValue().length != 0) {
216                long pSourceRef = 0;
217                long pSourceParamsRef = 0;
218                try {
219                    pSourceRef = NativeCrypto.asn1_write_tag(seqRef, 2);
220                    pSourceParamsRef = writeAlgorithmIdentifier(pSourceRef, PSPECIFIED_OID);
221                    NativeCrypto.asn1_write_octetstring(pSourceParamsRef, pSource.getValue());
222                } finally {
223                    NativeCrypto.asn1_write_flush(seqRef);
224                    NativeCrypto.asn1_write_free(pSourceParamsRef);
225                    NativeCrypto.asn1_write_free(pSourceRef);
226                }
227            }
228            return NativeCrypto.asn1_write_finish(cbbRef);
229        } catch (IOException e) {
230            NativeCrypto.asn1_write_cleanup(cbbRef);
231            throw e;
232        } finally {
233            NativeCrypto.asn1_write_free(seqRef);
234            NativeCrypto.asn1_write_free(cbbRef);
235        }
236    }
237
238    @Override
239    protected byte[] engineGetEncoded(String format) throws IOException {
240        if ((format == null) || format.equals("ASN.1")) {
241            return engineGetEncoded();
242        }
243        throw new IOException("Unsupported format: " + format);
244    }
245
246    /**
247     * Writes an ASN.1 AlgorithmIdentifier structure into container, which looks like
248     * <pre>
249     * SEQUENCE
250     *   OBJECT IDENTIFIER
251     *   PARAMS (based on the particular algorithm)
252     * </pre>
253     * This method returns a reference to the sequence such that the params may be added to it.
254     * The reference needs to be freed with asn1_write_free once it's used.
255     */
256    private static long writeAlgorithmIdentifier(long container, String oid) throws IOException {
257        long seqRef = 0;
258        try {
259            seqRef = NativeCrypto.asn1_write_sequence(container);
260            NativeCrypto.asn1_write_oid(seqRef, oid);
261        } catch (IOException e) {
262            NativeCrypto.asn1_write_free(seqRef);
263            throw e;
264        }
265        return seqRef;
266    }
267
268    @Override
269    protected String engineToString() {
270        return "Conscrypt OAEP AlgorithmParameters";
271    }
272}
273