summaryrefslogtreecommitdiff
path: root/chromium/net/cert/internal/verify_signed_data.cc
blob: fe6aae80d949a4cf058fac2ebd8e6db3d92965c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// 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 <openssl/bytestring.h>
#include <openssl/digest.h>
#include <openssl/ec.h>
#include <openssl/ec_key.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>

#include "base/compiler_specific.h"
#include "base/logging.h"
#include "crypto/openssl_util.h"
#include "crypto/scoped_openssl_types.h"
#include "net/cert/internal/signature_algorithm.h"
#include "net/cert/internal/signature_policy.h"
#include "net/der/input.h"
#include "net/der/parse_values.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.
// See https://crbug.com/522228 and https://crbug.com/522232
WARN_UNUSED_RESULT bool ImportPkeyFromSpki(const der::Input& spki,
                                           int expected_pkey_id,
                                           crypto::ScopedEVP_PKEY* pkey) {
  crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);

  CBS cbs;
  CBS_init(&cbs, spki.UnsafeData(), spki.Length());
  pkey->reset(EVP_parse_public_key(&cbs));
  if (!*pkey || CBS_len(&cbs) != 0 ||
      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. See
// https://crbug.com/522232
//
//     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,
                                            const SignaturePolicy* policy) {
  if (!ImportPkeyFromSpki(public_key_spki, EVP_PKEY_RSA, pkey))
    return false;

  // Extract the modulus length from the key.
  crypto::ScopedRSA rsa(EVP_PKEY_get1_RSA(pkey->get()));
  if (!rsa)
    return false;
  unsigned int modulus_length_bits = BN_num_bits(rsa->n);

  return policy->IsAcceptableModulusLengthForRsa(modulus_length_bits);
}

// Does signature verification using either RSA or ECDSA.
WARN_UNUSED_RESULT bool DoVerify(const SignatureAlgorithm& algorithm,
                                 const der::Input& signed_data,
                                 const der::BitString& signature_value,
                                 EVP_PKEY* public_key) {
  DCHECK(algorithm.algorithm() == SignatureAlgorithmId::RsaPkcs1 ||
         algorithm.algorithm() == SignatureAlgorithmId::RsaPss ||
         algorithm.algorithm() == SignatureAlgorithmId::Ecdsa);

  // For the supported algorithms the signature value must be a whole
  // number of bytes.
  if (signature_value.unused_bits() != 0)
    return false;
  const der::Input& signature_value_bytes = signature_value.bytes();

  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_bytes.UnsafeData(),
                                    signature_value_bytes.Length());
}

// 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,
                                           const SignaturePolicy* policy) {
  if (!ImportPkeyFromSpki(public_key_spki, EVP_PKEY_EC, pkey))
    return false;

  // Extract the curve name.
  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 policy->IsAcceptableCurveForEcdsa(curve_nid);
}

}  // namespace

bool VerifySignedData(const SignatureAlgorithm& signature_algorithm,
                      const der::Input& signed_data,
                      const der::BitString& signature_value,
                      const der::Input& public_key_spki,
                      const SignaturePolicy* policy) {
  if (!policy->IsAcceptableSignatureAlgorithm(signature_algorithm))
    return false;

  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, policy))
        return false;
      break;
    case SignatureAlgorithmId::Ecdsa:
      if (!ParseEcKeyFromSpki(public_key_spki, &public_key, policy))
        return false;
      break;
  }

  return DoVerify(signature_algorithm, signed_data, signature_value,
                  public_key.get());
}

}  // namespace net