| Index: net/cert/internal/verify_signed_data.cc
|
| diff --git a/net/cert/internal/verify_signed_data.cc b/net/cert/internal/verify_signed_data.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fd68328694be015b60e39de5d041bfdd3a875678
|
| --- /dev/null
|
| +++ b/net/cert/internal/verify_signed_data.cc
|
| @@ -0,0 +1,318 @@
|
| +// Copyright 2015 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 "net/cert/internal/verify_signed_data.h"
|
| +
|
| +#include "base/logging.h"
|
| +
|
| +// TODO(eroman): There is no intention to implement this for non-OpenSSL. Remove
|
| +// this branch once the migration is complete. This could have been done as a
|
| +// conditional file (_openssl.cc) in the build file instead, but that is likely
|
| +// not worth the effort at this point.
|
| +
|
| +#if !defined(USE_OPENSSL)
|
| +
|
| +namespace net {
|
| +
|
| +bool VerifySignedData(const SignatureAlgorithm& signature_algorithm,
|
| + const der::Input& signed_data,
|
| + const der::Input& signature_value_bit_string,
|
| + const der::Input& public_key) {
|
| + NOTIMPLEMENTED();
|
| + return false;
|
| +}
|
| +
|
| +} // namespace net
|
| +
|
| +#else
|
| +
|
| +#include <openssl/digest.h>
|
| +#include <openssl/ec.h>
|
| +#include <openssl/ec_key.h>
|
| +#include <openssl/evp.h>
|
| +#include <openssl/rsa.h>
|
| +#include <openssl/x509.h>
|
| +
|
| +#include "base/compiler_specific.h"
|
| +#include "crypto/openssl_util.h"
|
| +#include "crypto/scoped_openssl_types.h"
|
| +#include "net/cert/internal/signature_algorithm.h"
|
| +#include "net/der/input.h"
|
| +#include "net/der/parser.h"
|
| +
|
| +namespace net {
|
| +
|
| +namespace {
|
| +
|
| +// Converts a DigestAlgorithm to an equivalent EVP_MD*.
|
| +WARN_UNUSED_RESULT bool GetDigest(DigestAlgorithm digest, const EVP_MD** out) {
|
| + *out = nullptr;
|
| +
|
| + switch (digest) {
|
| + case DigestAlgorithm::Sha1:
|
| + *out = EVP_sha1();
|
| + break;
|
| + case DigestAlgorithm::Sha256:
|
| + *out = EVP_sha256();
|
| + break;
|
| + case DigestAlgorithm::Sha384:
|
| + *out = EVP_sha384();
|
| + break;
|
| + case DigestAlgorithm::Sha512:
|
| + *out = EVP_sha512();
|
| + break;
|
| + }
|
| +
|
| + return *out != nullptr;
|
| +}
|
| +
|
| +// Sets the RSASSA-PSS parameters on |pctx|. Returns true on success.
|
| +WARN_UNUSED_RESULT bool ApplyRsaPssOptions(const RsaPssParameters* params,
|
| + EVP_PKEY_CTX* pctx) {
|
| + // BoringSSL takes a signed int for the salt length, and interprets
|
| + // negative values in a special manner. Make sure not to silently underflow.
|
| + base::CheckedNumeric<int> salt_length_bytes_int(params->salt_length());
|
| + if (!salt_length_bytes_int.IsValid())
|
| + return false;
|
| +
|
| + const EVP_MD* mgf1_hash;
|
| + if (!GetDigest(params->mgf1_hash(), &mgf1_hash))
|
| + return false;
|
| +
|
| + return EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) &&
|
| + EVP_PKEY_CTX_set_rsa_mgf1_md(pctx, mgf1_hash) &&
|
| + EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx,
|
| + salt_length_bytes_int.ValueOrDie());
|
| +}
|
| +
|
| +// TODO(eroman): This function is not strict enough. It accepts BER, other RSA
|
| +// OIDs, and does not check id-rsaEncryption parameters.
|
| +WARN_UNUSED_RESULT bool ImportPkeyFromSpki(const der::Input& spki,
|
| + int expected_pkey_id,
|
| + crypto::ScopedEVP_PKEY* pkey) {
|
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
|
| +
|
| + const uint8_t* ptr = spki.UnsafeData();
|
| + pkey->reset(d2i_PUBKEY(nullptr, &ptr, spki.Length()));
|
| + if (!pkey->get() || ptr != spki.UnsafeData() + spki.Length() ||
|
| + EVP_PKEY_id(pkey->get()) != expected_pkey_id) {
|
| + pkey->reset();
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +// Parses an RSA public key from SPKI to an EVP_PKEY.
|
| +//
|
| +// Returns true on success.
|
| +//
|
| +// There are two flavors of RSA public key that this function should recognize
|
| +// from RFC 5912 (however note that pk-rsaSSA-PSS is not supported in the
|
| +// current implementation).
|
| +// TODO(eroman): Support id-RSASSA-PSS and its associated parameters.
|
| +//
|
| +// pk-rsa PUBLIC-KEY ::= {
|
| +// IDENTIFIER rsaEncryption
|
| +// KEY RSAPublicKey
|
| +// PARAMS TYPE NULL ARE absent
|
| +// -- Private key format not in this module --
|
| +// CERT-KEY-USAGE {digitalSignature, nonRepudiation,
|
| +// keyEncipherment, dataEncipherment, keyCertSign, cRLSign}
|
| +// }
|
| +//
|
| +// ...
|
| +//
|
| +// pk-rsaSSA-PSS PUBLIC-KEY ::= {
|
| +// IDENTIFIER id-RSASSA-PSS
|
| +// KEY RSAPublicKey
|
| +// PARAMS TYPE RSASSA-PSS-params ARE optional
|
| +// -- Private key format not in this module --
|
| +// CERT-KEY-USAGE { nonRepudiation, digitalSignature,
|
| +// keyCertSign, cRLSign }
|
| +// }
|
| +//
|
| +// Any RSA signature algorithm can accept a "pk-rsa" (rsaEncryption). However a
|
| +// "pk-rsaSSA-PSS" key is only accepted if the signature algorithm was for PSS
|
| +// mode:
|
| +//
|
| +// sa-rsaSSA-PSS SIGNATURE-ALGORITHM ::= {
|
| +// IDENTIFIER id-RSASSA-PSS
|
| +// PARAMS TYPE RSASSA-PSS-params ARE required
|
| +// HASHES { mda-sha1 | mda-sha224 | mda-sha256 | mda-sha384
|
| +// | mda-sha512 }
|
| +// PUBLIC-KEYS { pk-rsa | pk-rsaSSA-PSS }
|
| +// SMIME-CAPS { IDENTIFIED BY id-RSASSA-PSS }
|
| +// }
|
| +//
|
| +// Moreover, if a "pk-rsaSSA-PSS" key was used and it optionally provided
|
| +// parameters for the algorithm, they must match those of the signature
|
| +// algorithm.
|
| +//
|
| +// COMPATIBILITY NOTE: RFC 5912 and RFC 3279 are in disagreement on the value
|
| +// of parameters for rsaEncryption. Whereas RFC 5912 says they must be absent,
|
| +// RFC 3279 says they must be NULL:
|
| +//
|
| +// The rsaEncryption OID is intended to be used in the algorithm field
|
| +// of a value of type AlgorithmIdentifier. The parameters field MUST
|
| +// have ASN.1 type NULL for this algorithm identifier.
|
| +//
|
| +// Following RFC 3279 in this case.
|
| +WARN_UNUSED_RESULT bool ParseRsaKeyFromSpki(const der::Input& public_key_spki,
|
| + crypto::ScopedEVP_PKEY* pkey) {
|
| + return ImportPkeyFromSpki(public_key_spki, EVP_PKEY_RSA, pkey);
|
| +}
|
| +
|
| +// Does signature verification using either RSA or ECDSA.
|
| +//
|
| +// Note that the |signature_value| input is expected to be a byte string (and
|
| +// not a DER-encoded BIT STRING)
|
| +WARN_UNUSED_RESULT bool DoVerify(const SignatureAlgorithm& algorithm,
|
| + const der::Input& signed_data,
|
| + const der::Input& signature_value,
|
| + EVP_PKEY* public_key) {
|
| + DCHECK(algorithm.algorithm() == SignatureAlgorithmId::RsaPkcs1 ||
|
| + algorithm.algorithm() == SignatureAlgorithmId::RsaPss ||
|
| + algorithm.algorithm() == SignatureAlgorithmId::Ecdsa);
|
| +
|
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
|
| +
|
| + crypto::ScopedEVP_MD_CTX ctx(EVP_MD_CTX_create());
|
| + EVP_PKEY_CTX* pctx = nullptr; // Owned by |ctx|.
|
| +
|
| + const EVP_MD* digest;
|
| + if (!GetDigest(algorithm.digest(), &digest))
|
| + return false;
|
| +
|
| + if (!EVP_DigestVerifyInit(ctx.get(), &pctx, digest, nullptr, public_key))
|
| + return false;
|
| +
|
| + // Set the RSASSA-PSS specific options.
|
| + if (algorithm.algorithm() == SignatureAlgorithmId::RsaPss &&
|
| + !ApplyRsaPssOptions(algorithm.ParamsForRsaPss(), pctx)) {
|
| + return false;
|
| + }
|
| +
|
| + if (!EVP_DigestVerifyUpdate(ctx.get(), signed_data.UnsafeData(),
|
| + signed_data.Length())) {
|
| + return false;
|
| + }
|
| +
|
| + return 1 == EVP_DigestVerifyFinal(ctx.get(), signature_value.UnsafeData(),
|
| + signature_value.Length());
|
| +}
|
| +
|
| +// Returns true if the given curve is allowed for ECDSA. The input is a
|
| +// BoringSSL NID.
|
| +//
|
| +// TODO(eroman): Extract policy decisions such as allowed curves, hashes, RSA
|
| +// modulus size, to somewhere more central.
|
| +WARN_UNUSED_RESULT bool IsAllowedCurveName(int curve_nid) {
|
| + switch (curve_nid) {
|
| + case NID_X9_62_prime256v1:
|
| + case NID_secp384r1:
|
| + case NID_secp521r1:
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +// Parses an EC public key from SPKI to an EVP_PKEY.
|
| +//
|
| +// Returns true on success.
|
| +//
|
| +// RFC 5912 describes all the ECDSA signature algorithms as requiring a public
|
| +// key of type "pk-ec":
|
| +//
|
| +// pk-ec PUBLIC-KEY ::= {
|
| +// IDENTIFIER id-ecPublicKey
|
| +// KEY ECPoint
|
| +// PARAMS TYPE ECParameters ARE required
|
| +// -- Private key format not in this module --
|
| +// CERT-KEY-USAGE { digitalSignature, nonRepudiation, keyAgreement,
|
| +// keyCertSign, cRLSign }
|
| +// }
|
| +//
|
| +// Moreover RFC 5912 stipulates what curves are allowed. The ECParameters
|
| +// MUST NOT use an implicitCurve or specificCurve for PKIX:
|
| +//
|
| +// ECParameters ::= CHOICE {
|
| +// namedCurve CURVE.&id({NamedCurve})
|
| +// -- implicitCurve NULL
|
| +// -- implicitCurve MUST NOT be used in PKIX
|
| +// -- specifiedCurve SpecifiedCurve
|
| +// -- specifiedCurve MUST NOT be used in PKIX
|
| +// -- Details for specifiedCurve can be found in [X9.62]
|
| +// -- Any future additions to this CHOICE should be coordinated
|
| +// -- with ANSI X.9.
|
| +// }
|
| +// -- If you need to be able to decode ANSI X.9 parameter structures,
|
| +// -- uncomment the implicitCurve and specifiedCurve above, and also
|
| +// -- uncomment the following:
|
| +// --(WITH COMPONENTS {namedCurve PRESENT})
|
| +//
|
| +// The namedCurves are extensible. The ones described by RFC 5912 are:
|
| +//
|
| +// NamedCurve CURVE ::= {
|
| +// { ID secp192r1 } | { ID sect163k1 } | { ID sect163r2 } |
|
| +// { ID secp224r1 } | { ID sect233k1 } | { ID sect233r1 } |
|
| +// { ID secp256r1 } | { ID sect283k1 } | { ID sect283r1 } |
|
| +// { ID secp384r1 } | { ID sect409k1 } | { ID sect409r1 } |
|
| +// { ID secp521r1 } | { ID sect571k1 } | { ID sect571r1 },
|
| +// ... -- Extensible
|
| +// }
|
| +WARN_UNUSED_RESULT bool ParseEcKeyFromSpki(const der::Input& public_key_spki,
|
| + crypto::ScopedEVP_PKEY* pkey) {
|
| + if (!ImportPkeyFromSpki(public_key_spki, EVP_PKEY_EC, pkey))
|
| + return false;
|
| +
|
| + // Enforce policy on allowed curves in case ImportPkeyFromSpki() were to
|
| + // recognize and allow use of a weak curve.
|
| + crypto::ScopedEC_KEY ec(EVP_PKEY_get1_EC_KEY(pkey->get()));
|
| + if (!ec.get())
|
| + return false; // Unexpected.
|
| +
|
| + int curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec.get()));
|
| + return IsAllowedCurveName(curve_nid);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +bool VerifySignedData(const SignatureAlgorithm& signature_algorithm,
|
| + const der::Input& signed_data,
|
| + const der::Input& signature_value_bit_string,
|
| + const der::Input& public_key_spki) {
|
| + crypto::ScopedEVP_PKEY public_key;
|
| +
|
| + // Parse the SPKI to an EVP_PKEY appropriate for the signature algorithm.
|
| + switch (signature_algorithm.algorithm()) {
|
| + case SignatureAlgorithmId::RsaPkcs1:
|
| + case SignatureAlgorithmId::RsaPss:
|
| + if (!ParseRsaKeyFromSpki(public_key_spki, &public_key))
|
| + return false;
|
| + break;
|
| + case SignatureAlgorithmId::Ecdsa:
|
| + if (!ParseEcKeyFromSpki(public_key_spki, &public_key))
|
| + return false;
|
| + break;
|
| + }
|
| +
|
| + // Extract the bytes of the signature_value. Assume that the BIT STRING has
|
| + // no unused bits (in other words, is a multiple of 8 bits), since that is the
|
| + // case for all of the currently supported algorithms.
|
| + der::Input signature_value;
|
| + der::Parser parser(signature_value_bit_string);
|
| + if (!parser.ReadBitStringNoUnusedBits(&signature_value))
|
| + return false;
|
| + // By definition signature_value_bit_string must be a single BIT STRING.
|
| + if (parser.HasMore())
|
| + return false;
|
| +
|
| + return DoVerify(signature_algorithm, signed_data, signature_value,
|
| + public_key.get());
|
| +}
|
| +
|
| +} // namespace net
|
| +
|
| +#endif
|
|
|