| /* |
| * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.security.mscapi; |
| |
| import java.math.BigInteger; |
| import java.security.*; |
| import java.security.Key; |
| import java.security.interfaces.*; |
| import java.security.spec.*; |
| |
| import javax.crypto.*; |
| import javax.crypto.spec.*; |
| |
| import sun.security.rsa.RSAKeyFactory; |
| import sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec; |
| import sun.security.util.KeyUtil; |
| |
| /** |
| * RSA cipher implementation using the Microsoft Crypto API. |
| * Supports RSA en/decryption and signing/verifying using PKCS#1 v1.5 padding. |
| * |
| * Objects should be instantiated by calling Cipher.getInstance() using the |
| * following algorithm name: |
| * |
| * . "RSA/ECB/PKCS1Padding" (or "RSA") for PKCS#1 padding. The mode (blocktype) |
| * is selected based on the en/decryption mode and public/private key used. |
| * |
| * We only do one RSA operation per doFinal() call. If the application passes |
| * more data via calls to update() or doFinal(), we throw an |
| * IllegalBlockSizeException when doFinal() is called (see JCE API spec). |
| * Bulk encryption using RSA does not make sense and is not standardized. |
| * |
| * Note: RSA keys should be at least 512 bits long |
| * |
| * @since 1.6 |
| * @author Andreas Sterbenz |
| * @author Vincent Ryan |
| */ |
| public final class RSACipher extends CipherSpi { |
| |
| // constant for an empty byte array |
| private final static byte[] B0 = new byte[0]; |
| |
| // mode constant for public key encryption |
| private final static int MODE_ENCRYPT = 1; |
| // mode constant for private key decryption |
| private final static int MODE_DECRYPT = 2; |
| // mode constant for private key encryption (signing) |
| private final static int MODE_SIGN = 3; |
| // mode constant for public key decryption (verifying) |
| private final static int MODE_VERIFY = 4; |
| |
| // constant for PKCS#1 v1.5 RSA |
| private final static String PAD_PKCS1 = "PKCS1Padding"; |
| private final static int PAD_PKCS1_LENGTH = 11; |
| |
| // current mode, one of MODE_* above. Set when init() is called |
| private int mode; |
| |
| // active padding type, one of PAD_* above. Set by setPadding() |
| private String paddingType; |
| private int paddingLength = 0; |
| |
| // buffer for the data |
| private byte[] buffer; |
| // offset into the buffer (number of bytes buffered) |
| private int bufOfs; |
| |
| // size of the output (the length of the key). |
| private int outputSize; |
| |
| // the public key, if we were initialized using a public key |
| private sun.security.mscapi.Key publicKey; |
| |
| // the private key, if we were initialized using a private key |
| private sun.security.mscapi.Key privateKey; |
| |
| // cipher parameter for TLS RSA premaster secret |
| private AlgorithmParameterSpec spec = null; |
| |
| // the source of randomness |
| private SecureRandom random; |
| |
| public RSACipher() { |
| paddingType = PAD_PKCS1; |
| } |
| |
| // modes do not make sense for RSA, but allow ECB |
| // see JCE spec |
| protected void engineSetMode(String mode) throws NoSuchAlgorithmException { |
| if (mode.equalsIgnoreCase("ECB") == false) { |
| throw new NoSuchAlgorithmException("Unsupported mode " + mode); |
| } |
| } |
| |
| // set the padding type |
| // see JCE spec |
| protected void engineSetPadding(String paddingName) |
| throws NoSuchPaddingException { |
| if (paddingName.equalsIgnoreCase(PAD_PKCS1)) { |
| paddingType = PAD_PKCS1; |
| } else { |
| throw new NoSuchPaddingException |
| ("Padding " + paddingName + " not supported"); |
| } |
| } |
| |
| // return 0 as block size, we are not a block cipher |
| // see JCE spec |
| protected int engineGetBlockSize() { |
| return 0; |
| } |
| |
| // return the output size |
| // see JCE spec |
| protected int engineGetOutputSize(int inputLen) { |
| return outputSize; |
| } |
| |
| // no iv, return null |
| // see JCE spec |
| protected byte[] engineGetIV() { |
| return null; |
| } |
| |
| // no parameters, return null |
| // see JCE spec |
| protected AlgorithmParameters engineGetParameters() { |
| return null; |
| } |
| |
| // see JCE spec |
| protected void engineInit(int opmode, Key key, SecureRandom random) |
| throws InvalidKeyException { |
| init(opmode, key); |
| } |
| |
| // see JCE spec |
| protected void engineInit(int opmode, Key key, |
| AlgorithmParameterSpec params, SecureRandom random) |
| throws InvalidKeyException, InvalidAlgorithmParameterException { |
| |
| if (params != null) { |
| if (!(params instanceof TlsRsaPremasterSecretParameterSpec)) { |
| throw new InvalidAlgorithmParameterException( |
| "Parameters not supported"); |
| } |
| spec = params; |
| this.random = random; // for TLS RSA premaster secret |
| } |
| init(opmode, key); |
| } |
| |
| // see JCE spec |
| protected void engineInit(int opmode, Key key, |
| AlgorithmParameters params, SecureRandom random) |
| throws InvalidKeyException, InvalidAlgorithmParameterException { |
| |
| if (params != null) { |
| throw new InvalidAlgorithmParameterException |
| ("Parameters not supported"); |
| } |
| init(opmode, key); |
| } |
| |
| // initialize this cipher |
| private void init(int opmode, Key key) throws InvalidKeyException { |
| |
| boolean encrypt; |
| |
| switch (opmode) { |
| case Cipher.ENCRYPT_MODE: |
| case Cipher.WRAP_MODE: |
| paddingLength = PAD_PKCS1_LENGTH; |
| encrypt = true; |
| break; |
| case Cipher.DECRYPT_MODE: |
| case Cipher.UNWRAP_MODE: |
| paddingLength = 0; // reset |
| encrypt = false; |
| break; |
| default: |
| throw new InvalidKeyException("Unknown mode: " + opmode); |
| } |
| |
| if (!(key instanceof sun.security.mscapi.Key)) { |
| if (key instanceof java.security.interfaces.RSAPublicKey) { |
| java.security.interfaces.RSAPublicKey rsaKey = |
| (java.security.interfaces.RSAPublicKey) key; |
| |
| // Convert key to MSCAPI format |
| |
| BigInteger modulus = rsaKey.getModulus(); |
| BigInteger exponent = rsaKey.getPublicExponent(); |
| |
| // Check against the local and global values to make sure |
| // the sizes are ok. Round up to the nearest byte. |
| RSAKeyFactory.checkKeyLengths(((modulus.bitLength() + 7) & ~7), |
| exponent, -1, RSAKeyPairGenerator.KEY_SIZE_MAX); |
| |
| byte[] modulusBytes = modulus.toByteArray(); |
| byte[] exponentBytes = exponent.toByteArray(); |
| |
| // Adjust key length due to sign bit |
| int keyBitLength = (modulusBytes[0] == 0) |
| ? (modulusBytes.length - 1) * 8 |
| : modulusBytes.length * 8; |
| |
| byte[] keyBlob = RSASignature.generatePublicKeyBlob( |
| keyBitLength, modulusBytes, exponentBytes); |
| |
| try { |
| key = RSASignature.importPublicKey(keyBlob, keyBitLength); |
| |
| } catch (KeyStoreException e) { |
| throw new InvalidKeyException(e); |
| } |
| |
| } else { |
| throw new InvalidKeyException("Unsupported key type: " + key); |
| } |
| } |
| |
| if (key instanceof PublicKey) { |
| mode = encrypt ? MODE_ENCRYPT : MODE_VERIFY; |
| publicKey = (sun.security.mscapi.Key)key; |
| privateKey = null; |
| outputSize = publicKey.length() / 8; |
| } else if (key instanceof PrivateKey) { |
| mode = encrypt ? MODE_SIGN : MODE_DECRYPT; |
| privateKey = (sun.security.mscapi.Key)key; |
| publicKey = null; |
| outputSize = privateKey.length() / 8; |
| } else { |
| throw new InvalidKeyException("Unknown key type: " + key); |
| } |
| |
| bufOfs = 0; |
| buffer = new byte[outputSize]; |
| } |
| |
| // internal update method |
| private void update(byte[] in, int inOfs, int inLen) { |
| if ((inLen == 0) || (in == null)) { |
| return; |
| } |
| if (bufOfs + inLen > (buffer.length - paddingLength)) { |
| bufOfs = buffer.length + 1; |
| return; |
| } |
| System.arraycopy(in, inOfs, buffer, bufOfs, inLen); |
| bufOfs += inLen; |
| } |
| |
| // internal doFinal() method. Here we perform the actual RSA operation |
| private byte[] doFinal() throws BadPaddingException, |
| IllegalBlockSizeException { |
| if (bufOfs > buffer.length) { |
| throw new IllegalBlockSizeException("Data must not be longer " |
| + "than " + (buffer.length - paddingLength) + " bytes"); |
| } |
| |
| try { |
| byte[] data = buffer; |
| switch (mode) { |
| case MODE_SIGN: |
| return encryptDecrypt(data, bufOfs, |
| privateKey.getHCryptKey(), true); |
| |
| case MODE_VERIFY: |
| return encryptDecrypt(data, bufOfs, |
| publicKey.getHCryptKey(), false); |
| |
| case MODE_ENCRYPT: |
| return encryptDecrypt(data, bufOfs, |
| publicKey.getHCryptKey(), true); |
| |
| case MODE_DECRYPT: |
| return encryptDecrypt(data, bufOfs, |
| privateKey.getHCryptKey(), false); |
| |
| default: |
| throw new AssertionError("Internal error"); |
| } |
| |
| } catch (KeyException e) { |
| throw new ProviderException(e); |
| |
| } finally { |
| bufOfs = 0; |
| } |
| } |
| |
| // see JCE spec |
| protected byte[] engineUpdate(byte[] in, int inOfs, int inLen) { |
| update(in, inOfs, inLen); |
| return B0; |
| } |
| |
| // see JCE spec |
| protected int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, |
| int outOfs) { |
| update(in, inOfs, inLen); |
| return 0; |
| } |
| |
| // see JCE spec |
| protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen) |
| throws BadPaddingException, IllegalBlockSizeException { |
| update(in, inOfs, inLen); |
| return doFinal(); |
| } |
| |
| // see JCE spec |
| protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out, |
| int outOfs) throws ShortBufferException, BadPaddingException, |
| IllegalBlockSizeException { |
| if (outputSize > out.length - outOfs) { |
| throw new ShortBufferException |
| ("Need " + outputSize + " bytes for output"); |
| } |
| update(in, inOfs, inLen); |
| byte[] result = doFinal(); |
| int n = result.length; |
| System.arraycopy(result, 0, out, outOfs, n); |
| return n; |
| } |
| |
| // see JCE spec |
| protected byte[] engineWrap(Key key) throws InvalidKeyException, |
| IllegalBlockSizeException { |
| byte[] encoded = key.getEncoded(); // TODO - unextractable key |
| if ((encoded == null) || (encoded.length == 0)) { |
| throw new InvalidKeyException("Could not obtain encoded key"); |
| } |
| if (encoded.length > buffer.length) { |
| throw new InvalidKeyException("Key is too long for wrapping"); |
| } |
| update(encoded, 0, encoded.length); |
| try { |
| return doFinal(); |
| } catch (BadPaddingException e) { |
| // should not occur |
| throw new InvalidKeyException("Wrapping failed", e); |
| } |
| } |
| |
| // see JCE spec |
| protected java.security.Key engineUnwrap(byte[] wrappedKey, |
| String algorithm, |
| int type) throws InvalidKeyException, NoSuchAlgorithmException { |
| |
| if (wrappedKey.length > buffer.length) { |
| throw new InvalidKeyException("Key is too long for unwrapping"); |
| } |
| |
| boolean isTlsRsaPremasterSecret = |
| algorithm.equals("TlsRsaPremasterSecret"); |
| Exception failover = null; |
| byte[] encoded = null; |
| |
| update(wrappedKey, 0, wrappedKey.length); |
| try { |
| encoded = doFinal(); |
| } catch (BadPaddingException e) { |
| if (isTlsRsaPremasterSecret) { |
| failover = e; |
| } else { |
| throw new InvalidKeyException("Unwrapping failed", e); |
| } |
| } catch (IllegalBlockSizeException e) { |
| // should not occur, handled with length check above |
| throw new InvalidKeyException("Unwrapping failed", e); |
| } |
| |
| if (isTlsRsaPremasterSecret) { |
| if (!(spec instanceof TlsRsaPremasterSecretParameterSpec)) { |
| throw new IllegalStateException( |
| "No TlsRsaPremasterSecretParameterSpec specified"); |
| } |
| |
| // polish the TLS premaster secret |
| encoded = KeyUtil.checkTlsPreMasterSecretKey( |
| ((TlsRsaPremasterSecretParameterSpec)spec).getClientVersion(), |
| ((TlsRsaPremasterSecretParameterSpec)spec).getServerVersion(), |
| random, encoded, (failover != null)); |
| } |
| |
| return constructKey(encoded, algorithm, type); |
| } |
| |
| // see JCE spec |
| protected int engineGetKeySize(Key key) throws InvalidKeyException { |
| |
| if (key instanceof sun.security.mscapi.Key) { |
| return ((sun.security.mscapi.Key) key).length(); |
| |
| } else if (key instanceof RSAKey) { |
| return ((RSAKey) key).getModulus().bitLength(); |
| |
| } else { |
| throw new InvalidKeyException("Unsupported key type: " + key); |
| } |
| } |
| |
| // Construct an X.509 encoded public key. |
| private static PublicKey constructPublicKey(byte[] encodedKey, |
| String encodedKeyAlgorithm) |
| throws InvalidKeyException, NoSuchAlgorithmException { |
| |
| try { |
| KeyFactory keyFactory = KeyFactory.getInstance(encodedKeyAlgorithm); |
| X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey); |
| |
| return keyFactory.generatePublic(keySpec); |
| |
| } catch (NoSuchAlgorithmException nsae) { |
| throw new NoSuchAlgorithmException("No installed provider " + |
| "supports the " + encodedKeyAlgorithm + " algorithm", nsae); |
| |
| } catch (InvalidKeySpecException ike) { |
| throw new InvalidKeyException("Cannot construct public key", ike); |
| } |
| } |
| |
| // Construct a PKCS #8 encoded private key. |
| private static PrivateKey constructPrivateKey(byte[] encodedKey, |
| String encodedKeyAlgorithm) |
| throws InvalidKeyException, NoSuchAlgorithmException { |
| |
| try { |
| KeyFactory keyFactory = KeyFactory.getInstance(encodedKeyAlgorithm); |
| PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey); |
| |
| return keyFactory.generatePrivate(keySpec); |
| |
| } catch (NoSuchAlgorithmException nsae) { |
| throw new NoSuchAlgorithmException("No installed provider " + |
| "supports the " + encodedKeyAlgorithm + " algorithm", nsae); |
| |
| } catch (InvalidKeySpecException ike) { |
| throw new InvalidKeyException("Cannot construct private key", ike); |
| } |
| } |
| |
| // Construct an encoded secret key. |
| private static SecretKey constructSecretKey(byte[] encodedKey, |
| String encodedKeyAlgorithm) { |
| |
| return new SecretKeySpec(encodedKey, encodedKeyAlgorithm); |
| } |
| |
| private static Key constructKey(byte[] encodedKey, |
| String encodedKeyAlgorithm, |
| int keyType) throws InvalidKeyException, NoSuchAlgorithmException { |
| |
| switch (keyType) { |
| case Cipher.PUBLIC_KEY: |
| return constructPublicKey(encodedKey, encodedKeyAlgorithm); |
| case Cipher.PRIVATE_KEY: |
| return constructPrivateKey(encodedKey, encodedKeyAlgorithm); |
| case Cipher.SECRET_KEY: |
| return constructSecretKey(encodedKey, encodedKeyAlgorithm); |
| default: |
| throw new InvalidKeyException("Unknown key type " + keyType); |
| } |
| } |
| |
| /* |
| * Encrypt/decrypt a data buffer using Microsoft Crypto API with HCRYPTKEY. |
| * It expects and returns ciphertext data in big-endian form. |
| */ |
| private native static byte[] encryptDecrypt(byte[] data, int dataSize, |
| long hCryptKey, boolean doEncrypt) throws KeyException; |
| |
| } |