8054834: Modular Source Code
Reviewed-by: alanb, chegar, ihse, mduigou
Contributed-by: alan.bateman@oracle.com, alex.buckley@oracle.com, chris.hegarty@oracle.com, erik.joelsson@oracle.com, jonathan.gibbons@oracle.com, karen.kinnear@oracle.com, magnus.ihse.bursie@oracle.com, mandy.chung@oracle.com, mark.reinhold@oracle.com, paul.sandoz@oracle.com
/*
* 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;
}