[GCrypt] Implement CryptoKeyRSA PKCS#8 exports
authorzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 15 Jul 2017 05:23:19 +0000 (05:23 +0000)
committerzandobersek@gmail.com <zandobersek@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 15 Jul 2017 05:23:19 +0000 (05:23 +0000)
https://bugs.webkit.org/show_bug.cgi?id=173697

Reviewed by Jiewen Tan.

Source/WebCore:

Implement the PKCS#8 import operation for RSA keys for platforms that use
libgcrypt.

In CryptoKeyRSA::exportPkcs8(), we bail early with an invalid access exception if
this export is not being done for a private key. Otherwise, we start with creating
the `RSAPrivateKey` ASN.1 structure, writing out '0' under the `version` element
and then retrieving the modulus, public and private exponent and both primes.
MPI data for those parameters is written out into corresponding elements in the
`RSAPrivateKey` structure. We then manually compute values of both exponents and
the coefficient parameters, using the private exponent's and both primes' MPI
values. The p and q parameters (i.e. the primes) are switched in libgcrypt,
deviating from the standard practice, so we have to operate with those two
accordingly. We eliminate the optional `otherPrimeInfos` attribute on the
`RSAPrivateKey` structure. Support for this attribute will be added later.

We then create the `PrivateKeyInfo` ASN.1 structure, and write out '0' under the
`version` element. The id-rsaEncryption object identifier is written out under
the `algorithm.algorithm` element. In the future, an object identifier that
matches this key's algorithm will have to be written out here (id-RSASSA-PSS or
id-RSAES-OAEP), along with the appropriate parameters structure, but no test in
WebKit or the web-platform-tests suite covers this detail. For now, a null value
is written out under the `algorithm.parameters` element.

Data for the `RSAPrivateKey` structure is retrieved and written out under the
`privateKey` element.  The optional `attributes` element on the `PrivateKeyInfo`
structure is eliminated.

Data that was encoded through the `PrivateKeyInfo` structure is then retrieved
and returned from the exportPkcs8() method.

No new tests -- related tests are now passing and are unskipped.

* crypto/gcrypt/CryptoKeyRSAGCrypt.cpp:
(WebCore::CryptoKeyRSA::exportPkcs8):

LayoutTests:

* platform/gtk/TestExpectations: Unskip the RSA PKCS#8 export tests

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

LayoutTests/ChangeLog
LayoutTests/platform/gtk/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/crypto/gcrypt/CryptoKeyRSAGCrypt.cpp

index 4800b23..fd8bb1f 100644 (file)
@@ -1,5 +1,14 @@
 2017-07-14  Zan Dobersek  <zdobersek@igalia.com>
 
+        [GCrypt] Implement CryptoKeyRSA PKCS#8 exports
+        https://bugs.webkit.org/show_bug.cgi?id=173697
+
+        Reviewed by Jiewen Tan.
+
+        * platform/gtk/TestExpectations: Unskip the RSA PKCS#8 export tests
+
+2017-07-14  Zan Dobersek  <zdobersek@igalia.com>
+
         [GCrypt] Implement CryptoKeyRSA PKCS#8 imports
         https://bugs.webkit.org/show_bug.cgi?id=173696
 
index 19066fd..b8f8fec 100644 (file)
@@ -766,17 +766,12 @@ webkit.org/b/133122 crypto/subtle/ec-import-pkcs8-key-export-jwk-key.html [ Skip
 webkit.org/b/133122 crypto/subtle/ec-import-pkcs8-key-export-pkcs8-key-p256.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/ec-import-pkcs8-key-export-pkcs8-key-p384.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-export-key-malformed-parameters.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/rsa-import-jwk-key-export-pkcs8-key.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-import-pkcs8-key-export-pkcs8-key.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-import-spki-key-export-spki-key.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-indexeddb-non-exportable-private.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-indexeddb-non-exportable.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-indexeddb-private.html [ Skip ]
 webkit.org/b/133122 crypto/subtle/rsa-indexeddb.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/rsa-oaep-generate-export-key-pkcs8.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/rsa-pss-generate-export-key-pkcs8.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/rsaes-pkcs1-v1_5-generate-export-key-pkcs8.html [ Skip ]
-webkit.org/b/133122 crypto/subtle/rsassa-pkcs1-v1_5-generate-export-key-pkcs8.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/aes-cfb-import-key-decrypt.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/aes-cfb-import-key-encrypt.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/aes-cfb-import-key-unwrap-key.html [ Skip ]
@@ -788,7 +783,6 @@ webkit.org/b/133122 crypto/workers/subtle/hmac-postMessage-worker.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/hrsa-postMessage-worker.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/multiple-postMessage-worker.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/raw-postMessage-worker.html [ Skip ]
-webkit.org/b/133122 crypto/workers/subtle/rsa-export-pkcs8-key.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/rsa-postMessage-worker.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/rsa-pss-import-key-sign.html [ Skip ]
 webkit.org/b/133122 crypto/workers/subtle/rsa-pss-import-key-verify.html [ Skip ]
index 743f8b4..eab7df5 100644 (file)
@@ -1,5 +1,47 @@
 2017-07-14  Zan Dobersek  <zdobersek@igalia.com>
 
+        [GCrypt] Implement CryptoKeyRSA PKCS#8 exports
+        https://bugs.webkit.org/show_bug.cgi?id=173697
+
+        Reviewed by Jiewen Tan.
+
+        Implement the PKCS#8 import operation for RSA keys for platforms that use
+        libgcrypt.
+
+        In CryptoKeyRSA::exportPkcs8(), we bail early with an invalid access exception if
+        this export is not being done for a private key. Otherwise, we start with creating
+        the `RSAPrivateKey` ASN.1 structure, writing out '0' under the `version` element
+        and then retrieving the modulus, public and private exponent and both primes.
+        MPI data for those parameters is written out into corresponding elements in the
+        `RSAPrivateKey` structure. We then manually compute values of both exponents and
+        the coefficient parameters, using the private exponent's and both primes' MPI
+        values. The p and q parameters (i.e. the primes) are switched in libgcrypt,
+        deviating from the standard practice, so we have to operate with those two
+        accordingly. We eliminate the optional `otherPrimeInfos` attribute on the
+        `RSAPrivateKey` structure. Support for this attribute will be added later.
+
+        We then create the `PrivateKeyInfo` ASN.1 structure, and write out '0' under the
+        `version` element. The id-rsaEncryption object identifier is written out under
+        the `algorithm.algorithm` element. In the future, an object identifier that
+        matches this key's algorithm will have to be written out here (id-RSASSA-PSS or
+        id-RSAES-OAEP), along with the appropriate parameters structure, but no test in
+        WebKit or the web-platform-tests suite covers this detail. For now, a null value
+        is written out under the `algorithm.parameters` element.
+
+        Data for the `RSAPrivateKey` structure is retrieved and written out under the
+        `privateKey` element.  The optional `attributes` element on the `PrivateKeyInfo`
+        structure is eliminated.
+
+        Data that was encoded through the `PrivateKeyInfo` structure is then retrieved
+        and returned from the exportPkcs8() method.
+
+        No new tests -- related tests are now passing and are unskipped.
+
+        * crypto/gcrypt/CryptoKeyRSAGCrypt.cpp:
+        (WebCore::CryptoKeyRSA::exportPkcs8):
+
+2017-07-14  Zan Dobersek  <zdobersek@igalia.com>
+
         [GCrypt] Implement CryptoKeyRSA PKCS#8 imports
         https://bugs.webkit.org/show_bug.cgi?id=173696
 
index 0a38ae6..9904a29 100644 (file)
@@ -488,9 +488,141 @@ ExceptionOr<Vector<uint8_t>> CryptoKeyRSA::exportSpki() const
 
 ExceptionOr<Vector<uint8_t>> CryptoKeyRSA::exportPkcs8() const
 {
-    notImplemented();
+    if (type() != CryptoKeyType::Private)
+        return Exception { INVALID_ACCESS_ERR };
+
+    PAL::TASN1::Structure rsaPrivateKey;
+    {
+        // Create the `RSAPrivateKey` structure.
+        if (!PAL::TASN1::createStructure("WebCrypto.RSAPrivateKey", &rsaPrivateKey))
+            return Exception { OperationError };
+
+        // Write out '0' under `version`.
+        if (!PAL::TASN1::writeElement(rsaPrivateKey, "version", "0", 0))
+            return Exception { OperationError };
+
+        // Retrieve the `n`, `e`, `d`, `q` and `p` s-expression tokens. libgcrypt swaps the usage of
+        // the p and q primes internally, so we adjust the lookup accordingly.
+        PAL::GCrypt::Handle<gcry_sexp_t> nSexp(gcry_sexp_find_token(m_platformKey, "n", 0));
+        PAL::GCrypt::Handle<gcry_sexp_t> eSexp(gcry_sexp_find_token(m_platformKey, "e", 0));
+        PAL::GCrypt::Handle<gcry_sexp_t> dSexp(gcry_sexp_find_token(m_platformKey, "d", 0));
+        PAL::GCrypt::Handle<gcry_sexp_t> pSexp(gcry_sexp_find_token(m_platformKey, "q", 0));
+        PAL::GCrypt::Handle<gcry_sexp_t> qSexp(gcry_sexp_find_token(m_platformKey, "p", 0));
+        if (!nSexp || !eSexp || !dSexp || !pSexp || !qSexp)
+            return Exception { OperationError };
+
+        // Write the MPI data of retrieved s-expression tokens under `modulus`, `publicExponent`,
+        // `privateExponent`, `prime1` and `prime2`.
+        {
+            auto modulus = mpiSignedData(nSexp);
+            auto publicExponent = mpiSignedData(eSexp);
+            auto privateExponent = mpiSignedData(dSexp);
+            auto prime1 = mpiSignedData(pSexp);
+            auto prime2 = mpiSignedData(qSexp);
+            if (!modulus || !publicExponent || !privateExponent || !prime1 || !prime2)
+                return Exception { OperationError };
+
+            if (!PAL::TASN1::writeElement(rsaPrivateKey, "modulus", modulus->data(), modulus->size())
+                || !PAL::TASN1::writeElement(rsaPrivateKey, "publicExponent", publicExponent->data(), publicExponent->size())
+                || !PAL::TASN1::writeElement(rsaPrivateKey, "privateExponent", privateExponent->data(), privateExponent->size())
+                || !PAL::TASN1::writeElement(rsaPrivateKey, "prime1", prime1->data(), prime1->size())
+                || !PAL::TASN1::writeElement(rsaPrivateKey, "prime2", prime2->data(), prime2->size()))
+                return Exception { OperationError };
+        }
+
+        // Manually compute the MPI values for the `exponent1`, `exponent2` and `coefficient`
+        // parameters. Again note the swapped usage of the `p` and `q` s-expression parameters.
+        {
+            PAL::GCrypt::Handle<gcry_mpi_t> dMPI(gcry_sexp_nth_mpi(dSexp, 1, GCRYMPI_FMT_USG));
+            PAL::GCrypt::Handle<gcry_mpi_t> pMPI(gcry_sexp_nth_mpi(pSexp, 1, GCRYMPI_FMT_USG));
+            PAL::GCrypt::Handle<gcry_mpi_t> qMPI(gcry_sexp_nth_mpi(qSexp, 1, GCRYMPI_FMT_USG));
+            if (!dMPI || !pMPI || !qMPI)
+                return Exception { OperationError };
+
+            // `exponent1`
+            {
+                PAL::GCrypt::Handle<gcry_mpi_t> dpMPI(gcry_mpi_set_ui(nullptr, 0));
+                PAL::GCrypt::Handle<gcry_mpi_t> pm1MPI(gcry_mpi_set(nullptr, pMPI));
+                gcry_mpi_sub_ui(pm1MPI, pm1MPI, 1);
+                gcry_mpi_mod(dpMPI, dMPI, pm1MPI);
+
+                auto dp = mpiSignedData(dpMPI);
+                if (!dp || !PAL::TASN1::writeElement(rsaPrivateKey, "exponent1", dp->data(), dp->size()))
+                    return Exception { OperationError };
+            }
+
+            // `exponent2`
+            {
+                PAL::GCrypt::Handle<gcry_mpi_t> dqMPI(gcry_mpi_set_ui(nullptr, 0));
+                PAL::GCrypt::Handle<gcry_mpi_t> qm1MPI(gcry_mpi_set(nullptr, qMPI));
+                gcry_mpi_sub_ui(qm1MPI, qm1MPI, 1);
+                gcry_mpi_mod(dqMPI, dMPI, qm1MPI);
+
+                auto dq = mpiSignedData(dqMPI);
+                if (!dq || !PAL::TASN1::writeElement(rsaPrivateKey, "exponent2", dq->data(), dq->size()))
+                    return Exception { OperationError };
+            }
+
+            // `coefficient`
+            {
+                PAL::GCrypt::Handle<gcry_mpi_t> qiMPI(gcry_mpi_set_ui(nullptr, 0));
+                gcry_mpi_invm(qiMPI, qMPI, pMPI);
+
+                auto qi = mpiSignedData(qiMPI);
+                if (!qi || !PAL::TASN1::writeElement(rsaPrivateKey, "coefficient", qi->data(), qi->size()))
+                    return Exception { OperationError };
+            }
+        }
+
+        // Eliminate the optional `otherPrimeInfos` element.
+        // FIXME: this should be supported in the future, if there is such information available.
+        if (!PAL::TASN1::writeElement(rsaPrivateKey, "otherPrimeInfos", nullptr, 0))
+            return Exception { OperationError };
+    }
+
+    PAL::TASN1::Structure pkcs8;
+    {
+        // Create the `PrivateKeyInfo` structure.
+        if (!PAL::TASN1::createStructure("WebCrypto.PrivateKeyInfo", &pkcs8))
+            return Exception { OperationError };
+
+        // Write out '0' under `version`.
+        if (!PAL::TASN1::writeElement(pkcs8, "version", "0", 0))
+            return Exception { OperationError };
+
+        // Write out the id-rsaEncryption identifier under `algorithm.algorithm`.
+        // FIXME: In case the key algorithm is:
+        // - RSA-PSS:
+        //     - this should write out id-RSASSA-PSS, along with setting `algorithm.parameters`
+        //       to a RSASSA-PSS-params structure
+        // - RSA-OAEP:
+        //     - this should write out id-RSAES-OAEP, along with setting `algorithm.parameters`
+        //       to a RSAES-OAEP-params structure
+        if (!PAL::TASN1::writeElement(pkcs8, "privateKeyAlgorithm.algorithm", "1.2.840.113549.1.1.1", 1))
+            return Exception { OperationError };
+
+        // Write out a null value under `algorithm.parameters`.
+        if (!PAL::TASN1::writeElement(pkcs8, "privateKeyAlgorithm.parameters", "\x05\x00", 2))
+            return Exception { OperationError };
+
+        // Write out the `RSAPrivateKey` data under `privateKey`.
+        {
+            auto data = PAL::TASN1::encodedData(rsaPrivateKey, "");
+            if (!data || !PAL::TASN1::writeElement(pkcs8, "privateKey", data->data(), data->size()))
+                return Exception { OperationError };
+        }
 
-    return Exception { NOT_SUPPORTED_ERR };
+        // Eliminate the optional `attributes` element.
+        if (!PAL::TASN1::writeElement(pkcs8, "attributes", nullptr, 0))
+            return Exception { OperationError };
+    }
+
+    // Retrieve the encoded `PrivateKeyInfo` data and return it.
+    auto result = PAL::TASN1::encodedData(pkcs8, "");
+    if (!result)
+        return Exception { OperationError };
+
+    return WTFMove(result.value());
 }
 
 std::unique_ptr<KeyAlgorithm> CryptoKeyRSA::buildAlgorithm() const