1/* 2 * Copyright (C) 2012 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.security.AlgorithmParameters; 20import java.security.InvalidAlgorithmParameterException; 21import java.security.InvalidKeyException; 22import java.security.InvalidParameterException; 23import java.security.Key; 24import java.security.KeyFactory; 25import java.security.NoSuchAlgorithmException; 26import java.security.SecureRandom; 27import java.security.SignatureException; 28import java.security.interfaces.RSAPrivateCrtKey; 29import java.security.interfaces.RSAPrivateKey; 30import java.security.interfaces.RSAPublicKey; 31import java.security.spec.AlgorithmParameterSpec; 32import java.security.spec.InvalidKeySpecException; 33import java.security.spec.PKCS8EncodedKeySpec; 34import java.security.spec.X509EncodedKeySpec; 35import java.util.Arrays; 36import java.util.Locale; 37import javax.crypto.BadPaddingException; 38import javax.crypto.Cipher; 39import javax.crypto.CipherSpi; 40import javax.crypto.IllegalBlockSizeException; 41import javax.crypto.NoSuchPaddingException; 42import javax.crypto.ShortBufferException; 43import javax.crypto.spec.SecretKeySpec; 44import org.conscrypt.util.EmptyArray; 45 46public abstract class OpenSSLCipherRSA extends CipherSpi { 47 /** 48 * The current OpenSSL key we're operating on. 49 */ 50 private OpenSSLKey key; 51 52 /** 53 * Current key type: private or public. 54 */ 55 private boolean usingPrivateKey; 56 57 /** 58 * Current cipher mode: encrypting or decrypting. 59 */ 60 private boolean encrypting; 61 62 /** 63 * Buffer for operations 64 */ 65 private byte[] buffer; 66 67 /** 68 * Current offset in the buffer. 69 */ 70 private int bufferOffset; 71 72 /** 73 * Flag that indicates an exception should be thrown when the input is too 74 * large during doFinal. 75 */ 76 private boolean inputTooLarge; 77 78 /** 79 * Current padding mode 80 */ 81 private int padding = NativeConstants.RSA_PKCS1_PADDING; 82 83 protected OpenSSLCipherRSA(int padding) { 84 this.padding = padding; 85 } 86 87 @Override 88 protected void engineSetMode(String mode) throws NoSuchAlgorithmException { 89 final String modeUpper = mode.toUpperCase(Locale.ROOT); 90 if ("NONE".equals(modeUpper) || "ECB".equals(modeUpper)) { 91 return; 92 } 93 94 throw new NoSuchAlgorithmException("mode not supported: " + mode); 95 } 96 97 @Override 98 protected void engineSetPadding(String padding) throws NoSuchPaddingException { 99 final String paddingUpper = padding.toUpperCase(Locale.ROOT); 100 if ("PKCS1PADDING".equals(paddingUpper)) { 101 this.padding = NativeConstants.RSA_PKCS1_PADDING; 102 return; 103 } 104 if ("NOPADDING".equals(paddingUpper)) { 105 this.padding = NativeConstants.RSA_NO_PADDING; 106 return; 107 } 108 109 throw new NoSuchPaddingException("padding not supported: " + padding); 110 } 111 112 @Override 113 protected int engineGetBlockSize() { 114 if (encrypting) { 115 return paddedBlockSizeBytes(); 116 } 117 return keySizeBytes(); 118 } 119 120 @Override 121 protected int engineGetOutputSize(int inputLen) { 122 if (encrypting) { 123 return keySizeBytes(); 124 } 125 return paddedBlockSizeBytes(); 126 } 127 128 private int paddedBlockSizeBytes() { 129 int paddedBlockSizeBytes = keySizeBytes(); 130 if (padding == NativeConstants.RSA_PKCS1_PADDING) { 131 paddedBlockSizeBytes--; // for 0 prefix 132 paddedBlockSizeBytes -= 10; // PKCS1 padding header length 133 } 134 return paddedBlockSizeBytes; 135 } 136 137 private int keySizeBytes() { 138 if (key == null) { 139 throw new IllegalStateException("cipher is not initialized"); 140 } 141 return NativeCrypto.RSA_size(this.key.getNativeRef()); 142 } 143 144 @Override 145 protected byte[] engineGetIV() { 146 return null; 147 } 148 149 @Override 150 protected AlgorithmParameters engineGetParameters() { 151 return null; 152 } 153 154 private void engineInitInternal(int opmode, Key key) throws InvalidKeyException { 155 if (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) { 156 encrypting = true; 157 } else if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) { 158 encrypting = false; 159 } else { 160 throw new InvalidParameterException("Unsupported opmode " + opmode); 161 } 162 163 if (key instanceof OpenSSLRSAPrivateKey) { 164 OpenSSLRSAPrivateKey rsaPrivateKey = (OpenSSLRSAPrivateKey) key; 165 usingPrivateKey = true; 166 this.key = rsaPrivateKey.getOpenSSLKey(); 167 } else if (key instanceof RSAPrivateCrtKey) { 168 RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) key; 169 usingPrivateKey = true; 170 this.key = OpenSSLRSAPrivateCrtKey.getInstance(rsaPrivateKey); 171 } else if (key instanceof RSAPrivateKey) { 172 RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) key; 173 usingPrivateKey = true; 174 this.key = OpenSSLRSAPrivateKey.getInstance(rsaPrivateKey); 175 } else if (key instanceof OpenSSLRSAPublicKey) { 176 OpenSSLRSAPublicKey rsaPublicKey = (OpenSSLRSAPublicKey) key; 177 usingPrivateKey = false; 178 this.key = rsaPublicKey.getOpenSSLKey(); 179 } else if (key instanceof RSAPublicKey) { 180 RSAPublicKey rsaPublicKey = (RSAPublicKey) key; 181 usingPrivateKey = false; 182 this.key = OpenSSLRSAPublicKey.getInstance(rsaPublicKey); 183 } else { 184 throw new InvalidKeyException("Need RSA private or public key"); 185 } 186 187 buffer = new byte[NativeCrypto.RSA_size(this.key.getNativeRef())]; 188 bufferOffset = 0; 189 inputTooLarge = false; 190 } 191 192 @Override 193 protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { 194 engineInitInternal(opmode, key); 195 } 196 197 @Override 198 protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, 199 SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { 200 if (params != null) { 201 throw new InvalidAlgorithmParameterException("unknown param type: " 202 + params.getClass().getName()); 203 } 204 205 engineInitInternal(opmode, key); 206 } 207 208 @Override 209 protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) 210 throws InvalidKeyException, InvalidAlgorithmParameterException { 211 if (params != null) { 212 throw new InvalidAlgorithmParameterException("unknown param type: " 213 + params.getClass().getName()); 214 } 215 216 engineInitInternal(opmode, key); 217 } 218 219 @Override 220 protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { 221 if (bufferOffset + inputLen > buffer.length) { 222 inputTooLarge = true; 223 return EmptyArray.BYTE; 224 } 225 226 System.arraycopy(input, inputOffset, buffer, bufferOffset, inputLen); 227 bufferOffset += inputLen; 228 return EmptyArray.BYTE; 229 } 230 231 @Override 232 protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, 233 int outputOffset) throws ShortBufferException { 234 engineUpdate(input, inputOffset, inputLen); 235 return 0; 236 } 237 238 @Override 239 protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) 240 throws IllegalBlockSizeException, BadPaddingException { 241 if (input != null) { 242 engineUpdate(input, inputOffset, inputLen); 243 } 244 245 if (inputTooLarge) { 246 throw new IllegalBlockSizeException("input must be under " + buffer.length + " bytes"); 247 } 248 249 final byte[] tmpBuf; 250 if (bufferOffset != buffer.length) { 251 if (padding == NativeConstants.RSA_NO_PADDING) { 252 tmpBuf = new byte[buffer.length]; 253 System.arraycopy(buffer, 0, tmpBuf, buffer.length - bufferOffset, bufferOffset); 254 } else { 255 tmpBuf = Arrays.copyOf(buffer, bufferOffset); 256 } 257 } else { 258 tmpBuf = buffer; 259 } 260 261 byte[] output = new byte[buffer.length]; 262 int resultSize; 263 if (encrypting) { 264 if (usingPrivateKey) { 265 resultSize = NativeCrypto.RSA_private_encrypt(tmpBuf.length, tmpBuf, output, 266 key.getNativeRef(), padding); 267 } else { 268 resultSize = NativeCrypto.RSA_public_encrypt(tmpBuf.length, tmpBuf, output, 269 key.getNativeRef(), padding); 270 } 271 } else { 272 try { 273 if (usingPrivateKey) { 274 resultSize = NativeCrypto.RSA_private_decrypt(tmpBuf.length, tmpBuf, output, 275 key.getNativeRef(), padding); 276 } else { 277 resultSize = NativeCrypto.RSA_public_decrypt(tmpBuf.length, tmpBuf, output, 278 key.getNativeRef(), padding); 279 } 280 } catch (SignatureException e) { 281 IllegalBlockSizeException newE = new IllegalBlockSizeException(); 282 newE.initCause(e); 283 throw newE; 284 } 285 } 286 if (!encrypting && resultSize != output.length) { 287 output = Arrays.copyOf(output, resultSize); 288 } 289 290 bufferOffset = 0; 291 return output; 292 } 293 294 @Override 295 protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, 296 int outputOffset) throws ShortBufferException, IllegalBlockSizeException, 297 BadPaddingException { 298 byte[] b = engineDoFinal(input, inputOffset, inputLen); 299 300 final int lastOffset = outputOffset + b.length; 301 if (lastOffset > output.length) { 302 throw new ShortBufferException("output buffer is too small " + output.length + " < " 303 + lastOffset); 304 } 305 306 System.arraycopy(b, 0, output, outputOffset, b.length); 307 return b.length; 308 } 309 310 @Override 311 protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKeyException { 312 try { 313 byte[] encoded = key.getEncoded(); 314 return engineDoFinal(encoded, 0, encoded.length); 315 } catch (BadPaddingException e) { 316 IllegalBlockSizeException newE = new IllegalBlockSizeException(); 317 newE.initCause(e); 318 throw newE; 319 } 320 } 321 322 @Override 323 protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, 324 int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException { 325 try { 326 byte[] encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length); 327 if (wrappedKeyType == Cipher.PUBLIC_KEY) { 328 KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); 329 return keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); 330 } else if (wrappedKeyType == Cipher.PRIVATE_KEY) { 331 KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); 332 return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); 333 } else if (wrappedKeyType == Cipher.SECRET_KEY) { 334 return new SecretKeySpec(encoded, wrappedKeyAlgorithm); 335 } else { 336 throw new UnsupportedOperationException("wrappedKeyType == " + wrappedKeyType); 337 } 338 } catch (IllegalBlockSizeException e) { 339 throw new InvalidKeyException(e); 340 } catch (BadPaddingException e) { 341 throw new InvalidKeyException(e); 342 } catch (InvalidKeySpecException e) { 343 throw new InvalidKeyException(e); 344 } 345 } 346 347 public static class PKCS1 extends OpenSSLCipherRSA { 348 public PKCS1() { 349 super(NativeConstants.RSA_PKCS1_PADDING); 350 } 351 } 352 353 public static class Raw extends OpenSSLCipherRSA { 354 public Raw() { 355 super(NativeConstants.RSA_NO_PADDING); 356 } 357 } 358} 359