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