Support exporting private WebCrypto RSA keys
authorap@apple.com <ap@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 3 Dec 2013 20:18:14 +0000 (20:18 +0000)
committerap@apple.com <ap@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 3 Dec 2013 20:18:14 +0000 (20:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=124483

Reviewed by Anders Carlsson.

Source/WebCore:

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

It might be better to have our own bignum implementation in WTF, but we currently
don't, and the need for this computation is Common Crypto specific anyway.

* crypto/CommonCryptoUtilities.h:
* crypto/CommonCryptoUtilities.cpp:
(WebCore::CCBigNum::CCBigNum):
(WebCore::CCBigNum::~CCBigNum):
(WebCore::CCBigNum::operator=):
(WebCore::CCBigNum::data):
(WebCore::CCBigNum::operator-):
(WebCore::CCBigNum::operator%):
(WebCore::CCBigNum::inverse):
Added a minimal wrapper around CommonCrypto BigNum.

* crypto/mac/CryptoKeyRSAMac.cpp:
(WebCore::getPrivateKeyComponents): Compute missing parts using CCBigNum.
(WebCore::CryptoKeyRSA::exportData): Implemented private key case.

LayoutTests:

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

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

LayoutTests/ChangeLog
LayoutTests/crypto/subtle/rsa-export-private-key-expected.txt [new file with mode: 0644]
LayoutTests/crypto/subtle/rsa-export-private-key.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/crypto/CommonCryptoUtilities.cpp
Source/WebCore/crypto/CommonCryptoUtilities.h
Source/WebCore/crypto/mac/CryptoKeyRSAMac.cpp

index a10ecbd..f2d1dc6 100644 (file)
@@ -1,5 +1,15 @@
 2013-12-03  Alexey Proskuryakov  <ap@apple.com>
 
+        Support exporting private WebCrypto RSA keys
+        https://bugs.webkit.org/show_bug.cgi?id=124483
+
+        Reviewed by Anders Carlsson.
+
+        * crypto/subtle/rsa-export-private-key-expected.txt: Added.
+        * crypto/subtle/rsa-export-private-key.html: Added.
+
+2013-12-03  Alexey Proskuryakov  <ap@apple.com>
+
         WebCrypto HMAC doesn't check key algorithm's hash
         https://bugs.webkit.org/show_bug.cgi?id=125114
 
diff --git a/LayoutTests/crypto/subtle/rsa-export-private-key-expected.txt b/LayoutTests/crypto/subtle/rsa-export-private-key-expected.txt
new file mode 100644 (file)
index 0000000..83835c9
--- /dev/null
@@ -0,0 +1,26 @@
+Test exporting a private RSA key.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Importing a JWK key...
+
+Exporting the key as JWK...
+PASS exportedJWK.kty is 'RSA'
+PASS exportedJWK.n is privateKeyJSON.n
+PASS exportedJWK.e is privateKeyJSON.e
+PASS exportedJWK.d is privateKeyJSON.d
+PASS exportedJWK.p is privateKeyJSON.p
+PASS exportedJWK.q is privateKeyJSON.q
+PASS exportedJWK.dp is privateKeyJSON.dp
+PASS exportedJWK.dq is privateKeyJSON.dq
+PASS exportedJWK.qi is privateKeyJSON.qi
+PASS exportedJWK.oth is privateKeyJSON.oth
+PASS exportedJWK.alg is privateKeyJSON.alg
+PASS exportedJWK.extractable is true
+PASS exportedJWK.use is 'sig'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/crypto/subtle/rsa-export-private-key.html b/LayoutTests/crypto/subtle/rsa-export-private-key.html
new file mode 100644 (file)
index 0000000..24e262f
--- /dev/null
@@ -0,0 +1,64 @@
+<!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 a private RSA key.");
+
+jsTestIsAsync = true;
+
+var extractable = true;
+var nonExtractable = false;
+
+// Example from JWK specification.
+var privateKeyJSON = {
+    "kty":"RSA",
+    "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
+    "e":"AQAB",
+    "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q",
+    "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs",
+    "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk",
+    "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0",
+    "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk",
+    "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",
+    "alg":"RS256",
+    "kid":"2011-04-29"
+}
+var jwkKeyAsArrayBuffer = asciiToUint8Array(JSON.stringify(privateKeyJSON));
+
+debug("\nImporting a JWK key...");
+crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, "RSASSA-PKCS1-v1_5", extractable, ['sign', 'verify']).then(function(result) {
+    key = result;
+
+    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", "privateKeyJSON.n");
+    shouldBe("exportedJWK.e", "privateKeyJSON.e");
+    shouldBe("exportedJWK.d", "privateKeyJSON.d");
+    shouldBe("exportedJWK.p", "privateKeyJSON.p");
+    shouldBe("exportedJWK.q", "privateKeyJSON.q");
+    shouldBe("exportedJWK.dp", "privateKeyJSON.dp");
+    shouldBe("exportedJWK.dq", "privateKeyJSON.dq");
+    shouldBe("exportedJWK.qi", "privateKeyJSON.qi");
+    shouldBe("exportedJWK.oth", "privateKeyJSON.oth");
+    shouldBe("exportedJWK.alg", "privateKeyJSON.alg");
+    shouldBe("exportedJWK.extractable", "true");
+    shouldBe("exportedJWK.use", "'sig'");
+
+    finishJSTest();
+});
+</script>
+
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 2c5afc4..a89bc77 100644 (file)
@@ -1,3 +1,30 @@
+2013-12-03  Alexey Proskuryakov  <ap@apple.com>
+
+        Support exporting private WebCrypto RSA keys
+        https://bugs.webkit.org/show_bug.cgi?id=124483
+
+        Reviewed by Anders Carlsson.
+
+        Test: crypto/subtle/rsa-export-private-key.html
+
+        It might be better to have our own bignum implementation in WTF, but we currently
+        don't, and the need for this computation is Common Crypto specific anyway.
+
+        * crypto/CommonCryptoUtilities.h:
+        * crypto/CommonCryptoUtilities.cpp:
+        (WebCore::CCBigNum::CCBigNum):
+        (WebCore::CCBigNum::~CCBigNum):
+        (WebCore::CCBigNum::operator=):
+        (WebCore::CCBigNum::data):
+        (WebCore::CCBigNum::operator-):
+        (WebCore::CCBigNum::operator%):
+        (WebCore::CCBigNum::inverse):
+        Added a minimal wrapper around CommonCrypto BigNum.
+
+        * crypto/mac/CryptoKeyRSAMac.cpp:
+        (WebCore::getPrivateKeyComponents): Compute missing parts using CCBigNum.
+        (WebCore::CryptoKeyRSA::exportData): Implemented private key case.
+
 2013-12-03  Ryosuke Niwa  <rniwa@webkit.org>
 
         XML fragment parsing algorithm doesn't use the context element's default namespace URI
index a10bca3..642bdde 100644 (file)
 
 #if ENABLE(SUBTLE_CRYPTO)
 
+#if defined(__has_include)
+#if __has_include(<CommonCrypto/CommonBigNum.h>)
+#include <CommonCrypto/CommonBigNum.h>
+#endif
+#endif
+
+typedef CCCryptorStatus CCStatus;
+extern "C" CCBigNumRef CCBigNumFromData(CCStatus *status, const void *s, size_t len);
+extern "C" size_t CCBigNumToData(CCStatus *status, const CCBigNumRef bn, void *to);
+extern "C" uint32_t CCBigNumByteCount(const CCBigNumRef bn);
+extern "C" CCBigNumRef CCCreateBigNum(CCStatus *status);
+extern "C" void CCBigNumFree(CCBigNumRef bn);
+extern "C" CCBigNumRef CCBigNumCopy(CCStatus *status, const CCBigNumRef bn);
+extern "C" CCStatus CCBigNumSubI(CCBigNumRef result, const CCBigNumRef a, const uint32_t b);
+extern "C" CCStatus CCBigNumMod(CCBigNumRef result, CCBigNumRef dividend, CCBigNumRef modulus);
+extern "C" CCStatus CCBigNumInverseMod(CCBigNumRef result, const CCBigNumRef a, const CCBigNumRef modulus);
+
 namespace WebCore {
 
 bool getCommonCryptoDigestAlgorithm(CryptoAlgorithmIdentifier hashFunction, CCDigestAlgorithm& algorithm)
@@ -53,6 +70,106 @@ bool getCommonCryptoDigestAlgorithm(CryptoAlgorithmIdentifier hashFunction, CCDi
     }
 }
 
+CCBigNum::CCBigNum(CCBigNumRef number)
+    : m_number(number)
+{
+}
+
+CCBigNum::CCBigNum(const uint8_t* data, size_t size)
+{
+    CCStatus status = kCCSuccess;
+    m_number = CCBigNumFromData(&status, data, size);
+    RELEASE_ASSERT(!status);
+}
+
+CCBigNum::~CCBigNum()
+{
+    CCBigNumFree(m_number);
+}
+
+CCBigNum::CCBigNum(const CCBigNum& other)
+{
+    CCStatus status = kCCSuccess;
+    m_number = CCBigNumCopy(&status, other.m_number);
+    RELEASE_ASSERT(!status);
+}
+
+CCBigNum::CCBigNum(CCBigNum&& other)
+{
+    m_number = other.m_number;
+    other.m_number = nullptr;
+}
+
+CCBigNum& CCBigNum::operator=(const CCBigNum& other)
+{
+    if (this == &other)
+        return *this;
+
+    CCBigNumFree(m_number);
+
+    CCStatus status = kCCSuccess;
+    m_number = CCBigNumCopy(&status, other.m_number);
+    RELEASE_ASSERT(!status);
+    return *this;
+}
+
+CCBigNum& CCBigNum::operator=(CCBigNum&& other)
+{
+    if (this == &other)
+        return *this;
+
+    m_number = other.m_number;
+    other.m_number = nullptr;
+
+    return *this;
+}
+
+Vector<uint8_t> CCBigNum::data() const
+{
+    Vector<uint8_t> result(CCBigNumByteCount(m_number));
+    CCStatus status = kCCSuccess;
+    CCBigNumToData(&status, m_number, result.data());
+    RELEASE_ASSERT(!status);
+
+    return result;
+}
+
+CCBigNum CCBigNum::operator-(uint32_t b) const
+{
+    CCStatus status = kCCSuccess;
+    CCBigNumRef result = CCCreateBigNum(&status);
+    RELEASE_ASSERT(!status);
+
+    status = CCBigNumSubI(result, m_number, b);
+    RELEASE_ASSERT(!status);
+
+    return result;
+}
+
+CCBigNum CCBigNum::operator%(const CCBigNum& modulus) const
+{
+    CCStatus status = kCCSuccess;
+    CCBigNumRef result = CCCreateBigNum(&status);
+    RELEASE_ASSERT(!status);
+
+    status = CCBigNumMod(result, m_number, modulus.m_number);
+    RELEASE_ASSERT(!status);
+
+    return result;
+}
+
+CCBigNum CCBigNum::inverse(const CCBigNum& modulus) const
+{
+    CCStatus status = kCCSuccess;
+    CCBigNumRef result = CCCreateBigNum(&status);
+    RELEASE_ASSERT(!status);
+
+    status = CCBigNumInverseMod(result, m_number, modulus.m_number);
+    RELEASE_ASSERT(!status);
+
+    return result;
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(SUBTLE_CRYPTO)
index 8c765e3..62459f6 100644 (file)
@@ -29,8 +29,8 @@
 #if ENABLE(SUBTLE_CRYPTO)
 
 #include "CryptoAlgorithmIdentifier.h"
-
 #include <CommonCrypto/CommonCryptor.h>
+#include <wtf/Vector.h>
 
 #if defined(__has_include)
 #if __has_include(<CommonCrypto/CommonRSACryptor.h>)
@@ -71,6 +71,8 @@ enum {
 };
 #endif
 
+typedef struct _CCBigNumRef *CCBigNumRef;
+
 typedef struct __CCRandom *CCRandomRef;
 extern const CCRandomRef kCCRandomDefault;
 extern "C" int CCRandomCopyBytes(CCRandomRef rnd, void *bytes, size_t count);
@@ -89,6 +91,28 @@ extern "C" CCRSAKeyType CCRSAGetKeyType(CCRSACryptorRef key);
 
 namespace WebCore {
 
+class CCBigNum {
+public:
+    CCBigNum(const uint8_t*, size_t);
+    ~CCBigNum();
+
+    CCBigNum(const CCBigNum&);
+    CCBigNum(CCBigNum&&);
+    CCBigNum& operator=(const CCBigNum&);
+    CCBigNum& operator=(CCBigNum&&);
+
+    Vector<uint8_t> data() const;
+
+    CCBigNum operator-(uint32_t) const;
+    CCBigNum operator%(const CCBigNum&) const;
+    CCBigNum inverse(const CCBigNum& modulus) const;
+
+private:
+    CCBigNum(CCBigNumRef);
+
+    CCBigNumRef m_number;
+};
+
 bool getCommonCryptoDigestAlgorithm(CryptoAlgorithmIdentifier, CCDigestAlgorithm&);
 
 } // namespace WebCore
index 2a7b4b7..3cfa455 100644 (file)
@@ -59,6 +59,42 @@ static CCCryptorStatus getPublicKeyComponents(CCRSACryptorRef rsaKey, Vector<uin
     return status;
 }
 
+static CCCryptorStatus getPrivateKeyComponents(CCRSACryptorRef rsaKey, Vector<uint8_t>& privateExponent, CryptoKeyDataRSAComponents::PrimeInfo& firstPrimeInfo, CryptoKeyDataRSAComponents::PrimeInfo& secondPrimeInfo)
+{
+    ASSERT(CCRSAGetKeyType(rsaKey) == ccRSAKeyPrivate);
+
+    Vector<uint8_t> unusedModulus(16384);
+    size_t modulusLength = unusedModulus.size();
+    privateExponent.resize(16384);
+    size_t exponentLength = privateExponent.size();
+    firstPrimeInfo.primeFactor.resize(16384);
+    size_t pLength = firstPrimeInfo.primeFactor.size();
+    secondPrimeInfo.primeFactor.resize(16384);
+    size_t qLength = secondPrimeInfo.primeFactor.size();
+
+    CCCryptorStatus status = CCRSAGetKeyComponents(rsaKey, unusedModulus.data(), &modulusLength, privateExponent.data(), &exponentLength, firstPrimeInfo.primeFactor.data(), &pLength, secondPrimeInfo.primeFactor.data(), &qLength);
+    if (status)
+        return status;
+
+    privateExponent.shrink(exponentLength);
+    firstPrimeInfo.primeFactor.shrink(pLength);
+    secondPrimeInfo.primeFactor.shrink(qLength);
+
+    CCBigNum d(privateExponent.data(), privateExponent.size());
+    CCBigNum p(firstPrimeInfo.primeFactor.data(), firstPrimeInfo.primeFactor.size());
+    CCBigNum q(secondPrimeInfo.primeFactor.data(), secondPrimeInfo.primeFactor.size());
+
+    CCBigNum dp = d % (p - 1);
+    CCBigNum dq = d % (q - 1);
+    CCBigNum qi = q.inverse(p);
+
+    firstPrimeInfo.factorCRTExponent = dp.data();
+    secondPrimeInfo.factorCRTExponent = dq.data();
+    secondPrimeInfo.factorCRTCoefficient = qi.data();
+
+    return status;
+}
+
 CryptoKeyRSA::CryptoKeyRSA(CryptoAlgorithmIdentifier identifier, CryptoKeyType type, PlatformRSAKey platformKey, bool extractable, CryptoKeyUsage usage)
     : CryptoKey(identifier, type, extractable, usage)
     , m_platformKey(platformKey)
@@ -165,8 +201,25 @@ std::unique_ptr<CryptoKeyData> CryptoKeyRSA::exportData() const
         }
         return CryptoKeyDataRSAComponents::createPublic(modulus, publicExponent);
     }
-    case ccRSAKeyPrivate:
-        // Not supported yet.
+    case ccRSAKeyPrivate: {
+        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;
+        }
+        Vector<uint8_t> privateExponent;
+        CryptoKeyDataRSAComponents::PrimeInfo firstPrimeInfo;
+        CryptoKeyDataRSAComponents::PrimeInfo secondPrimeInfo;
+        Vector<CryptoKeyDataRSAComponents::PrimeInfo> otherPrimeInfos; // Always empty, CommonCrypto only supports two primes (cf. <rdar://problem/15444074>).
+        status = getPrivateKeyComponents(m_platformKey, privateExponent, firstPrimeInfo, secondPrimeInfo);
+        if (status) {
+            WTFLogAlways("Couldn't get RSA key components, status %d", status);
+            return nullptr;
+        }
+        return CryptoKeyDataRSAComponents::createPrivateWithAdditionalData(modulus, publicExponent, privateExponent, firstPrimeInfo, secondPrimeInfo, otherPrimeInfos);
+    }
     default:
         return nullptr;
     }