LibCrypto+LibWeb: Check RSA keys validity on SubtleCrypto import_key
Some checks are pending
CI / Linux, x86_64, Fuzzers_CI, Clang (push) Waiting to run
CI / macOS, arm64, Sanitizer_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

Fix various TODO by checking the validity of RSA keys when they are
imported.

Also add some internal tests since WPT doesn't seem to provide them.
This commit is contained in:
devgianlu 2025-05-31 19:30:59 +02:00 committed by Shannon Booth
commit 4e747f525a
Notes: github-actions[bot] 2025-06-25 00:23:02 +00:00
5 changed files with 301 additions and 17 deletions

View file

@ -172,7 +172,7 @@ ErrorOr<RSA::KeyPairType> RSA::generate_key_pair(size_t bits, UnsignedBigInteger
OPENSSL_TRY(OSSL_PARAM_BLD_push_BN(params_bld, openssl_name, param##_bn.ptr())); \
}
ErrorOr<OpenSSL_PKEY> RSA::public_key_to_openssl_pkey(PublicKeyType const& public_key)
static ErrorOr<OpenSSL_PKEY> public_key_to_openssl_pkey(RSAPublicKey const& public_key)
{
auto ctx = TRY(OpenSSL_PKEY_CTX::wrap(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr)));
@ -193,7 +193,7 @@ ErrorOr<OpenSSL_PKEY> RSA::public_key_to_openssl_pkey(PublicKeyType const& publi
return key;
}
ErrorOr<OpenSSL_PKEY> RSA::private_key_to_openssl_pkey(PrivateKeyType const& private_key)
static ErrorOr<OpenSSL_PKEY> private_key_to_openssl_pkey(RSAPrivateKey const& private_key)
{
auto ctx = TRY(OpenSSL_PKEY_CTX::wrap(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr)));
@ -222,6 +222,125 @@ ErrorOr<OpenSSL_PKEY> RSA::private_key_to_openssl_pkey(PrivateKeyType const& pri
#undef OPENSSL_SET_KEY_PARAM_NOT_ZERO
// https://www.rfc-editor.org/rfc/rfc3447.html#section-3.1
ErrorOr<bool> RSAPublicKey::is_valid() const
{
// In a valid RSA public key, the RSA modulus n is a product of u
// distinct odd primes r_i, i = 1, 2, ..., u, where u >= 2, and the RSA
// public exponent e is an integer between 3 and n - 1 satisfying GCD(e,
// \lambda(n)) = 1, where \lambda(n) = LCM(r_1 - 1, ..., r_u - 1).
if (!m_public_exponent.is_odd())
return false;
if (m_public_exponent < 3 || m_public_exponent >= m_modulus)
return false;
return true;
}
// https://www.rfc-editor.org/rfc/rfc3447.html#section-3.2
ErrorOr<bool> RSAPrivateKey::is_valid() const
{
if (!m_public_exponent.is_odd())
return false;
if (m_public_exponent < 3 || m_public_exponent >= m_modulus)
return false;
if (!m_prime_1.is_zero() && !m_prime_2.is_zero() && !m_exponent_1.is_zero() && !m_exponent_2.is_zero() && !m_coefficient.is_zero()) {
// In a valid RSA private key with the second representation, the two
// factors p and q are the first two prime factors of the RSA modulus n
// (i.e., r_1 and r_2), the CRT exponents dP and dQ are positive
// integers less than p and q respectively satisfying
// e * dP == 1 (mod (p-1))
// e * dQ == 1 (mod (q-1)) ,
// and the CRT coefficient qInv is a positive integer less than p
// satisfying
// q * qInv == 1 (mod p).
// If u > 2, the representation will include one or more triplets (r_i,
// d_i, t_i), i = 3, ..., u. The factors r_i are the additional prime
// factors of the RSA modulus n. Each CRT exponent d_i (i = 3, ..., u)
// satisfies
// e * d_i == 1 (mod (r_i - 1)).
// Each CRT coefficient t_i (i = 3, ..., u) is a positive integer less
// than r_i satisfying
// R_i * t_i == 1 (mod r_i) ,
// where R_i = r_1 * r_2 * ... * r_(i-1).
if (m_exponent_1 >= m_prime_1 || m_exponent_2 >= m_prime_2 || m_coefficient >= m_prime_1)
return false;
if (m_prime_1.multiplied_by(m_prime_2) != m_modulus)
return false;
auto tmp_bn = TRY(OpenSSL_BN::create());
auto e = TRY(unsigned_big_integer_to_openssl_bignum(m_public_exponent)),
p = TRY(unsigned_big_integer_to_openssl_bignum(m_prime_1)),
q = TRY(unsigned_big_integer_to_openssl_bignum(m_prime_2));
auto dp = TRY(unsigned_big_integer_to_openssl_bignum(m_exponent_1)),
dq = TRY(unsigned_big_integer_to_openssl_bignum(m_exponent_2));
auto* bn_ctx = OPENSSL_TRY_PTR(BN_CTX_new());
ScopeGuard const free_bn_ctx = [&] { BN_CTX_free(bn_ctx); };
auto p1 = TRY(OpenSSL_BN::create());
OPENSSL_TRY(BN_sub(p1.ptr(), p.ptr(), BN_value_one()));
OPENSSL_TRY(BN_mod_mul(tmp_bn.ptr(), e.ptr(), dp.ptr(), p1.ptr(), bn_ctx));
if (!BN_is_one(tmp_bn.ptr()))
return false;
auto q1 = TRY(OpenSSL_BN::create());
OPENSSL_TRY(BN_sub(q1.ptr(), q.ptr(), BN_value_one()));
OPENSSL_TRY(BN_mod_mul(tmp_bn.ptr(), e.ptr(), dq.ptr(), q1.ptr(), bn_ctx));
if (!BN_is_one(tmp_bn.ptr()))
return false;
auto q_inv = TRY(unsigned_big_integer_to_openssl_bignum(m_coefficient));
OPENSSL_TRY(BN_mod_mul(tmp_bn.ptr(), q.ptr(), q_inv.ptr(), p.ptr(), bn_ctx));
if (!BN_is_one(tmp_bn.ptr()))
return false;
if (!m_private_exponent.is_zero()) {
if (m_private_exponent >= m_modulus)
return false;
auto lambda = TRY(m_prime_1.minus(1)).lcm(TRY(m_prime_2.minus(1)));
auto lambda_bn = TRY(unsigned_big_integer_to_openssl_bignum(lambda));
auto d = TRY(unsigned_big_integer_to_openssl_bignum(m_private_exponent));
OPENSSL_TRY(BN_mod_mul(tmp_bn.ptr(), d.ptr(), e.ptr(), lambda_bn.ptr(), bn_ctx));
if (!BN_is_one(tmp_bn.ptr()))
return false;
}
return true;
}
if (!m_modulus.is_zero() && !m_private_exponent.is_zero()) {
// In a valid RSA private key with the first representation, the RSA
// modulus n is the same as in the corresponding RSA public key and is
// the product of u distinct odd primes r_i, i = 1, 2, ..., u, where u
// >= 2. The RSA private exponent d is a positive integer less than n
// satisfying
// e * d == 1 (mod \lambda(n)),
// where e is the corresponding RSA public exponent and \lambda(n) is
// defined as in Section 3.1.
if (m_private_exponent >= m_modulus)
return false;
return true;
}
return false;
}
ErrorOr<void> RSA::configure(OpenSSL_PKEY_CTX& ctx)
{
OPENSSL_TRY(EVP_PKEY_CTX_set_rsa_padding(ctx.ptr(), RSA_NO_PADDING));

View file

@ -35,6 +35,8 @@ public:
UnsignedBigInteger const& public_exponent() const { return m_public_exponent; }
size_t length() const { return m_length; }
ErrorOr<bool> is_valid() const;
ErrorOr<ByteBuffer> export_as_der() const
{
ASN1::Encoder encoder;
@ -88,6 +90,8 @@ public:
UnsignedBigInteger const& coefficient() const { return m_coefficient; }
size_t length() const { return m_length; }
ErrorOr<bool> is_valid() const;
ErrorOr<ByteBuffer> export_as_der() const
{
if (m_prime_1.is_zero() || m_prime_2.is_zero()) {
@ -196,9 +200,6 @@ public:
protected:
virtual ErrorOr<void> configure(OpenSSL_PKEY_CTX& ctx);
static ErrorOr<OpenSSL_PKEY> public_key_to_openssl_pkey(PublicKeyType const& public_key);
static ErrorOr<OpenSSL_PKEY> private_key_to_openssl_pkey(PrivateKeyType const& private_key);
};
ErrorOr<EVP_MD const*> hash_kind_to_hash_type(Hash::HashKind hash_kind);

View file

@ -837,7 +837,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAOAEP::import_key(Web::Crypto::Algorit
// 6. If an error occurred while parsing, or it can be determined that publicKey is not
// a valid public key according to [RFC3447], then throw a DataError.
// FIXME: Validate the public key
auto maybe_valid = public_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 7. Let key be a new CryptoKey that represents the RSA public key identified by publicKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key });
@ -874,7 +878,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAOAEP::import_key(Web::Crypto::Algorit
// 6. If an error occurred while parsing, or if rsaPrivateKey is not
// a valid RSA private key according to [RFC3447], then throw a DataError.
// FIXME: Validate the private key
auto maybe_valid = rsa_private_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 7. Let key be a new CryptoKey that represents the RSA private key identified by rsaPrivateKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { rsa_private_key });
@ -989,7 +997,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAOAEP::import_key(Web::Crypto::Algorit
auto private_key = TRY(parse_jwk_rsa_private_key(realm, jwk));
// 3. If privateKey can be determined to not be a valid RSA private key according to [RFC3447], then throw a DataError.
// FIXME: Validate the private key
auto maybe_valid = private_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 4. Let key be a new CryptoKey representing privateKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key });
@ -1008,7 +1020,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAOAEP::import_key(Web::Crypto::Algorit
auto public_key = TRY(parse_jwk_rsa_public_key(realm, jwk));
// 3. If publicKey can be determined to not be a valid RSA public key according to [RFC3447], then throw a DataError.
// FIXME: Validate the public key
auto maybe_valid = public_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 4. Let key be a new CryptoKey representing publicKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key });
@ -1414,7 +1430,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAPSS::import_key(AlgorithmParams const
// 6. If an error occurred while parsing, or it can be determined that publicKey is not
// a valid public key according to [RFC3447], then throw a DataError.
// FIXME: Validate the public key
auto maybe_valid = public_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 7. Let key be a new CryptoKey that represents the RSA public key identified by publicKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key });
@ -1451,7 +1471,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAPSS::import_key(AlgorithmParams const
// 6. If an error occurred while parsing, or if rsaPrivateKey is not
// a valid RSA private key according to [RFC3447], then throw a DataError.
// FIXME: Validate the private key
auto maybe_valid = rsa_private_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 7. Let key be a new CryptoKey that represents the RSA private key identified by rsaPrivateKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { rsa_private_key });
@ -1567,7 +1591,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAPSS::import_key(AlgorithmParams const
// FIXME: Spec error, it should say 'not to be a valid RSA private key'
// 3. If privateKey can be determined to not be a valid RSA public key according to [RFC3447], then throw a DataError.
// FIXME: Validate the private key
auto maybe_valid = private_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 4. Let key be a new CryptoKey representing privateKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key });
@ -1586,7 +1614,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAPSS::import_key(AlgorithmParams const
auto public_key = TRY(parse_jwk_rsa_public_key(realm, jwk));
// 3. If publicKey can be determined to not be a valid RSA public key according to [RFC3447], then throw a DataError.
// FIXME: Validate the public key
auto maybe_valid = public_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 4. Let key be a new CryptoKey representing publicKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key });
@ -1987,7 +2019,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSASSAPKCS1::import_key(AlgorithmParams
// 6. If an error occurred while parsing, or it can be determined that publicKey is not
// a valid public key according to [RFC3447], then throw a DataError.
// FIXME: Validate the public key
auto maybe_valid = public_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 7. Let key be a new CryptoKey that represents the RSA public key identified by publicKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key });
@ -2024,7 +2060,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSASSAPKCS1::import_key(AlgorithmParams
// 6. If an error occurred while parsing, or if rsaPrivateKey is not
// a valid RSA private key according to [RFC3447], then throw a DataError.
// FIXME: Validate the private key
auto maybe_valid = rsa_private_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 7. Let key be a new CryptoKey that represents the RSA private key identified by rsaPrivateKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { rsa_private_key });
@ -2138,7 +2178,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSASSAPKCS1::import_key(AlgorithmParams
auto private_key = TRY(parse_jwk_rsa_private_key(realm, jwk));
// 3. If privateKey can be determined to not be a valid RSA private key according to [RFC3447], then throw a DataError.
// FIXME: Validate the private key
auto maybe_valid = private_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 4. Let key be a new CryptoKey representing privateKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key });
@ -2157,7 +2201,11 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSASSAPKCS1::import_key(AlgorithmParams
auto public_key = TRY(parse_jwk_rsa_public_key(realm, jwk));
// 3. If publicKey can be determined to not be a valid RSA public key according to [RFC3447], then throw a DataError.
// FIXME: Validate the public key
auto maybe_valid = public_key.is_valid();
if (maybe_valid.is_error())
return WebIDL::DataError::create(m_realm, "Failed to verify key"_string);
if (!maybe_valid.value())
return WebIDL::DataError::create(m_realm, "Invalid key"_string);
// 4. Let key be a new CryptoKey representing publicKey.
key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key });

View file

@ -0,0 +1,36 @@
RSASSA-PKCS1-v1_5 SHA-1 PRIV - jwk OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-1 PRIV - pkcs8 OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-1 PRIV - pkcs8 OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-256 PRIV - jwk OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-256 PRIV - pkcs8 OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-256 PRIV - pkcs8 OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-384 PRIV - jwk OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-384 PRIV - pkcs8 OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-384 PRIV - pkcs8 OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-512 PRIV - jwk OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-512 PRIV - pkcs8 OK: DataError: Invalid key
RSASSA-PKCS1-v1_5 SHA-512 PRIV - pkcs8 OK: DataError: Invalid key
RSA-PSS SHA-1 PRIV - jwk OK: DataError: Invalid key
RSA-PSS SHA-1 PRIV - pkcs8 OK: DataError: Invalid key
RSA-PSS SHA-1 PRIV - pkcs8 OK: DataError: Invalid key
RSA-PSS SHA-256 PRIV - jwk OK: DataError: Invalid key
RSA-PSS SHA-256 PRIV - pkcs8 OK: DataError: Invalid key
RSA-PSS SHA-256 PRIV - pkcs8 OK: DataError: Invalid key
RSA-PSS SHA-384 PRIV - jwk OK: DataError: Invalid key
RSA-PSS SHA-384 PRIV - pkcs8 OK: DataError: Invalid key
RSA-PSS SHA-384 PRIV - pkcs8 OK: DataError: Invalid key
RSA-PSS SHA-512 PRIV - jwk OK: DataError: Invalid key
RSA-PSS SHA-512 PRIV - pkcs8 OK: DataError: Invalid key
RSA-PSS SHA-512 PRIV - pkcs8 OK: DataError: Invalid key
RSA-OAEP SHA-1 PRIV - jwk OK: DataError: Invalid key
RSA-OAEP SHA-1 PRIV - pkcs8 OK: DataError: Invalid key
RSA-OAEP SHA-1 PRIV - pkcs8 OK: DataError: Invalid key
RSA-OAEP SHA-256 PRIV - jwk OK: DataError: Invalid key
RSA-OAEP SHA-256 PRIV - pkcs8 OK: DataError: Invalid key
RSA-OAEP SHA-256 PRIV - pkcs8 OK: DataError: Invalid key
RSA-OAEP SHA-384 PRIV - jwk OK: DataError: Invalid key
RSA-OAEP SHA-384 PRIV - pkcs8 OK: DataError: Invalid key
RSA-OAEP SHA-384 PRIV - pkcs8 OK: DataError: Invalid key
RSA-OAEP SHA-512 PRIV - jwk OK: DataError: Invalid key
RSA-OAEP SHA-512 PRIV - pkcs8 OK: DataError: Invalid key
RSA-OAEP SHA-512 PRIV - pkcs8 OK: DataError: Invalid key

View file

@ -0,0 +1,80 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<script>
function b64ToBn(b64) {
const bin = atob(b64.replace(/-/g, '+').replace(/_/g, '/'));
const hex = bin.split('').map((ch) => ch.charCodeAt(0).toString(16).padStart(2, '0')).join('');
return [BigInt('0x' + hex), bin.length];
}
function bnToB64(bn, len) {
const hex = bn.toString(16);
const bin = hex.match(/.{1,2}/g).map((byte) => String.fromCharCode(parseInt(byte, 16))).join('');
return btoa(bin.padStart(len, '\x00')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function corruptJwk(key) {
const [bn, len] = b64ToBn(key.d);
// Corrupt the private key
return {
...key,
d: bnToB64(bn ^ 1n, len),
};
}
function corruptPkcs8Pub(privateKey) {
// Corrupt the public key, which is encoded between 33 and 165 for the PKCS8
const corrupted = new Uint8Array(privateKey);
corrupted[128] ^= 0x01;
return corrupted.buffer;
}
function corruptPkcs8Priv(privateKey) {
// Corrupt a piece of the private key, which is encoded at the end of the PKCS8
const corrupted = new Uint8Array(privateKey);
corrupted[corrupted.length - 20] ^= 0x01;
return corrupted.buffer;
}
asyncTest(async done => {
for (const [name, genUsages, privUsages] of [
["RSASSA-PKCS1-v1_5", ["sign", "verify"], ["sign"]],
["RSA-PSS", ["sign", "verify"], ["sign"]],
["RSA-OAEP", ["encrypt", "decrypt"], ["decrypt"]],
]) {
for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) {
const keyPair = await window.crypto.subtle.generateKey({
name: name,
hash: hash,
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
}, true, genUsages);
// NOTE: We don't check the public key for RSA since it's harder to corrupt.
for (const [format, corruptFn] of [["jwk", corruptJwk], ["pkcs8", corruptPkcs8Priv], ["pkcs8", corruptPkcs8Pub]]) {
const privateKey = await window.crypto.subtle.exportKey(format, keyPair.privateKey);
// Prove that the original key can be imported successfully
await window.crypto.subtle.importKey(format, privateKey, {name: name, hash: hash,}, false, privUsages);
// Corrupt the private key and try to import it
const corruptedPrivateKey = corruptFn(privateKey);
try {
await window.crypto.subtle.importKey(format, corruptedPrivateKey, {
name: name,
hash: hash,
}, false, privUsages);
println(`${name.padEnd(5, ' ')} ${hash} PRIV - ${format.padEnd(5, ' ')} FAILED`);
} catch (e) {
println(`${name.padEnd(5, ' ')} ${hash} PRIV - ${format.padEnd(5, ' ')} OK: ${e}`);
}
}
}
}
done();
});
</script>