changeset 4306:5a4e2a734f1d

6753664: Support SHA256 (and higher) in SunMSCAPI Reviewed-by: mullan
author vinnie
date Wed, 27 Apr 2011 20:21:32 +0100
parents d64f9348c7ca
children 7c109d060365
files src/windows/classes/sun/security/mscapi/RSASignature.java src/windows/classes/sun/security/mscapi/SunMSCAPI.java src/windows/native/sun/security/mscapi/security.cpp test/sun/security/mscapi/SignUsingSHA2withRSA.java test/sun/security/mscapi/SignUsingSHA2withRSA.sh
diffstat 5 files changed, 391 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/src/windows/classes/sun/security/mscapi/RSASignature.java	Fri Apr 22 17:03:51 2011 +0100
+++ b/src/windows/classes/sun/security/mscapi/RSASignature.java	Wed Apr 27 20:21:32 2011 +0100
@@ -50,6 +50,9 @@
  * following algorithm names:
  *
  *  . "SHA1withRSA"
+ *  . "SHA256withRSA"
+ *  . "SHA384withRSA"
+ *  . "SHA512withRSA"
  *  . "MD5withRSA"
  *  . "MD2withRSA"
  *
@@ -63,7 +66,10 @@
     // message digest implementation we use
     private final MessageDigest messageDigest;
 
-    // flag indicating whether the digest is reset
+    // message digest name
+    private final String messageDigestAlgorithm;
+
+    // flag indicating whether the digest has been reset
     private boolean needsReset;
 
     // the signing key
@@ -73,10 +79,15 @@
     private Key publicKey = null;
 
 
+    /**
+     * 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);
@@ -91,6 +102,24 @@
         }
     }
 
+    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");
@@ -103,16 +132,7 @@
         }
     }
 
-    /**
-     * Initializes this signature object with the specified
-     * public key for verification operations.
-     *
-     * @param publicKey the public key of the identity whose signature is
-     * going to be verified.
-     *
-     * @exception InvalidKeyException if the key is improperly
-     * encoded, parameters are missing, and so on.
-     */
+    // initialize for signing. See JCA doc
     protected void engineInitVerify(PublicKey key)
         throws InvalidKeyException
     {
@@ -158,24 +178,12 @@
             publicKey = (sun.security.mscapi.RSAPublicKey) key;
         }
 
-        if (needsReset) {
-            messageDigest.reset();
-            needsReset = false;
-        }
+        this.privateKey = null;
+        resetDigest();
     }
 
-    /**
-     * Initializes this signature object with the specified
-     * private key for signing operations.
-     *
-     * @param privateKey the private key of the identity whose signature
-     * will be generated.
-     *
-     * @exception InvalidKeyException if the key is improperly
-     * encoded, parameters are missing, and so on.
-     */
-    protected void engineInitSign(PrivateKey key)
-        throws InvalidKeyException
+    // 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) {
@@ -189,12 +197,25 @@
             null, RSAKeyPairGenerator.KEY_SIZE_MIN,
             RSAKeyPairGenerator.KEY_SIZE_MAX);
 
+        this.publicKey = null;
+        resetDigest();
+    }
+
+    /**
+     * Resets the message digest if needed.
+     */
+    private void resetDigest() {
         if (needsReset) {
             messageDigest.reset();
             needsReset = false;
         }
     }
 
+    private byte[] getDigestValue() {
+        needsReset = false;
+        return messageDigest.digest();
+    }
+
     /**
      * Updates the data to be signed or verified
      * using the specified byte.
@@ -254,13 +275,12 @@
      */
     protected byte[] engineSign() throws SignatureException {
 
-        byte[] hash = messageDigest.digest();
-        needsReset = false;
+        byte[] hash = getDigestValue();
 
         // Sign hash using MS Crypto APIs
 
         byte[] result = signHash(hash, hash.length,
-            messageDigest.getAlgorithm(), privateKey.getHCryptProvider(),
+            messageDigestAlgorithm, privateKey.getHCryptProvider(),
             privateKey.getHCryptKey());
 
         // Convert signature array from little endian to big endian
@@ -314,11 +334,10 @@
     protected boolean engineVerify(byte[] sigBytes)
         throws SignatureException
     {
-        byte[] hash = messageDigest.digest();
-        needsReset = false;
+        byte[] hash = getDigestValue();
 
         return verifySignedHash(hash, hash.length,
-            messageDigest.getAlgorithm(), convertEndianArray(sigBytes),
+            messageDigestAlgorithm, convertEndianArray(sigBytes),
             sigBytes.length, publicKey.getHCryptProvider(),
             publicKey.getHCryptKey());
     }
--- a/src/windows/classes/sun/security/mscapi/SunMSCAPI.java	Fri Apr 22 17:03:51 2011 +0100
+++ b/src/windows/classes/sun/security/mscapi/SunMSCAPI.java	Wed Apr 27 20:21:32 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2011, 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
@@ -81,6 +81,12 @@
          */
         map.put("Signature.SHA1withRSA",
             "sun.security.mscapi.RSASignature$SHA1");
+        map.put("Signature.SHA256withRSA",
+            "sun.security.mscapi.RSASignature$SHA256");
+        map.put("Signature.SHA384withRSA",
+            "sun.security.mscapi.RSASignature$SHA384");
+        map.put("Signature.SHA512withRSA",
+            "sun.security.mscapi.RSASignature$SHA512");
         map.put("Signature.MD5withRSA",
             "sun.security.mscapi.RSASignature$MD5");
         map.put("Signature.MD2withRSA",
@@ -89,12 +95,16 @@
         // supported key classes
         map.put("Signature.SHA1withRSA SupportedKeyClasses",
             "sun.security.mscapi.Key");
+        map.put("Signature.SHA256withRSA SupportedKeyClasses",
+            "sun.security.mscapi.Key");
+        map.put("Signature.SHA384withRSA SupportedKeyClasses",
+            "sun.security.mscapi.Key");
+        map.put("Signature.SHA512withRSA SupportedKeyClasses",
+            "sun.security.mscapi.Key");
         map.put("Signature.MD5withRSA SupportedKeyClasses",
             "sun.security.mscapi.Key");
         map.put("Signature.MD2withRSA SupportedKeyClasses",
             "sun.security.mscapi.Key");
-        map.put("Signature.NONEwithRSA SupportedKeyClasses",
-            "sun.security.mscapi.Key");
 
         /*
          * Key Pair Generator engines
--- a/src/windows/native/sun/security/mscapi/security.cpp	Fri Apr 22 17:03:51 2011 +0100
+++ b/src/windows/native/sun/security/mscapi/security.cpp	Wed Apr 27 20:21:32 2011 +0100
@@ -483,6 +483,7 @@
     jbyte* pHashBuffer = NULL;
     jbyte* pSignedHashBuffer = NULL;
     jbyteArray jSignedHash = NULL;
+    HCRYPTPROV hCryptProvAlt = NULL;
 
     __try
     {
@@ -492,8 +493,32 @@
         // Acquire a hash object handle.
         if (::CryptCreateHash(HCRYPTPROV(hCryptProv), algId, 0, 0, &hHash) == FALSE)
         {
-            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
-            __leave;
+            // Failover to using the PROV_RSA_AES CSP
+
+            DWORD cbData = 256;
+            BYTE pbData[256];
+            pbData[0] = '\0';
+
+            // Get name of the key container
+            ::CryptGetProvParam((HCRYPTPROV)hCryptProv, PP_CONTAINER,
+                (BYTE *)pbData, &cbData, 0);
+
+            // Acquire an alternative CSP handle
+            if (::CryptAcquireContext(&hCryptProvAlt, LPCSTR(pbData), NULL,
+                PROV_RSA_AES, 0) == FALSE)
+            {
+
+                ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
+                __leave;
+            }
+
+            // Acquire a hash object handle.
+            if (::CryptCreateHash(HCRYPTPROV(hCryptProvAlt), algId, 0, 0,
+                &hHash) == FALSE)
+            {
+                ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
+                __leave;
+            }
         }
 
         // Copy hash from Java to native buffer
@@ -546,6 +571,9 @@
     }
     __finally
     {
+        if (hCryptProvAlt)
+            ::CryptReleaseContext(hCryptProvAlt, 0);
+
         if (pSignedHashBuffer)
             delete [] pSignedHashBuffer;
 
@@ -574,6 +602,7 @@
     jbyte* pSignedHashBuffer = NULL;
     DWORD dwSignedHashBufferLen = jSignedHashSize;
     jboolean result = JNI_FALSE;
+    HCRYPTPROV hCryptProvAlt = NULL;
 
     __try
     {
@@ -584,8 +613,32 @@
         if (::CryptCreateHash(HCRYPTPROV(hCryptProv), algId, 0, 0, &hHash)
             == FALSE)
         {
-            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
-            __leave;
+            // Failover to using the PROV_RSA_AES CSP
+
+            DWORD cbData = 256;
+            BYTE pbData[256];
+            pbData[0] = '\0';
+
+            // Get name of the key container
+            ::CryptGetProvParam((HCRYPTPROV)hCryptProv, PP_CONTAINER,
+                (BYTE *)pbData, &cbData, 0);
+
+            // Acquire an alternative CSP handle
+            if (::CryptAcquireContext(&hCryptProvAlt, LPCSTR(pbData), NULL,
+                PROV_RSA_AES, 0) == FALSE)
+            {
+
+                ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
+                __leave;
+            }
+
+            // Acquire a hash object handle.
+            if (::CryptCreateHash(HCRYPTPROV(hCryptProvAlt), algId, 0, 0,
+                &hHash) == FALSE)
+            {
+                ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
+                __leave;
+            }
         }
 
         // Copy hash and signedHash from Java to native buffer
@@ -616,6 +669,9 @@
 
     __finally
     {
+        if (hCryptProvAlt)
+            ::CryptReleaseContext(hCryptProvAlt, 0);
+
         if (pSignedHashBuffer)
             delete [] pSignedHashBuffer;
 
@@ -648,15 +704,27 @@
         pszKeyContainerName = env->GetStringUTFChars(keyContainerName, NULL);
 
         // Acquire a CSP context (create a new key container).
+        // Prefer a PROV_RSA_AES CSP, when available, due to its support
+        // for SHA-2-based signatures.
         if (::CryptAcquireContext(
             &hCryptProv,
             pszKeyContainerName,
             NULL,
-            PROV_RSA_FULL,
+            PROV_RSA_AES,
             CRYPT_NEWKEYSET) == FALSE)
         {
-            ThrowException(env, KEY_EXCEPTION, GetLastError());
-            __leave;
+            // Failover to using the default CSP (PROV_RSA_FULL)
+
+            if (::CryptAcquireContext(
+                &hCryptProv,
+                pszKeyContainerName,
+                NULL,
+                PROV_RSA_FULL,
+                CRYPT_NEWKEYSET) == FALSE)
+            {
+                ThrowException(env, KEY_EXCEPTION, GetLastError());
+                __leave;
+            }
         }
 
         // Generate an RSA keypair
@@ -1849,15 +1917,27 @@
         pbKeyBlob = (BYTE *) env->GetByteArrayElements(keyBlob, 0);
 
         // Acquire a CSP context (create a new key container).
+        // Prefer a PROV_RSA_AES CSP, when available, due to its support
+        // for SHA-2-based signatures.
         if (::CryptAcquireContext(
             &hCryptProv,
             NULL,
             NULL,
-            PROV_RSA_FULL,
+            PROV_RSA_AES,
             CRYPT_VERIFYCONTEXT) == FALSE)
         {
-            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
-            __leave;
+            // Failover to using the default CSP (PROV_RSA_FULL)
+
+            if (::CryptAcquireContext(
+                &hCryptProv,
+                NULL,
+                NULL,
+                PROV_RSA_FULL,
+                CRYPT_VERIFYCONTEXT) == FALSE)
+            {
+                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
+                __leave;
+            }
         }
 
         // Import the public key
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/mscapi/SignUsingSHA2withRSA.java	Wed Apr 27 20:21:32 2011 +0100
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2011, 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.
+ *
+ * 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.
+ */
+
+/**
+ * @see SignUsingSHA2withRSA.sh
+ */
+
+import java.security.*;
+import java.util.*;
+
+public class SignUsingSHA2withRSA {
+
+    private static final byte[] toBeSigned = new byte[] {
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10
+    };
+
+    private static List<byte[]> generatedSignatures = new ArrayList<>();
+
+    public static void main(String[] args) throws Exception {
+
+        Provider[] providers = Security.getProviders("Signature.SHA256withRSA");
+        if (providers == null) {
+            System.out.println("No JCE providers support the " +
+                "'Signature.SHA256withRSA' algorithm");
+            System.out.println("Skipping this test...");
+            return;
+
+        } else {
+            System.out.println("The following JCE providers support the " +
+                "'Signature.SHA256withRSA' algorithm: ");
+            for (Provider provider : providers) {
+                System.out.println("    " + provider.getName());
+            }
+        }
+        System.out.println("-------------------------------------------------");
+
+        KeyStore ks = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
+        ks.load(null, null);
+        System.out.println("Loaded keystore: Windows-MY");
+
+        Enumeration e = ks.aliases();
+        PrivateKey privateKey = null;
+        PublicKey publicKey = null;
+
+        while (e.hasMoreElements()) {
+            String alias = (String) e.nextElement();
+            if (alias.equals("6753664")) {
+                System.out.println("Loaded entry: " + alias);
+                privateKey = (PrivateKey) ks.getKey(alias, null);
+                publicKey = (PublicKey) ks.getCertificate(alias).getPublicKey();
+            }
+        }
+        if (privateKey == null || publicKey == null) {
+            throw new Exception("Cannot load the keys need to run this test");
+        }
+        System.out.println("-------------------------------------------------");
+
+        generatedSignatures.add(signUsing("SHA256withRSA", privateKey));
+        generatedSignatures.add(signUsing("SHA384withRSA", privateKey));
+        generatedSignatures.add(signUsing("SHA512withRSA", privateKey));
+
+        System.out.println("-------------------------------------------------");
+
+        verifyUsing("SHA256withRSA", publicKey, generatedSignatures.get(0));
+        verifyUsing("SHA384withRSA", publicKey, generatedSignatures.get(1));
+        verifyUsing("SHA512withRSA", publicKey, generatedSignatures.get(2));
+
+        System.out.println("-------------------------------------------------");
+    }
+
+    private static byte[] signUsing(String signAlgorithm,
+        PrivateKey privateKey) throws Exception {
+
+        // Must explicitly specify the SunMSCAPI JCE provider
+        // (otherwise SunJCE is chosen because it appears earlier in the list)
+        Signature sig1 = Signature.getInstance(signAlgorithm, "SunMSCAPI");
+        if (sig1 == null) {
+            throw new Exception("'" + signAlgorithm + "' is not supported");
+        }
+        System.out.println("Using " + signAlgorithm + " signer from the " +
+            sig1.getProvider().getName() + " JCE provider");
+
+        System.out.println("Using key: " + privateKey);
+        sig1.initSign(privateKey);
+        sig1.update(toBeSigned);
+        byte [] sigBytes = null;
+
+        try {
+            sigBytes = sig1.sign();
+            System.out.println("Generated RSA signature over a " +
+                toBeSigned.length + "-byte data (signature length: " +
+                sigBytes.length * 8 + " bits)");
+            System.out.println(String.format("0x%0" +
+                (sigBytes.length * 2) + "x",
+                new java.math.BigInteger(1, sigBytes)));
+
+        } catch (SignatureException se) {
+                System.out.println("Error generating RSA signature: " + se);
+        }
+
+        return sigBytes;
+    }
+
+    private static void verifyUsing(String signAlgorithm, PublicKey publicKey,
+        byte[] signature) throws Exception {
+
+        // Must explicitly specify the SunMSCAPI JCE provider
+        // (otherwise SunJCE is chosen because it appears earlier in the list)
+        Signature sig1 = Signature.getInstance(signAlgorithm, "SunMSCAPI");
+        if (sig1 == null) {
+            throw new Exception("'" + signAlgorithm + "' is not supported");
+        }
+        System.out.println("Using " + signAlgorithm + " verifier from the "
+            + sig1.getProvider().getName() + " JCE provider");
+
+        System.out.println("Using key: " + publicKey);
+
+        System.out.println("\nVerifying RSA Signature over a " +
+            toBeSigned.length + "-byte data (signature length: " +
+            signature.length * 8 + " bits)");
+        System.out.println(String.format("0x%0" + (signature.length * 2) +
+            "x", new java.math.BigInteger(1, signature)));
+
+        sig1.initVerify(publicKey);
+        sig1.update(toBeSigned);
+
+        if (sig1.verify(signature)) {
+            System.out.println("Verify PASSED\n");
+        } else {
+            throw new Exception("Verify FAILED");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/mscapi/SignUsingSHA2withRSA.sh	Wed Apr 27 20:21:32 2011 +0100
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+#
+# Copyright (c) 2011, 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.
+#
+# 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.
+#
+
+
+# @test
+# @bug 6753664
+# @run shell SignUsingSHA2withRSA.sh
+# @summary Support SHA256 (and higher) in SunMSCAPI
+
+# set a few environment variables so that the shell-script can run stand-alone
+# in the source directory
+if [ "${TESTSRC}" = "" ] ; then
+   TESTSRC="."
+fi
+
+if [ "${TESTCLASSES}" = "" ] ; then
+   TESTCLASSES="."
+fi
+
+if [ "${TESTJAVA}" = "" ] ; then
+   echo "TESTJAVA not set.  Test cannot execute."
+   echo "FAILED!!!"
+   exit 1
+fi
+
+OS=`uname -s`
+case "$OS" in
+    Windows* | CYGWIN* )
+
+        echo "Creating a temporary RSA keypair in the Windows-My store..."
+        ${TESTJAVA}/bin/keytool \
+	    -genkeypair \
+	    -storetype Windows-My \
+	    -keyalg RSA \
+	    -alias 6753664 \
+	    -dname "cn=6753664,c=US" \
+	    -noprompt
+
+        echo
+	echo "Running the test..."
+        ${TESTJAVA}/bin/javac -d . ${TESTSRC}\\SignUsingSHA2withRSA.java
+        ${TESTJAVA}/bin/java SignUsingSHA2withRSA
+
+        rc=$?
+
+        echo
+        echo "Removing the temporary RSA keypair from the Windows-My store..."
+        ${TESTJAVA}/bin/keytool \
+	    -delete \
+	    -storetype Windows-My \
+	    -alias 6753664
+
+	echo done.
+        exit $rc
+        ;;
+
+    * )
+        echo "This test is not intended for '$OS' - passing test"
+        exit 0
+        ;;
+esac