summaryrefslogtreecommitdiffstats
path: root/security/pkix/lib/pkixcert.cpp
blob: ffa58fd3b5152271d1516b435b87d07956a5b50a (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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
 * of licensing terms:
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
/* Copyright 2014 Mozilla Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "pkixutil.h"

namespace mozilla { namespace pkix {

Result
BackCert::Init()
{
  Result rv;

  // Certificate  ::=  SEQUENCE  {
  //         tbsCertificate       TBSCertificate,
  //         signatureAlgorithm   AlgorithmIdentifier,
  //         signatureValue       BIT STRING  }

  Reader tbsCertificate;

  // The scope of |input| and |certificate| are limited to this block so we
  // don't accidentally confuse them for tbsCertificate later.
  {
    Reader certificate;
    rv = der::ExpectTagAndGetValueAtEnd(der, der::SEQUENCE, certificate);
    if (rv != Success) {
      return rv;
    }
    rv = der::SignedData(certificate, tbsCertificate, signedData);
    if (rv != Success) {
      return rv;
    }
    rv = der::End(certificate);
    if (rv != Success) {
      return rv;
    }
  }

  // TBSCertificate  ::=  SEQUENCE  {
  //      version         [0]  EXPLICIT Version DEFAULT v1,
  //      serialNumber         CertificateSerialNumber,
  //      signature            AlgorithmIdentifier,
  //      issuer               Name,
  //      validity             Validity,
  //      subject              Name,
  //      subjectPublicKeyInfo SubjectPublicKeyInfo,
  //      issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
  //                           -- If present, version MUST be v2 or v3
  //      subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
  //                           -- If present, version MUST be v2 or v3
  //      extensions      [3]  EXPLICIT Extensions OPTIONAL
  //                           -- If present, version MUST be v3
  //      }
  rv = der::OptionalVersion(tbsCertificate, version);
  if (rv != Success) {
    return rv;
  }
  rv = der::CertificateSerialNumber(tbsCertificate, serialNumber);
  if (rv != Success) {
    return rv;
  }
  rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, signature);
  if (rv != Success) {
    return rv;
  }
  rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE, issuer);
  if (rv != Success) {
    return rv;
  }
  rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, validity);
  if (rv != Success) {
    return rv;
  }
  // TODO(bug XXXXXXX): We rely on the the caller of mozilla::pkix to validate
  // that the name is syntactically valid, if they care. In Gecko we do this
  // implicitly by parsing the certificate into a CERTCertificate object.
  // Instead of relying on the caller to do this, we should do it ourselves.
  rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE, subject);
  if (rv != Success) {
    return rv;
  }
  rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE,
                               subjectPublicKeyInfo);
  if (rv != Success) {
    return rv;
  }

  static const uint8_t CSC = der::CONTEXT_SPECIFIC | der::CONSTRUCTED;

  // According to RFC 5280, all fields below this line are forbidden for
  // certificate versions less than v3.  However, for compatibility reasons,
  // we parse v1/v2 certificates in the same way as v3 certificates.  So if
  // these fields appear in a v1 certificate, they will be used.

  // Ignore issuerUniqueID if present.
  if (tbsCertificate.Peek(CSC | 1)) {
    rv = der::ExpectTagAndSkipValue(tbsCertificate, CSC | 1);
    if (rv != Success) {
      return rv;
    }
  }

  // Ignore subjectUniqueID if present.
  if (tbsCertificate.Peek(CSC | 2)) {
    rv = der::ExpectTagAndSkipValue(tbsCertificate, CSC | 2);
    if (rv != Success) {
      return rv;
    }
  }

  rv = der::OptionalExtensions(
         tbsCertificate, CSC | 3,
         [this](Reader& extnID, const Input& extnValue, bool critical,
                /*out*/ bool& understood) {
           return RememberExtension(extnID, extnValue, critical, understood);
         });
  if (rv != Success) {
    return rv;
  }

  // The Netscape Certificate Type extension is an obsolete
  // Netscape-proprietary mechanism that we ignore in favor of the standard
  // extensions. However, some CAs have issued certificates with the Netscape
  // Cert Type extension marked critical. Thus, for compatibility reasons, we
  // "understand" this extension by ignoring it when it is not critical, and
  // by ensuring that the equivalent standardized extensions are present when
  // it is marked critical, based on the assumption that the information in
  // the Netscape Cert Type extension is consistent with the information in
  // the standard extensions.
  //
  // Here is a mapping between the Netscape Cert Type extension and the
  // standard extensions:
  //
  // Netscape Cert Type  |  BasicConstraints.cA  |  Extended Key Usage
  // --------------------+-----------------------+----------------------
  // SSL Server          |  false                |  id_kp_serverAuth
  // SSL Client          |  false                |  id_kp_clientAuth
  // S/MIME Client       |  false                |  id_kp_emailProtection
  // Object Signing      |  false                |  id_kp_codeSigning
  // SSL Server CA       |  true                 |  id_pk_serverAuth
  // SSL Client CA       |  true                 |  id_kp_clientAuth
  // S/MIME CA           |  true                 |  id_kp_emailProtection
  // Object Signing CA   |  true                 |  id_kp_codeSigning
  if (criticalNetscapeCertificateType.GetLength() > 0 &&
      (basicConstraints.GetLength() == 0 || extKeyUsage.GetLength() == 0)) {
    return Result::ERROR_UNKNOWN_CRITICAL_EXTENSION;
  }

  return der::End(tbsCertificate);
}

Result
BackCert::RememberExtension(Reader& extnID, Input extnValue,
                            bool critical, /*out*/ bool& understood)
{
  understood = false;

  // python DottedOIDToCode.py id-ce-keyUsage 2.5.29.15
  static const uint8_t id_ce_keyUsage[] = {
    0x55, 0x1d, 0x0f
  };
  // python DottedOIDToCode.py id-ce-subjectAltName 2.5.29.17
  static const uint8_t id_ce_subjectAltName[] = {
    0x55, 0x1d, 0x11
  };
  // python DottedOIDToCode.py id-ce-basicConstraints 2.5.29.19
  static const uint8_t id_ce_basicConstraints[] = {
    0x55, 0x1d, 0x13
  };
  // python DottedOIDToCode.py id-ce-nameConstraints 2.5.29.30
  static const uint8_t id_ce_nameConstraints[] = {
    0x55, 0x1d, 0x1e
  };
  // python DottedOIDToCode.py id-ce-certificatePolicies 2.5.29.32
  static const uint8_t id_ce_certificatePolicies[] = {
    0x55, 0x1d, 0x20
  };
  // python DottedOIDToCode.py id-ce-policyConstraints 2.5.29.36
  static const uint8_t id_ce_policyConstraints[] = {
    0x55, 0x1d, 0x24
  };
  // python DottedOIDToCode.py id-ce-extKeyUsage 2.5.29.37
  static const uint8_t id_ce_extKeyUsage[] = {
    0x55, 0x1d, 0x25
  };
  // python DottedOIDToCode.py id-ce-inhibitAnyPolicy 2.5.29.54
  static const uint8_t id_ce_inhibitAnyPolicy[] = {
    0x55, 0x1d, 0x36
  };
  // python DottedOIDToCode.py id-pe-authorityInfoAccess 1.3.6.1.5.5.7.1.1
  static const uint8_t id_pe_authorityInfoAccess[] = {
    0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
  };
  // python DottedOIDToCode.py id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5
  static const uint8_t id_pkix_ocsp_nocheck[] = {
    0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05
  };
  // python DottedOIDToCode.py Netscape-certificate-type 2.16.840.1.113730.1.1
  static const uint8_t Netscape_certificate_type[] = {
    0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01
  };
  // python DottedOIDToCode.py id-pe-tlsfeature 1.3.6.1.5.5.7.1.24
  static const uint8_t id_pe_tlsfeature[] = {
    0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18
  };
  // python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
  // See Section 3.3 of RFC 6962.
  static const uint8_t id_embeddedSctList[] = {
    0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
  };

  Input* out = nullptr;

  // We already enforce the maximum possible constraints for policies so we
  // can safely ignore even critical policy constraint extensions.
  //
  // XXX: Doing it this way won't allow us to detect duplicate
  // policyConstraints extensions, but that's OK because (and only because) we
  // ignore the extension.
  Input dummyPolicyConstraints;

  // We don't need to save the contents of this extension if it is present. We
  // just need to handle its presence (it is essentially ignored right now).
  Input dummyOCSPNocheck;

  // For compatibility reasons, for some extensions we have to allow empty
  // extension values. This would normally interfere with our duplicate
  // extension checking code. However, as long as the extensions we allow to
  // have empty values are also the ones we implicitly allow duplicates of,
  // this will work fine.
  bool emptyValueAllowed = false;

  // RFC says "Conforming CAs MUST mark this extension as non-critical" for
  // both authorityKeyIdentifier and subjectKeyIdentifier, and we do not use
  // them for anything, so we totally ignore them here.

  if (extnID.MatchRest(id_ce_keyUsage)) {
    out = &keyUsage;
  } else if (extnID.MatchRest(id_ce_subjectAltName)) {
    out = &subjectAltName;
  } else if (extnID.MatchRest(id_ce_basicConstraints)) {
    out = &basicConstraints;
  } else if (extnID.MatchRest(id_ce_nameConstraints)) {
    out = &nameConstraints;
  } else if (extnID.MatchRest(id_ce_certificatePolicies)) {
    out = &certificatePolicies;
  } else if (extnID.MatchRest(id_ce_policyConstraints)) {
    out = &dummyPolicyConstraints;
  } else if (extnID.MatchRest(id_ce_extKeyUsage)) {
    out = &extKeyUsage;
  } else if (extnID.MatchRest(id_ce_inhibitAnyPolicy)) {
    out = &inhibitAnyPolicy;
  } else if (extnID.MatchRest(id_pe_authorityInfoAccess)) {
    out = &authorityInfoAccess;
  } else if (extnID.MatchRest(id_pe_tlsfeature)) {
    out = &requiredTLSFeatures;
  } else if (extnID.MatchRest(id_embeddedSctList)) {
    out = &signedCertificateTimestamps;
  } else if (extnID.MatchRest(id_pkix_ocsp_nocheck) && critical) {
    // We need to make sure we don't reject delegated OCSP response signing
    // certificates that contain the id-pkix-ocsp-nocheck extension marked as
    // critical when validating OCSP responses. Without this, an application
    // that implements soft-fail OCSP might ignore a valid Revoked or Unknown
    // response, and an application that implements hard-fail OCSP might fail
    // to connect to a server given a valid Good response.
    out = &dummyOCSPNocheck;
    // We allow this extension to have an empty value.
    // See http://comments.gmane.org/gmane.ietf.x509/30947
    emptyValueAllowed = true;
  } else if (extnID.MatchRest(Netscape_certificate_type) && critical) {
    out = &criticalNetscapeCertificateType;
  }

  if (out) {
    // Don't allow an empty value for any extension we understand. This way, we
    // can test out->GetLength() != 0 or out->Init() to check for duplicates.
    if (extnValue.GetLength() == 0 && !emptyValueAllowed) {
      return Result::ERROR_EXTENSION_VALUE_INVALID;
    }
    if (out->Init(extnValue) != Success) {
      // Duplicate extension
      return Result::ERROR_EXTENSION_VALUE_INVALID;
    }
    understood = true;
  }

  return Success;
}

Result
ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
                                                   Input& sctList)
{
  Reader decodedValue;
  Result rv = der::ExpectTagAndGetValueAtEnd(extnValue, der::OCTET_STRING,
                                             decodedValue);
  if (rv != Success) {
    return rv;
  }
  return decodedValue.SkipToEnd(sctList);
}

} } // namespace mozilla::pkix