123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/webcrypto/algorithms/rsa.h"
- #include <utility>
- #include "base/check_op.h"
- #include "base/containers/span.h"
- #include "components/webcrypto/algorithms/asymmetric_key_util.h"
- #include "components/webcrypto/algorithms/util.h"
- #include "components/webcrypto/blink_key_handle.h"
- #include "components/webcrypto/generate_key_result.h"
- #include "components/webcrypto/jwk.h"
- #include "components/webcrypto/status.h"
- #include "crypto/openssl_util.h"
- #include "third_party/blink/public/platform/web_crypto_algorithm_params.h"
- #include "third_party/blink/public/platform/web_crypto_key_algorithm.h"
- #include "third_party/boringssl/src/include/openssl/bn.h"
- #include "third_party/boringssl/src/include/openssl/evp.h"
- #include "third_party/boringssl/src/include/openssl/rsa.h"
- namespace webcrypto {
- namespace {
- // Describes the RSA components for a parsed key. The names of the properties
- // correspond with those from the JWK spec. Note that Chromium's WebCrypto
- // implementation does not support multi-primes, so there is no parsed field
- // for "oth".
- struct JwkRsaInfo {
- bool is_private_key = false;
- std::vector<uint8_t> n;
- std::vector<uint8_t> e;
- std::vector<uint8_t> d;
- std::vector<uint8_t> p;
- std::vector<uint8_t> q;
- std::vector<uint8_t> dp;
- std::vector<uint8_t> dq;
- std::vector<uint8_t> qi;
- };
- // Parses a UTF-8 encoded JWK (key_data), and extracts the RSA components to
- // |*result|. Returns Status::Success() on success, otherwise an error.
- // In order for this to succeed:
- // * expected_alg must match the JWK's "alg", if present.
- // * expected_extractable must be consistent with the JWK's "ext", if
- // present.
- // * expected_usages must be a subset of the JWK's "key_ops" if present.
- Status ReadRsaKeyJwk(base::span<const uint8_t> key_data,
- const std::string& expected_alg,
- bool expected_extractable,
- blink::WebCryptoKeyUsageMask expected_usages,
- JwkRsaInfo* result) {
- JwkReader jwk;
- Status status = jwk.Init(key_data, expected_extractable, expected_usages,
- "RSA", expected_alg);
- if (status.IsError())
- return status;
- // An RSA public key must have an "n" (modulus) and an "e" (exponent) entry
- // in the JWK, while an RSA private key must have those, plus at least a "d"
- // (private exponent) entry.
- // See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-18,
- // section 6.3.
- status = jwk.GetBigInteger("n", &result->n);
- if (status.IsError())
- return status;
- status = jwk.GetBigInteger("e", &result->e);
- if (status.IsError())
- return status;
- result->is_private_key = jwk.HasMember("d");
- if (!result->is_private_key)
- return Status::Success();
- status = jwk.GetBigInteger("d", &result->d);
- if (status.IsError())
- return status;
- // The "p", "q", "dp", "dq", and "qi" properties are optional in the JWA
- // spec. However they are required by Chromium's WebCrypto implementation.
- status = jwk.GetBigInteger("p", &result->p);
- if (status.IsError())
- return status;
- status = jwk.GetBigInteger("q", &result->q);
- if (status.IsError())
- return status;
- status = jwk.GetBigInteger("dp", &result->dp);
- if (status.IsError())
- return status;
- status = jwk.GetBigInteger("dq", &result->dq);
- if (status.IsError())
- return status;
- status = jwk.GetBigInteger("qi", &result->qi);
- if (status.IsError())
- return status;
- return Status::Success();
- }
- // Creates a blink::WebCryptoAlgorithm having the modulus length and public
- // exponent of |key|.
- Status CreateRsaHashedKeyAlgorithm(
- blink::WebCryptoAlgorithmId rsa_algorithm,
- blink::WebCryptoAlgorithmId hash_algorithm,
- EVP_PKEY* key,
- blink::WebCryptoKeyAlgorithm* key_algorithm) {
- DCHECK_EQ(EVP_PKEY_RSA, EVP_PKEY_id(key));
- RSA* rsa = EVP_PKEY_get0_RSA(key);
- if (!rsa)
- return Status::ErrorUnexpected();
- unsigned int modulus_length_bits = BN_num_bits(rsa->n);
- // Convert the public exponent to big-endian representation.
- std::vector<uint8_t> e(BN_num_bytes(rsa->e));
- if (e.size() == 0)
- return Status::ErrorUnexpected();
- if (e.size() != BN_bn2bin(rsa->e, &e[0]))
- return Status::ErrorUnexpected();
- *key_algorithm = blink::WebCryptoKeyAlgorithm::CreateRsaHashed(
- rsa_algorithm, modulus_length_bits, &e[0],
- static_cast<unsigned int>(e.size()), hash_algorithm);
- return Status::Success();
- }
- // Creates a WebCryptoKey that wraps |private_key|.
- Status CreateWebCryptoRsaPrivateKey(
- bssl::UniquePtr<EVP_PKEY> private_key,
- const blink::WebCryptoAlgorithmId rsa_algorithm_id,
- const blink::WebCryptoAlgorithm& hash,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- blink::WebCryptoKey* key) {
- blink::WebCryptoKeyAlgorithm key_algorithm;
- Status status = CreateRsaHashedKeyAlgorithm(
- rsa_algorithm_id, hash.Id(), private_key.get(), &key_algorithm);
- if (status.IsError())
- return status;
- return CreateWebCryptoPrivateKey(std::move(private_key), key_algorithm,
- extractable, usages, key);
- }
- // Creates a WebCryptoKey that wraps |public_key|.
- Status CreateWebCryptoRsaPublicKey(
- bssl::UniquePtr<EVP_PKEY> public_key,
- const blink::WebCryptoAlgorithmId rsa_algorithm_id,
- const blink::WebCryptoAlgorithm& hash,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- blink::WebCryptoKey* key) {
- blink::WebCryptoKeyAlgorithm key_algorithm;
- Status status = CreateRsaHashedKeyAlgorithm(rsa_algorithm_id, hash.Id(),
- public_key.get(), &key_algorithm);
- if (status.IsError())
- return status;
- return CreateWebCryptoPublicKey(std::move(public_key), key_algorithm,
- extractable, usages, key);
- }
- Status ImportRsaPrivateKey(const blink::WebCryptoAlgorithm& algorithm,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- const JwkRsaInfo& params,
- blink::WebCryptoKey* key) {
- bssl::UniquePtr<RSA> rsa(RSA_new());
- rsa->n = BN_bin2bn(params.n.data(), params.n.size(), nullptr);
- rsa->e = BN_bin2bn(params.e.data(), params.e.size(), nullptr);
- rsa->d = BN_bin2bn(params.d.data(), params.d.size(), nullptr);
- rsa->p = BN_bin2bn(params.p.data(), params.p.size(), nullptr);
- rsa->q = BN_bin2bn(params.q.data(), params.q.size(), nullptr);
- rsa->dmp1 = BN_bin2bn(params.dp.data(), params.dp.size(), nullptr);
- rsa->dmq1 = BN_bin2bn(params.dq.data(), params.dq.size(), nullptr);
- rsa->iqmp = BN_bin2bn(params.qi.data(), params.qi.size(), nullptr);
- if (!rsa->n || !rsa->e || !rsa->d || !rsa->p || !rsa->q || !rsa->dmp1 ||
- !rsa->dmq1 || !rsa->iqmp) {
- return Status::OperationError();
- }
- // TODO(eroman): This should be a DataError.
- if (!RSA_check_key(rsa.get()))
- return Status::OperationError();
- // Create a corresponding EVP_PKEY.
- bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
- if (!pkey || !EVP_PKEY_set1_RSA(pkey.get(), rsa.get()))
- return Status::OperationError();
- return CreateWebCryptoRsaPrivateKey(
- std::move(pkey), algorithm.Id(),
- algorithm.RsaHashedImportParams()->GetHash(), extractable, usages, key);
- }
- Status ImportRsaPublicKey(const blink::WebCryptoAlgorithm& algorithm,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- base::span<const uint8_t> n,
- base::span<const uint8_t> e,
- blink::WebCryptoKey* key) {
- bssl::UniquePtr<RSA> rsa(RSA_new());
- rsa->n = BN_bin2bn(n.data(), n.size(), nullptr);
- rsa->e = BN_bin2bn(e.data(), e.size(), nullptr);
- if (!rsa->n || !rsa->e)
- return Status::OperationError();
- // Create a corresponding EVP_PKEY.
- bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
- if (!pkey || !EVP_PKEY_set1_RSA(pkey.get(), rsa.get()))
- return Status::OperationError();
- return CreateWebCryptoRsaPublicKey(
- std::move(pkey), algorithm.Id(),
- algorithm.RsaHashedImportParams()->GetHash(), extractable, usages, key);
- }
- // Converts a BIGNUM to a big endian byte array.
- std::vector<uint8_t> BIGNUMToVector(const BIGNUM* n) {
- std::vector<uint8_t> v(BN_num_bytes(n));
- BN_bn2bin(n, v.data());
- return v;
- }
- // Synthesizes an import algorithm given a key algorithm, so that
- // deserialization can re-use the ImportKey*() methods.
- blink::WebCryptoAlgorithm SynthesizeImportAlgorithmForClone(
- const blink::WebCryptoKeyAlgorithm& algorithm) {
- return blink::WebCryptoAlgorithm::AdoptParamsAndCreate(
- algorithm.Id(), new blink::WebCryptoRsaHashedImportParams(
- algorithm.RsaHashedParams()->GetHash()));
- }
- } // namespace
- Status RsaHashedAlgorithm::GenerateKey(
- const blink::WebCryptoAlgorithm& algorithm,
- bool extractable,
- blink::WebCryptoKeyUsageMask combined_usages,
- GenerateKeyResult* result) const {
- blink::WebCryptoKeyUsageMask public_usages = 0;
- blink::WebCryptoKeyUsageMask private_usages = 0;
- Status status = GetUsagesForGenerateAsymmetricKey(
- combined_usages, all_public_key_usages_, all_private_key_usages_,
- &public_usages, &private_usages);
- if (status.IsError())
- return status;
- const blink::WebCryptoRsaHashedKeyGenParams* params =
- algorithm.RsaHashedKeyGenParams();
- unsigned int modulus_length_bits = params->ModulusLengthBits();
- // Limit the RSA key sizes to:
- // * Multiple of 8 bits
- // * 256 bits to 16K bits
- //
- // These correspond with limitations at the time there was an NSS WebCrypto
- // implementation. However in practice the upper bound is also helpful
- // because generating large RSA keys is very slow.
- if (modulus_length_bits < 256 || modulus_length_bits > 16384 ||
- (modulus_length_bits % 8) != 0) {
- return Status::ErrorGenerateRsaUnsupportedModulus();
- }
- unsigned int public_exponent = 0;
- if (!params->ConvertPublicExponentToUnsigned(public_exponent))
- return Status::ErrorGenerateKeyPublicExponent();
- // OpenSSL hangs when given bad public exponents. Use a whitelist.
- if (public_exponent != 3 && public_exponent != 65537)
- return Status::ErrorGenerateKeyPublicExponent();
- crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
- // Generate an RSA key pair.
- bssl::UniquePtr<RSA> rsa_private_key(RSA_new());
- bssl::UniquePtr<BIGNUM> bn(BN_new());
- if (!rsa_private_key.get() || !bn.get() ||
- !BN_set_word(bn.get(), public_exponent)) {
- return Status::OperationError();
- }
- if (!RSA_generate_key_ex(rsa_private_key.get(), modulus_length_bits, bn.get(),
- nullptr)) {
- return Status::OperationError();
- }
- // Construct an EVP_PKEY for the private key.
- bssl::UniquePtr<EVP_PKEY> private_pkey(EVP_PKEY_new());
- if (!private_pkey ||
- !EVP_PKEY_set1_RSA(private_pkey.get(), rsa_private_key.get())) {
- return Status::OperationError();
- }
- // Construct an EVP_PKEY for the public key.
- bssl::UniquePtr<RSA> rsa_public_key(RSAPublicKey_dup(rsa_private_key.get()));
- bssl::UniquePtr<EVP_PKEY> public_pkey(EVP_PKEY_new());
- if (!public_pkey ||
- !EVP_PKEY_set1_RSA(public_pkey.get(), rsa_public_key.get())) {
- return Status::OperationError();
- }
- blink::WebCryptoKey public_key;
- blink::WebCryptoKey private_key;
- // Note that extractable is unconditionally set to true. This is because per
- // the WebCrypto spec generated public keys are always extractable.
- status = CreateWebCryptoRsaPublicKey(std::move(public_pkey), algorithm.Id(),
- params->GetHash(), true, public_usages,
- &public_key);
- if (status.IsError())
- return status;
- status = CreateWebCryptoRsaPrivateKey(std::move(private_pkey), algorithm.Id(),
- params->GetHash(), extractable,
- private_usages, &private_key);
- if (status.IsError())
- return status;
- result->AssignKeyPair(public_key, private_key);
- return Status::Success();
- }
- Status RsaHashedAlgorithm::ImportKey(blink::WebCryptoKeyFormat format,
- base::span<const uint8_t> key_data,
- const blink::WebCryptoAlgorithm& algorithm,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- blink::WebCryptoKey* key) const {
- switch (format) {
- case blink::kWebCryptoKeyFormatPkcs8:
- return ImportKeyPkcs8(key_data, algorithm, extractable, usages, key);
- case blink::kWebCryptoKeyFormatSpki:
- return ImportKeySpki(key_data, algorithm, extractable, usages, key);
- case blink::kWebCryptoKeyFormatJwk:
- return ImportKeyJwk(key_data, algorithm, extractable, usages, key);
- default:
- return Status::ErrorUnsupportedImportKeyFormat();
- }
- }
- Status RsaHashedAlgorithm::ExportKey(blink::WebCryptoKeyFormat format,
- const blink::WebCryptoKey& key,
- std::vector<uint8_t>* buffer) const {
- switch (format) {
- case blink::kWebCryptoKeyFormatPkcs8:
- return ExportKeyPkcs8(key, buffer);
- case blink::kWebCryptoKeyFormatSpki:
- return ExportKeySpki(key, buffer);
- case blink::kWebCryptoKeyFormatJwk:
- return ExportKeyJwk(key, buffer);
- default:
- return Status::ErrorUnsupportedExportKeyFormat();
- }
- }
- Status RsaHashedAlgorithm::ImportKeyPkcs8(
- base::span<const uint8_t> key_data,
- const blink::WebCryptoAlgorithm& algorithm,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- blink::WebCryptoKey* key) const {
- Status status = CheckKeyCreationUsages(all_private_key_usages_, usages);
- if (status.IsError())
- return status;
- bssl::UniquePtr<EVP_PKEY> private_key;
- status = ImportUnverifiedPkeyFromPkcs8(key_data, EVP_PKEY_RSA, &private_key);
- if (status.IsError())
- return status;
- // Verify the parameters of the key.
- RSA* rsa = EVP_PKEY_get0_RSA(private_key.get());
- if (!rsa)
- return Status::ErrorUnexpected();
- if (!RSA_check_key(rsa))
- return Status::DataError();
- // TODO(eroman): Validate the algorithm OID against the webcrypto provided
- // hash. http://crbug.com/389400
- return CreateWebCryptoRsaPrivateKey(
- std::move(private_key), algorithm.Id(),
- algorithm.RsaHashedImportParams()->GetHash(), extractable, usages, key);
- }
- Status RsaHashedAlgorithm::ImportKeySpki(
- base::span<const uint8_t> key_data,
- const blink::WebCryptoAlgorithm& algorithm,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- blink::WebCryptoKey* key) const {
- Status status = CheckKeyCreationUsages(all_public_key_usages_, usages);
- if (status.IsError())
- return status;
- bssl::UniquePtr<EVP_PKEY> public_key;
- status = ImportUnverifiedPkeyFromSpki(key_data, EVP_PKEY_RSA, &public_key);
- if (status.IsError())
- return status;
- // TODO(eroman): Validate the algorithm OID against the webcrypto provided
- // hash. http://crbug.com/389400
- return CreateWebCryptoRsaPublicKey(
- std::move(public_key), algorithm.Id(),
- algorithm.RsaHashedImportParams()->GetHash(), extractable, usages, key);
- }
- Status RsaHashedAlgorithm::ImportKeyJwk(
- base::span<const uint8_t> key_data,
- const blink::WebCryptoAlgorithm& algorithm,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- blink::WebCryptoKey* key) const {
- crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
- const char* jwk_algorithm =
- GetJwkAlgorithm(algorithm.RsaHashedImportParams()->GetHash().Id());
- if (!jwk_algorithm)
- return Status::ErrorUnexpected();
- JwkRsaInfo jwk;
- Status status =
- ReadRsaKeyJwk(key_data, jwk_algorithm, extractable, usages, &jwk);
- if (status.IsError())
- return status;
- // Once the key type is known, verify the usages.
- if (jwk.is_private_key) {
- status = CheckKeyCreationUsages(all_private_key_usages_, usages);
- } else {
- status = CheckKeyCreationUsages(all_public_key_usages_, usages);
- }
- if (status.IsError())
- return status;
- return jwk.is_private_key
- ? ImportRsaPrivateKey(algorithm, extractable, usages, jwk, key)
- : ImportRsaPublicKey(algorithm, extractable, usages, jwk.n, jwk.e,
- key);
- }
- Status RsaHashedAlgorithm::ExportKeyPkcs8(const blink::WebCryptoKey& key,
- std::vector<uint8_t>* buffer) const {
- if (key.GetType() != blink::kWebCryptoKeyTypePrivate)
- return Status::ErrorUnexpectedKeyType();
- return ExportPKeyPkcs8(GetEVP_PKEY(key), buffer);
- }
- Status RsaHashedAlgorithm::ExportKeySpki(const blink::WebCryptoKey& key,
- std::vector<uint8_t>* buffer) const {
- if (key.GetType() != blink::kWebCryptoKeyTypePublic)
- return Status::ErrorUnexpectedKeyType();
- return ExportPKeySpki(GetEVP_PKEY(key), buffer);
- }
- Status RsaHashedAlgorithm::ExportKeyJwk(const blink::WebCryptoKey& key,
- std::vector<uint8_t>* buffer) const {
- crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
- EVP_PKEY* pkey = GetEVP_PKEY(key);
- RSA* rsa = EVP_PKEY_get0_RSA(pkey);
- if (!rsa)
- return Status::ErrorUnexpected();
- const char* jwk_algorithm =
- GetJwkAlgorithm(key.Algorithm().RsaHashedParams()->GetHash().Id());
- if (!jwk_algorithm)
- return Status::ErrorUnexpected();
- switch (key.GetType()) {
- case blink::kWebCryptoKeyTypePublic: {
- JwkWriter writer(jwk_algorithm, key.Extractable(), key.Usages(), "RSA");
- writer.SetBytes("n", BIGNUMToVector(RSA_get0_n(rsa)));
- writer.SetBytes("e", BIGNUMToVector(RSA_get0_e(rsa)));
- writer.ToJson(buffer);
- return Status::Success();
- }
- case blink::kWebCryptoKeyTypePrivate: {
- JwkWriter writer(jwk_algorithm, key.Extractable(), key.Usages(), "RSA");
- writer.SetBytes("n", BIGNUMToVector(RSA_get0_n(rsa)));
- writer.SetBytes("e", BIGNUMToVector(RSA_get0_e(rsa)));
- writer.SetBytes("d", BIGNUMToVector(RSA_get0_d(rsa)));
- // Although these are "optional" in the JWA, WebCrypto spec requires them
- // to be emitted.
- writer.SetBytes("p", BIGNUMToVector(RSA_get0_p(rsa)));
- writer.SetBytes("q", BIGNUMToVector(RSA_get0_q(rsa)));
- writer.SetBytes("dp", BIGNUMToVector(RSA_get0_dmp1(rsa)));
- writer.SetBytes("dq", BIGNUMToVector(RSA_get0_dmq1(rsa)));
- writer.SetBytes("qi", BIGNUMToVector(RSA_get0_iqmp(rsa)));
- writer.ToJson(buffer);
- return Status::Success();
- }
- default:
- return Status::ErrorUnexpected();
- }
- }
- // TODO(eroman): Defer import to the crypto thread. http://crbug.com/430763
- Status RsaHashedAlgorithm::DeserializeKeyForClone(
- const blink::WebCryptoKeyAlgorithm& algorithm,
- blink::WebCryptoKeyType type,
- bool extractable,
- blink::WebCryptoKeyUsageMask usages,
- base::span<const uint8_t> key_data,
- blink::WebCryptoKey* key) const {
- if (algorithm.ParamsType() !=
- blink::kWebCryptoKeyAlgorithmParamsTypeRsaHashed)
- return Status::ErrorUnexpected();
- blink::WebCryptoAlgorithm import_algorithm =
- SynthesizeImportAlgorithmForClone(algorithm);
- Status status;
- // The serialized data will be either SPKI or PKCS8 formatted.
- switch (type) {
- case blink::kWebCryptoKeyTypePublic:
- status =
- ImportKeySpki(key_data, import_algorithm, extractable, usages, key);
- break;
- case blink::kWebCryptoKeyTypePrivate:
- status =
- ImportKeyPkcs8(key_data, import_algorithm, extractable, usages, key);
- break;
- default:
- return Status::ErrorUnexpected();
- }
- if (!status.IsSuccess())
- return status;
- // There is some duplicated information in the serialized format used by
- // structured clone (since the KeyAlgorithm is serialized separately from the
- // key data). Use this extra information to further validate what was
- // deserialized from the key data.
- if (algorithm.Id() != key->Algorithm().Id())
- return Status::ErrorUnexpected();
- if (key->GetType() != type)
- return Status::ErrorUnexpected();
- if (algorithm.RsaHashedParams()->ModulusLengthBits() !=
- key->Algorithm().RsaHashedParams()->ModulusLengthBits()) {
- return Status::ErrorUnexpected();
- }
- if (algorithm.RsaHashedParams()->PublicExponent().size() !=
- key->Algorithm().RsaHashedParams()->PublicExponent().size() ||
- 0 !=
- memcmp(algorithm.RsaHashedParams()->PublicExponent().data(),
- key->Algorithm().RsaHashedParams()->PublicExponent().data(),
- key->Algorithm().RsaHashedParams()->PublicExponent().size())) {
- return Status::ErrorUnexpected();
- }
- return Status::Success();
- }
- } // namespace webcrypto
|