Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(679)

Unified Diff: net/cert/internal/verify_signed_data.cc

Issue 1209283004: Implement VerifySignedData() for ECDSA, RSA PKCS#1 and RSA PSS. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@parse_pss
Patch Set: appease mscvc int --> bool cast Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/cert/internal/verify_signed_data.h ('k') | net/cert/internal/verify_signed_data_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « net/cert/internal/verify_signed_data.h ('k') | net/cert/internal/verify_signed_data_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698