/*
 * Copyright (c) 2005, 2018, 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.nio.ByteBuffer;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.MessageDigest;
import java.security.SignatureException;
import java.math.BigInteger;

import sun.security.rsa.RSAKeyFactory;

RSA signature implementation. Supports RSA signing using PKCS#1 v1.5 padding. Objects should be instantiated by calling Signature.getInstance() using the following algorithm names: . "NONEwithRSA" . "SHA1withRSA" . "SHA224withRSA" . "SHA256withRSA" . "SHA384withRSA" . "SHA512withRSA" . "MD5withRSA" . "MD2withRSA" NOTE: RSA keys must be at least 512 bits long. NOTE: NONEwithRSA must be supplied with a pre-computed message digest. Only the following digest algorithms are supported: MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 and a special-purpose digest algorithm which is a concatenation of SHA-1 and MD5 digests.
Author: Stanley Man-Kit Ho
Since: 1.6
/** * RSA signature implementation. Supports RSA signing using PKCS#1 v1.5 padding. * * Objects should be instantiated by calling Signature.getInstance() using the * following algorithm names: * * . "NONEwithRSA" * . "SHA1withRSA" * . "SHA224withRSA" * . "SHA256withRSA" * . "SHA384withRSA" * . "SHA512withRSA" * . "MD5withRSA" * . "MD2withRSA" * * NOTE: RSA keys must be at least 512 bits long. * * NOTE: NONEwithRSA must be supplied with a pre-computed message digest. * Only the following digest algorithms are supported: MD5, SHA-1, * SHA-224, SHA-256, SHA-384, SHA-512 and a special-purpose digest * algorithm which is a concatenation of SHA-1 and MD5 digests. * * @since 1.6 * @author Stanley Man-Kit Ho */
abstract class RSASignature extends java.security.SignatureSpi { // message digest implementation we use private final MessageDigest messageDigest; // message digest name private String messageDigestAlgorithm; // flag indicating whether the digest has been reset private boolean needsReset; // the signing key private Key privateKey = null; // the verification key private Key publicKey = null;
Constructs a new RSASignature. Used by Raw subclass.
/** * Constructs a new RSASignature. Used by Raw subclass. */
RSASignature() { messageDigest = null; messageDigestAlgorithm = null; }
Constructs a new RSASignature. Used by subclasses.
/** * Constructs a new RSASignature. Used by subclasses. */
RSASignature(String digestName) { try { messageDigest = MessageDigest.getInstance(digestName); // Get the digest's canonical name messageDigestAlgorithm = messageDigest.getAlgorithm(); } catch (NoSuchAlgorithmException e) { throw new ProviderException(e); } needsReset = false; } // Nested class for NONEwithRSA signatures public static final class Raw extends RSASignature { // the longest supported digest is 512 bits (SHA-512) private static final int RAW_RSA_MAX = 64; private final byte[] precomputedDigest; private int offset = 0; public Raw() { precomputedDigest = new byte[RAW_RSA_MAX]; } // Stores the precomputed message digest value. @Override protected void engineUpdate(byte b) throws SignatureException { if (offset >= precomputedDigest.length) { offset = RAW_RSA_MAX + 1; return; } precomputedDigest[offset++] = b; } // Stores the precomputed message digest value. @Override protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { if (len > (precomputedDigest.length - offset)) { offset = RAW_RSA_MAX + 1; return; } System.arraycopy(b, off, precomputedDigest, offset, len); offset += len; } // Stores the precomputed message digest value. @Override protected void engineUpdate(ByteBuffer byteBuffer) { int len = byteBuffer.remaining(); if (len <= 0) { return; } if (len > (precomputedDigest.length - offset)) { offset = RAW_RSA_MAX + 1; return; } byteBuffer.get(precomputedDigest, offset, len); offset += len; } @Override protected void resetDigest(){ offset = 0; } // Returns the precomputed message digest value. @Override protected byte[] getDigestValue() throws SignatureException { if (offset > RAW_RSA_MAX) { throw new SignatureException("Message digest is too long"); } // Determine the digest algorithm from the digest length if (offset == 20) { setDigestName("SHA1"); } else if (offset == 36) { setDigestName("SHA1+MD5"); } else if (offset == 32) { setDigestName("SHA-256"); } else if (offset == 48) { setDigestName("SHA-384"); } else if (offset == 64) { setDigestName("SHA-512"); } else if (offset == 16) { setDigestName("MD5"); } else if (offset == 28) { setDigestName("SHA-224"); } else { throw new SignatureException( "Message digest length is not supported"); } byte[] result = new byte[offset]; System.arraycopy(precomputedDigest, 0, result, 0, offset); offset = 0; return result; } } public static final class SHA1 extends RSASignature { public SHA1() { super("SHA1"); } } public static final class SHA224 extends RSASignature { public SHA224() { super("SHA-224"); } } public static final class SHA256 extends RSASignature { public SHA256() { super("SHA-256"); } } public static final class SHA384 extends RSASignature { public SHA384() { super("SHA-384"); } } public static final class SHA512 extends RSASignature { public SHA512() { super("SHA-512"); } } public static final class MD5 extends RSASignature { public MD5() { super("MD5"); } } public static final class MD2 extends RSASignature { public MD2() { super("MD2"); } } // initialize for signing. See JCA doc protected void engineInitVerify(PublicKey key) throws InvalidKeyException { // This signature accepts only RSAPublicKey if ((key instanceof java.security.interfaces.RSAPublicKey) == false) { throw new InvalidKeyException("Key type not supported"); } java.security.interfaces.RSAPublicKey rsaKey = (java.security.interfaces.RSAPublicKey) key; if ((key instanceof sun.security.mscapi.RSAPublicKey) == false) { // 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 = generatePublicKeyBlob( keyBitLength, modulusBytes, exponentBytes); try { publicKey = importPublicKey(keyBlob, keyBitLength); } catch (KeyStoreException e) { throw new InvalidKeyException(e); } } else { publicKey = (sun.security.mscapi.RSAPublicKey) key; } this.privateKey = null; resetDigest(); } // initialize for signing. See JCA doc protected void engineInitSign(PrivateKey key) throws InvalidKeyException { // This signature accepts only RSAPrivateKey if ((key instanceof sun.security.mscapi.RSAPrivateKey) == false) { throw new InvalidKeyException("Key type not supported"); } privateKey = (sun.security.mscapi.RSAPrivateKey) key; // Check against the local and global values to make sure // the sizes are ok. Round up to nearest byte. RSAKeyFactory.checkKeyLengths(((privateKey.length() + 7) & ~7), null, RSAKeyPairGenerator.KEY_SIZE_MIN, RSAKeyPairGenerator.KEY_SIZE_MAX); this.publicKey = null; resetDigest(); }
Resets the message digest if needed.
/** * Resets the message digest if needed. */
protected void resetDigest() { if (needsReset) { messageDigest.reset(); needsReset = false; } } protected byte[] getDigestValue() throws SignatureException { needsReset = false; return messageDigest.digest(); } protected void setDigestName(String name) { messageDigestAlgorithm = name; }
Updates the data to be signed or verified using the specified byte.
Params:
  • b – the byte to use for the update.
Throws:
/** * Updates the data to be signed or verified * using the specified byte. * * @param b the byte to use for the update. * * @exception SignatureException if the engine is not initialized * properly. */
protected void engineUpdate(byte b) throws SignatureException { messageDigest.update(b); needsReset = true; }
Updates the data to be signed or verified, using the specified array of bytes, starting at the specified offset.
Params:
  • b – the array of bytes
  • off – the offset to start from in the array of bytes
  • len – the number of bytes to use, starting at offset
Throws:
/** * Updates the data to be signed or verified, using the * specified array of bytes, starting at the specified offset. * * @param b the array of bytes * @param off the offset to start from in the array of bytes * @param len the number of bytes to use, starting at offset * * @exception SignatureException if the engine is not initialized * properly */
protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { messageDigest.update(b, off, len); needsReset = true; }
Updates the data to be signed or verified, using the specified ByteBuffer.
Params:
  • input – the ByteBuffer
/** * Updates the data to be signed or verified, using the * specified ByteBuffer. * * @param input the ByteBuffer */
protected void engineUpdate(ByteBuffer input) { messageDigest.update(input); needsReset = true; }
Returns the signature bytes of all the data updated so far. The format of the signature depends on the underlying signature scheme.
Throws:
  • SignatureException – if the engine is not initialized properly or if this signature algorithm is unable to process the input data provided.
Returns:the signature bytes of the signing operation's result.
/** * Returns the signature bytes of all the data * updated so far. * The format of the signature depends on the underlying * signature scheme. * * @return the signature bytes of the signing operation's result. * * @exception SignatureException if the engine is not * initialized properly or if this signature algorithm is unable to * process the input data provided. */
protected byte[] engineSign() throws SignatureException { byte[] hash = getDigestValue(); // Omit the hash OID when generating a Raw signature boolean noHashOID = this instanceof Raw; // Sign hash using MS Crypto APIs byte[] result = signHash(noHashOID, hash, hash.length, messageDigestAlgorithm, privateKey.getHCryptProvider(), privateKey.getHCryptKey()); // Convert signature array from little endian to big endian return convertEndianArray(result); }
Convert array from big endian to little endian, or vice versa.
/** * Convert array from big endian to little endian, or vice versa. */
private byte[] convertEndianArray(byte[] byteArray) { if (byteArray == null || byteArray.length == 0) return byteArray; byte [] retval = new byte[byteArray.length]; // make it big endian for (int i=0;i < byteArray.length;i++) retval[i] = byteArray[byteArray.length - i - 1]; return retval; }
Sign hash using Microsoft Crypto API with HCRYPTKEY. The returned data is in little-endian.
/** * Sign hash using Microsoft Crypto API with HCRYPTKEY. * The returned data is in little-endian. */
private native static byte[] signHash(boolean noHashOID, byte[] hash, int hashSize, String hashAlgorithm, long hCryptProv, long hCryptKey) throws SignatureException;
Verify a signed hash using Microsoft Crypto API with HCRYPTKEY.
/** * Verify a signed hash using Microsoft Crypto API with HCRYPTKEY. */
private native static boolean verifySignedHash(byte[] hash, int hashSize, String hashAlgorithm, byte[] signature, int signatureSize, long hCryptProv, long hCryptKey) throws SignatureException;
Verifies the passed-in signature.
Params:
  • sigBytes – the signature bytes to be verified.
Throws:
  • SignatureException – if the engine is not initialized properly, the passed-in signature is improperly encoded or of the wrong type, if this signature algorithm is unable to process the input data provided, etc.
Returns:true if the signature was verified, false if not.
/** * Verifies the passed-in signature. * * @param sigBytes the signature bytes to be verified. * * @return true if the signature was verified, false if not. * * @exception SignatureException if the engine is not * initialized properly, the passed-in signature is improperly * encoded or of the wrong type, if this signature algorithm is unable to * process the input data provided, etc. */
protected boolean engineVerify(byte[] sigBytes) throws SignatureException { byte[] hash = getDigestValue(); return verifySignedHash(hash, hash.length, messageDigestAlgorithm, convertEndianArray(sigBytes), sigBytes.length, publicKey.getHCryptProvider(), publicKey.getHCryptKey()); }
Sets the specified algorithm parameter to the specified value. This method supplies a general-purpose mechanism through which it is possible to set the various parameters of this object. A parameter may be any settable parameter for the algorithm, such as a parameter size, or a source of random bits for signature generation (if appropriate), or an indication of whether or not to perform a specific but optional computation. A uniform algorithm-specific naming scheme for each parameter is desirable but left unspecified at this time.
Params:
  • param – the string identifier of the parameter.
  • value – the parameter value.
Throws:
  • InvalidParameterException – if param is an invalid parameter for this signature algorithm engine, the parameter is already set and cannot be set again, a security exception occurs, and so on.
Deprecated:Replaced by engineSetParameter.
/** * Sets the specified algorithm parameter to the specified * value. This method supplies a general-purpose mechanism through * which it is possible to set the various parameters of this object. * A parameter may be any settable parameter for the algorithm, such as * a parameter size, or a source of random bits for signature generation * (if appropriate), or an indication of whether or not to perform * a specific but optional computation. A uniform algorithm-specific * naming scheme for each parameter is desirable but left unspecified * at this time. * * @param param the string identifier of the parameter. * * @param value the parameter value. * * @exception InvalidParameterException if <code>param</code> is an * invalid parameter for this signature algorithm engine, * the parameter is already set * and cannot be set again, a security exception occurs, and so on. * * @deprecated Replaced by {@link * #engineSetParameter(java.security.spec.AlgorithmParameterSpec) * engineSetParameter}. */
@Deprecated protected void engineSetParameter(String param, Object value) throws InvalidParameterException { throw new InvalidParameterException("Parameter not supported"); }
Gets the value of the specified algorithm parameter. This method supplies a general-purpose mechanism through which it is possible to get the various parameters of this object. A parameter may be any settable parameter for the algorithm, such as a parameter size, or a source of random bits for signature generation (if appropriate), or an indication of whether or not to perform a specific but optional computation. A uniform algorithm-specific naming scheme for each parameter is desirable but left unspecified at this time.
Params:
  • param – the string name of the parameter.
Throws:
  • InvalidParameterException – if param is an invalid parameter for this engine, or another exception occurs while trying to get this parameter.
Returns:the object that represents the parameter value, or null if there is none.
Deprecated:
/** * Gets the value of the specified algorithm parameter. * This method supplies a general-purpose mechanism through which it * is possible to get the various parameters of this object. A parameter * may be any settable parameter for the algorithm, such as a parameter * size, or a source of random bits for signature generation (if * appropriate), or an indication of whether or not to perform a * specific but optional computation. A uniform algorithm-specific * naming scheme for each parameter is desirable but left unspecified * at this time. * * @param param the string name of the parameter. * * @return the object that represents the parameter value, or null if * there is none. * * @exception InvalidParameterException if <code>param</code> is an * invalid parameter for this engine, or another exception occurs while * trying to get this parameter. * * @deprecated */
@Deprecated protected Object engineGetParameter(String param) throws InvalidParameterException { throw new InvalidParameterException("Parameter not supported"); }
Generates a public-key BLOB from a key's components.
/** * Generates a public-key BLOB from a key's components. */
// used by RSACipher static native byte[] generatePublicKeyBlob( int keyBitLength, byte[] modulus, byte[] publicExponent) throws InvalidKeyException;
Imports a public-key BLOB.
/** * Imports a public-key BLOB. */
// used by RSACipher static native RSAPublicKey importPublicKey(byte[] keyBlob, int keySize) throws KeyStoreException; }