Support exporting public RSASSA-PKCS1-v1_5 keys
authorap@apple.com <ap@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Nov 2013 08:42:41 +0000 (08:42 +0000)
committerap@apple.com <ap@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Nov 2013 08:42:41 +0000 (08:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=124475

Reviewed by Sam Weinig.

Source/WebCore:

Test: crypto/subtle/rsa-export-key.html

* bindings/js/JSCryptoKeySerializationJWK.h:
* bindings/js/JSCryptoKeySerializationJWK.cpp:
(WebCore::JSCryptoKeySerializationJWK::buildJSONForRSAComponents):
(WebCore::JSCryptoKeySerializationJWK::addJWKAlgorithmToJSON):
(WebCore::JSCryptoKeySerializationJWK::serialize):
Added said support (this part works with private keys too).

* crypto/keys/CryptoKeyRSA.h:
* crypto/mac/CryptoKeyRSAMac.cpp:
(WebCore::CryptoKeyRSA::getPublicKeyComponents): Moved the logic for getting a
public key from private one here for reuse in keySizeInBits().
(WebCore::CryptoKeyRSA::isRestrictedToHash):
(WebCore::CryptoKeyRSA::keySizeInBits):
(WebCore::CryptoKeyRSA::exportData):
Exposed information necessary for JWK serialization.

LayoutTests:

* crypto/subtle/rsa-export-key-expected.txt: Added.
* crypto/subtle/rsa-export-key.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@159403 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/crypto/subtle/rsa-export-key-expected.txt [new file with mode: 0644]
LayoutTests/crypto/subtle/rsa-export-key.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSCryptoKeySerializationJWK.cpp
Source/WebCore/bindings/js/JSCryptoKeySerializationJWK.h
Source/WebCore/crypto/keys/CryptoKeyRSA.h
Source/WebCore/crypto/mac/CryptoKeyRSAMac.cpp

index 8cc30b5..4dbdfe2 100644 (file)
@@ -1,3 +1,13 @@
+2013-11-17  Alexey Proskuryakov  <ap@apple.com>
+
+        Support exporting public RSASSA-PKCS1-v1_5 keys
+        https://bugs.webkit.org/show_bug.cgi?id=124475
+
+        Reviewed by Sam Weinig.
+
+        * crypto/subtle/rsa-export-key-expected.txt: Added.
+        * crypto/subtle/rsa-export-key.html: Added.
+
 2013-11-18  Zan Dobersek  <zdobersek@igalia.com>
 
         Unreviewed GTK gardening. Rebaselining CSS tests' expectations.
diff --git a/LayoutTests/crypto/subtle/rsa-export-key-expected.txt b/LayoutTests/crypto/subtle/rsa-export-key-expected.txt
new file mode 100644 (file)
index 0000000..b3077e5
--- /dev/null
@@ -0,0 +1,24 @@
+Test exporting an RSA key.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Importing a JWK key...
+PASS crypto.subtle.exportKey(null, key) threw exception TypeError: Unknown key format.
+PASS crypto.subtle.exportKey(undefined, key) threw exception TypeError: Unknown key format.
+PASS crypto.subtle.exportKey({}, key) threw exception TypeError: Unknown key format.
+PASS crypto.subtle.exportKey("", key) threw exception TypeError: Unknown key format.
+PASS crypto.subtle.exportKey("foobar", key) threw exception TypeError: Unknown key format.
+
+Exporting the key as JWK...
+PASS exportedJWK.kty is 'RSA'
+PASS exportedJWK.n is publicKeyJSON.n
+PASS exportedJWK.e is publicKeyJSON.e
+PASS exportedJWK.alg is 'RS256'
+PASS exportedJWK.extractable is true
+PASS exportedJWK.use is 'sig'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/crypto/subtle/rsa-export-key.html b/LayoutTests/crypto/subtle/rsa-export-key.html
new file mode 100644 (file)
index 0000000..6d457ed
--- /dev/null
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="resources/common.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+description("Test exporting an RSA key.");
+
+jsTestIsAsync = true;
+
+var extractable = true;
+var nonExtractable = false;
+
+var publicKeyJSON = {
+    kty: "RSA",
+    alg: "RS256",
+    n: "rcCUCv7Oc1HVam1DIhCzqknThWawOp8QLk8Ziy2p10ByjQFCajoFiyuAWl-R1WXZaf4xitLRracT9agpzIzc-MbLSHIGgWQGO21lGiImy5ftZ-D8bHAqRz2y15pzD4c4CEou7XSSLDoRnR0QG5MsDhD6s2gV9mwHkrtkCxtMWdBi-77as8wGmlNRldcOSgZDLK8UnCSgA1OguZ989bFyc8tOOEIb0xUSfPSz3LPSCnyYz68aDjmKVeNH-ig857OScyWbGyEy3Biw64qun3juUlNWsJ3zngkOdteYWytx5Qr4XKNs6R-Myyq72KUp02mJDZiiyiglxML_i3-_CeecCw",
+    e: "AQAB"
+};
+
+var jwkKeyAsArrayBuffer = asciiToUint8Array(JSON.stringify(publicKeyJSON));
+
+debug("\nImporting a JWK key...");
+crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, "RSASSA-PKCS1-v1_5", extractable, ['sign', 'verify']).then(function(result) {
+    key = result;
+
+    shouldThrow('crypto.subtle.exportKey(null, key)');
+    shouldThrow('crypto.subtle.exportKey(undefined, key)');
+    shouldThrow('crypto.subtle.exportKey({}, key)');
+    shouldThrow('crypto.subtle.exportKey("", key)');
+    shouldThrow('crypto.subtle.exportKey("foobar", key)');
+
+    debug("\nExporting the key as JWK...");
+    return crypto.subtle.exportKey("jwk", key);
+}).then(function(result) {
+    exportedJWK = JSON.parse(bytesToASCIIString(result));
+
+    shouldBe("exportedJWK.kty", "'RSA'");
+    shouldBe("exportedJWK.n", "publicKeyJSON.n");
+    shouldBe("exportedJWK.e", "publicKeyJSON.e");
+    shouldBe("exportedJWK.alg", "'RS256'");
+    shouldBe("exportedJWK.extractable", "true");
+    shouldBe("exportedJWK.use", "'sig'");
+
+    finishJSTest();
+});
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 81e2474..cb8d239 100644 (file)
@@ -1,5 +1,30 @@
 2013-11-17  Alexey Proskuryakov  <ap@apple.com>
 
+        Support exporting public RSASSA-PKCS1-v1_5 keys
+        https://bugs.webkit.org/show_bug.cgi?id=124475
+
+        Reviewed by Sam Weinig.
+
+        Test: crypto/subtle/rsa-export-key.html
+
+        * bindings/js/JSCryptoKeySerializationJWK.h:
+        * bindings/js/JSCryptoKeySerializationJWK.cpp:
+        (WebCore::JSCryptoKeySerializationJWK::buildJSONForRSAComponents):
+        (WebCore::JSCryptoKeySerializationJWK::addJWKAlgorithmToJSON):
+        (WebCore::JSCryptoKeySerializationJWK::serialize):
+        Added said support (this part works with private keys too).
+
+        * crypto/keys/CryptoKeyRSA.h:
+        * crypto/mac/CryptoKeyRSAMac.cpp:
+        (WebCore::CryptoKeyRSA::getPublicKeyComponents): Moved the logic for getting a
+        public key from private one here for reuse in keySizeInBits().
+        (WebCore::CryptoKeyRSA::isRestrictedToHash):
+        (WebCore::CryptoKeyRSA::keySizeInBits):
+        (WebCore::CryptoKeyRSA::exportData):
+        Exposed information necessary for JWK serialization.
+
+2013-11-17  Alexey Proskuryakov  <ap@apple.com>
+
         RSASSA-PKCS1-v1_5 JWK import doesn't check key size
         https://bugs.webkit.org/show_bug.cgi?id=124472
 
index 4ff8156..eb0e90c 100644 (file)
@@ -37,6 +37,7 @@
 #include "CryptoKeyDataOctetSequence.h"
 #include "CryptoKeyDataRSAComponents.h"
 #include "CryptoKeyHMAC.h"
+#include "CryptoKeyRSA.h"
 #include "ExceptionCode.h"
 #include "JSDOMBinding.h"
 #include <heap/StrongInlines.h>
@@ -433,6 +434,40 @@ void JSCryptoKeySerializationJWK::buildJSONForOctetSequence(ExecState* exec, con
     addToJSON(exec, result, "k", base64URLEncode(keyData));
 }
 
+void JSCryptoKeySerializationJWK::buildJSONForRSAComponents(JSC::ExecState* exec, const CryptoKeyDataRSAComponents& data, JSC::JSObject* result)
+{
+    addToJSON(exec, result, "kty", "RSA");
+    addToJSON(exec, result, "n", base64URLEncode(data.modulus()));
+    addToJSON(exec, result, "e", base64URLEncode(data.exponent()));
+
+    if (data.type() == CryptoKeyDataRSAComponents::Type::Public)
+        return;
+
+    addToJSON(exec, result, "d", base64URLEncode(data.privateExponent()));
+
+    if (!data.hasAdditionalPrivateKeyParameters())
+        return;
+
+    addToJSON(exec, result, "p", base64URLEncode(data.firstPrimeInfo().primeFactor));
+    addToJSON(exec, result, "q", base64URLEncode(data.secondPrimeInfo().primeFactor));
+    addToJSON(exec, result, "dp", base64URLEncode(data.firstPrimeInfo().factorCRTExponent));
+    addToJSON(exec, result, "dq", base64URLEncode(data.secondPrimeInfo().factorCRTExponent));
+    addToJSON(exec, result, "qi", base64URLEncode(data.secondPrimeInfo().factorCRTCoefficient));
+
+    if (data.otherPrimeInfos().isEmpty())
+        return;
+
+    JSArray* oth = constructEmptyArray(exec, 0, exec->lexicalGlobalObject(), data.otherPrimeInfos().size());
+    for (size_t i = 0, size = data.otherPrimeInfos().size(); i < size; ++i) {
+        JSObject* jsPrimeInfo = constructEmptyObject(exec);
+        addToJSON(exec, jsPrimeInfo, "r", base64URLEncode(data.otherPrimeInfos()[i].primeFactor));
+        addToJSON(exec, jsPrimeInfo, "d", base64URLEncode(data.otherPrimeInfos()[i].factorCRTExponent));
+        addToJSON(exec, jsPrimeInfo, "t", base64URLEncode(data.otherPrimeInfos()[i].factorCRTCoefficient));
+        oth->putDirectIndex(exec, i, jsPrimeInfo);
+    }
+    result->putDirect(exec->vm(), Identifier(exec, "oth"), oth);
+}
+
 void JSCryptoKeySerializationJWK::addToJSON(ExecState* exec, JSObject* json, const char* key, const String& value)
 {
     VM& vm = exec->vm();
@@ -482,6 +517,28 @@ void JSCryptoKeySerializationJWK::addJWKAlgorithmToJSON(ExecState* exec, JSObjec
             break;
         }
         break;
+    case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: {
+        const CryptoKeyRSA& rsaKey = toCryptoKeyRSA(key);
+        CryptoAlgorithmIdentifier hash;
+        if (!rsaKey.isRestrictedToHash(hash))
+            break;
+        if (rsaKey.keySizeInBits() < 2048)
+            break;
+        switch (hash) {
+        case CryptoAlgorithmIdentifier::SHA_256:
+            jwkAlgorithm = "RS256";
+            break;
+        case CryptoAlgorithmIdentifier::SHA_384:
+            jwkAlgorithm = "RS384";
+            break;
+        case CryptoAlgorithmIdentifier::SHA_512:
+            jwkAlgorithm = "RS512";
+            break;
+        default:
+            break;
+        }
+        break;
+    }
     default:
         break;
     }
@@ -534,11 +591,14 @@ String JSCryptoKeySerializationJWK::serialize(ExecState* exec, const CryptoKey&
 
     if (isCryptoKeyDataOctetSequence(*keyData))
         buildJSONForOctetSequence(exec, toCryptoKeyDataOctetSequence(*keyData).octetSequence(), result);
+    else if (isCryptoKeyDataRSAComponents(*keyData))
+        buildJSONForRSAComponents(exec, toCryptoKeyDataRSAComponents(*keyData), result);
     else {
         throwTypeError(exec, "Key doesn't support exportKey");
         return String();
     }
-    ASSERT(!exec->hadException());
+    if (exec->hadException())
+        return String();
 
     return JSONStringify(exec, result, 4);
 }
index 2e1b459..bc18f3c 100644 (file)
@@ -42,6 +42,7 @@ namespace WebCore {
 
 class CryptoAlgorithmParameters;
 class CryptoKey;
+class CryptoKeyDataRSAComponents;
 
 class JSCryptoKeySerializationJWK FINAL : public CryptoKeySerialization {
 WTF_MAKE_NONCOPYABLE(JSCryptoKeySerializationJWK);
@@ -66,6 +67,7 @@ private:
     virtual std::unique_ptr<CryptoKeyData> keyData() const OVERRIDE;
 
     static void buildJSONForOctetSequence(JSC::ExecState*, const Vector<uint8_t>&, JSC::JSObject* result);
+    static void buildJSONForRSAComponents(JSC::ExecState*, const CryptoKeyDataRSAComponents&, JSC::JSObject* result);
     static void addJWKAlgorithmToJSON(JSC::ExecState*, JSC::JSObject*, const CryptoKey& key);
     static void addJWKUseToJSON(JSC::ExecState*, JSC::JSObject*, CryptoKeyUsage);
     static void addToJSON(JSC::ExecState*, JSC::JSObject*, const char* key, const String& value);
index b3641a0..0434dcb 100644 (file)
@@ -51,16 +51,19 @@ public:
     virtual ~CryptoKeyRSA();
 
     void restrictToHash(CryptoAlgorithmIdentifier);
+    bool isRestrictedToHash(CryptoAlgorithmIdentifier&) const;
 
-    static void generatePair(CryptoAlgorithmIdentifier, unsigned modulusLength, const Vector<uint8_t>& publicExponent, bool extractable, CryptoKeyUsage, std::unique_ptr<PromiseWrapper>);
+    size_t keySizeInBits() const;
 
-    virtual CryptoKeyClass keyClass() const OVERRIDE { return CryptoKeyClass::RSA; }
+    static void generatePair(CryptoAlgorithmIdentifier, unsigned modulusLength, const Vector<uint8_t>& publicExponent, bool extractable, CryptoKeyUsage, std::unique_ptr<PromiseWrapper>);
 
     PlatformRSAKey platformKey() const { return m_platformKey; }
 
 private:
     CryptoKeyRSA(CryptoAlgorithmIdentifier, CryptoKeyType, PlatformRSAKey, bool extractable, CryptoKeyUsage);
 
+    virtual CryptoKeyClass keyClass() const OVERRIDE { return CryptoKeyClass::RSA; }
+
     virtual void buildAlgorithmDescription(CryptoAlgorithmDescriptionBuilder&) const OVERRIDE;
     virtual std::unique_ptr<CryptoKeyData> exportData() const OVERRIDE;
 
index c1a5848..bfe91cc 100644 (file)
@@ -60,13 +60,19 @@ namespace WebCore {
 
 static CCCryptorStatus getPublicKeyComponents(CCRSACryptorRef rsaKey, Vector<uint8_t>& modulus, Vector<uint8_t>& publicExponent)
 {
-    ASSERT(CCRSAGetKeyType(rsaKey) == ccRSAKeyPublic);
+    ASSERT(CCRSAGetKeyType(rsaKey) == ccRSAKeyPublic || CCRSAGetKeyType(rsaKey) == ccRSAKeyPrivate);
+    bool keyIsPublic = CCRSAGetKeyType(rsaKey) == ccRSAKeyPublic;
+    CCRSACryptorRef publicKey = keyIsPublic ? rsaKey : CCRSACryptorGetPublicKeyFromPrivateKey(rsaKey);
 
     modulus.resize(16384);
     size_t modulusLength = modulus.size();
     publicExponent.resize(16384);
     size_t exponentLength = publicExponent.size();
-    CCCryptorStatus status = CCRSAGetKeyComponents(rsaKey, modulus.data(), &modulusLength, publicExponent.data(), &exponentLength, 0, 0, 0, 0);
+    CCCryptorStatus status = CCRSAGetKeyComponents(publicKey, modulus.data(), &modulusLength, publicExponent.data(), &exponentLength, 0, 0, 0, 0);
+    if (!keyIsPublic) {
+        // CCRSACryptorGetPublicKeyFromPrivateKey has "Get" in the name, but its result needs to be released (see <rdar://problem/15449697>).
+        CCRSACryptorRelease(publicKey);
+    }
     if (status)
         return status;
 
@@ -122,21 +128,35 @@ void CryptoKeyRSA::restrictToHash(CryptoAlgorithmIdentifier identifier)
     m_hash = identifier;
 }
 
-void CryptoKeyRSA::buildAlgorithmDescription(CryptoAlgorithmDescriptionBuilder& builder) const
+bool CryptoKeyRSA::isRestrictedToHash(CryptoAlgorithmIdentifier& identifier) const
 {
-    CryptoKey::buildAlgorithmDescription(builder);
+    if (!m_restrictedToSpecificHash)
+        return false;
 
-    ASSERT(CCRSAGetKeyType(m_platformKey) == ccRSAKeyPublic || CCRSAGetKeyType(m_platformKey) == ccRSAKeyPrivate);
-    bool platformKeyIsPublic = CCRSAGetKeyType(m_platformKey) == ccRSAKeyPublic;
-    CCRSACryptorRef publicKey = platformKeyIsPublic ? m_platformKey : CCRSACryptorGetPublicKeyFromPrivateKey(m_platformKey);
+    identifier = m_hash;
+    return true;
+}
 
+size_t CryptoKeyRSA::keySizeInBits() const
+{
     Vector<uint8_t> modulus;
     Vector<uint8_t> publicExponent;
-    CCCryptorStatus status = getPublicKeyComponents(publicKey, modulus, publicExponent);
-    if (!platformKeyIsPublic) {
-        // CCRSACryptorGetPublicKeyFromPrivateKey has "Get" in the name, but its result needs to be released (see <rdar://problem/15449697>).
-        CCRSACryptorRelease(publicKey);
+    CCCryptorStatus status = getPublicKeyComponents(m_platformKey, modulus, publicExponent);
+    if (status) {
+        WTFLogAlways("Couldn't get RSA key components, status %d", status);
+        return 0;
     }
+
+    return modulus.size() * 8;
+}
+
+void CryptoKeyRSA::buildAlgorithmDescription(CryptoAlgorithmDescriptionBuilder& builder) const
+{
+    CryptoKey::buildAlgorithmDescription(builder);
+
+    Vector<uint8_t> modulus;
+    Vector<uint8_t> publicExponent;
+    CCCryptorStatus status = getPublicKeyComponents(m_platformKey, modulus, publicExponent);
     if (status) {
         WTFLogAlways("Couldn't get RSA key components, status %d", status);
         return;
@@ -154,9 +174,24 @@ void CryptoKeyRSA::buildAlgorithmDescription(CryptoAlgorithmDescriptionBuilder&
 
 std::unique_ptr<CryptoKeyData> CryptoKeyRSA::exportData() const
 {
-    // Not implemented yet.
     ASSERT(extractable());
-    return nullptr;
+
+    switch (CCRSAGetKeyType(m_platformKey)) {
+    case ccRSAKeyPublic: {
+        Vector<uint8_t> modulus;
+        Vector<uint8_t> publicExponent;
+        CCCryptorStatus status = getPublicKeyComponents(m_platformKey, modulus, publicExponent);
+        if (status) {
+            WTFLogAlways("Couldn't get RSA key components, status %d", status);
+            return nullptr;
+        }
+        return CryptoKeyDataRSAComponents::createPublic(modulus, publicExponent);
+    }
+    case ccRSAKeyPrivate:
+        // Not supported yet.
+    default:
+        return nullptr;
+    }
 }
 
 static bool bigIntegerToUInt32(const Vector<uint8_t>& bigInteger, uint32_t& result)