summaryrefslogtreecommitdiffstats
path: root/net/cert/x509_cert_types_mac.cc
blob: 6439c7f3e77391df933b614dd9edda871f1249a2 (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 (c) 2012 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/x509_cert_types.h"

#include <CoreServices/CoreServices.h>
#include <Security/SecAsn1Coder.h>
#include <Security/Security.h>

#include "base/i18n/icu_string_conversions.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "base/strings/utf_string_conversions.h"

namespace net {

namespace {

// The BER encoding of 0.9.2342.19200300.100.1.25.
// On 10.6 and later this is available as CSSMOID_DomainComponent, which is an
// external symbol from Security.framework. However, it appears that Apple's
// implementation improperly encoded this on 10.6+, and even still is
// unavailable on 10.5, so simply including the raw BER here.
//
// Note: CSSM is allowed to store CSSM_OIDs in any arbitrary format desired,
// as long as the symbols are properly exposed. The fact that Apple's
// implementation stores it in BER is an internal implementation detail
// observed by studying libsecurity_cssm.
const uint8 kDomainComponentData[] = {
  0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19
};

const CSSM_OID kDomainComponentOID = {
    arraysize(kDomainComponentData),
    const_cast<uint8*>(kDomainComponentData)
};

const CSSM_OID* kOIDs[] = {
    &CSSMOID_CommonName,
    &CSSMOID_LocalityName,
    &CSSMOID_StateProvinceName,
    &CSSMOID_CountryName,
    &CSSMOID_StreetAddress,
    &CSSMOID_OrganizationName,
    &CSSMOID_OrganizationalUnitName,
    &kDomainComponentOID,
};

// The following structs and templates work with Apple's very arcane and under-
// documented SecAsn1Parser API, which is apparently the same as NSS's ASN.1
// decoder:
// http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn1.html

// These are used to parse the contents of a raw
// BER DistinguishedName structure.

const SecAsn1Template kStringValueTemplate[] = {
  { SEC_ASN1_CHOICE, offsetof(CSSM_X509_TYPE_VALUE_PAIR, valueType), },
  { SEC_ASN1_PRINTABLE_STRING,
    offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
    BER_TAG_PRINTABLE_STRING },
  { SEC_ASN1_IA5_STRING,
    offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
    BER_TAG_IA5_STRING },
  { SEC_ASN1_T61_STRING,
    offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
    BER_TAG_T61_STRING },
  { SEC_ASN1_UTF8_STRING,
    offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
    BER_TAG_PKIX_UTF8_STRING },
  { SEC_ASN1_BMP_STRING,
    offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
    BER_TAG_PKIX_BMP_STRING },
  { SEC_ASN1_UNIVERSAL_STRING,
    offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
    BER_TAG_PKIX_UNIVERSAL_STRING },
  { 0, }
};

const SecAsn1Template kKeyValuePairTemplate[] = {
  { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CSSM_X509_TYPE_VALUE_PAIR) },
  { SEC_ASN1_OBJECT_ID, offsetof(CSSM_X509_TYPE_VALUE_PAIR, type), },
  { SEC_ASN1_INLINE, 0, &kStringValueTemplate, },
  { 0, }
};

struct KeyValuePairs {
  CSSM_X509_TYPE_VALUE_PAIR* pairs;
};

const SecAsn1Template kKeyValuePairSetTemplate[] = {
  { SEC_ASN1_SET_OF, offsetof(KeyValuePairs, pairs),
      kKeyValuePairTemplate, sizeof(KeyValuePairs) }
};

struct X509Name {
  KeyValuePairs** pairs_list;
};

const SecAsn1Template kNameTemplate[] = {
  { SEC_ASN1_SEQUENCE_OF, offsetof(X509Name, pairs_list),
      kKeyValuePairSetTemplate, sizeof(X509Name) }
};

// Converts raw CSSM_DATA to a std::string. (Char encoding is unaltered.)
std::string DataToString(CSSM_DATA data) {
  return std::string(
      reinterpret_cast<std::string::value_type*>(data.Data),
      data.Length);
}

// Converts raw CSSM_DATA in ISO-8859-1 to a std::string in UTF-8.
std::string Latin1DataToUTF8String(CSSM_DATA data) {
  base::string16 utf16;
  if (!CodepageToUTF16(DataToString(data), base::kCodepageLatin1,
                       base::OnStringConversionError::FAIL, &utf16))
    return "";
  return UTF16ToUTF8(utf16);
}

// Converts big-endian UTF-16 to UTF-8 in a std::string.
// Note: The byte-order flipping is done in place on the input buffer!
bool UTF16BigEndianToUTF8(base::char16* chars, size_t length,
                          std::string* out_string) {
  for (size_t i = 0; i < length; i++)
    chars[i] = EndianU16_BtoN(chars[i]);
  return UTF16ToUTF8(chars, length, out_string);
}

// Converts big-endian UTF-32 to UTF-8 in a std::string.
// Note: The byte-order flipping is done in place on the input buffer!
bool UTF32BigEndianToUTF8(char32* chars, size_t length,
                          std::string* out_string) {
  for (size_t i = 0; i < length; ++i)
    chars[i] = EndianS32_BtoN(chars[i]);
#if defined(WCHAR_T_IS_UTF32)
  return WideToUTF8(reinterpret_cast<const wchar_t*>(chars),
                    length, out_string);
#else
#error This code doesn't handle 16-bit wchar_t.
#endif
}

// Adds a type+value pair to the appropriate vector from a C array.
// The array is keyed by the matching OIDs from kOIDS[].
void AddTypeValuePair(const CSSM_OID type,
                      const std::string& value,
                      std::vector<std::string>* values[]) {
  for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) {
    if (CSSMOIDEqual(&type, kOIDs[oid])) {
      values[oid]->push_back(value);
      break;
    }
  }
}

// Stores the first string of the vector, if any, to *single_value.
void SetSingle(const std::vector<std::string>& values,
               std::string* single_value) {
  // We don't expect to have more than one CN, L, S, and C.
  LOG_IF(WARNING, values.size() > 1) << "Didn't expect multiple values";
  if (!values.empty())
    *single_value = values[0];
}

bool match(const std::string& str, const std::string& against) {
  // TODO(snej): Use the full matching rules specified in RFC 5280 sec. 7.1
  // including trimming and case-folding: <http://www.ietf.org/rfc/rfc5280.txt>.
  return against == str;
}

bool match(const std::vector<std::string>& rdn1,
           const std::vector<std::string>& rdn2) {
  // "Two relative distinguished names RDN1 and RDN2 match if they have the
  // same number of naming attributes and for each naming attribute in RDN1
  // there is a matching naming attribute in RDN2." --RFC 5280 sec. 7.1.
  if (rdn1.size() != rdn2.size())
    return false;
  for (unsigned i1 = 0; i1 < rdn1.size(); ++i1) {
    unsigned i2;
    for (i2 = 0; i2 < rdn2.size(); ++i2) {
      if (match(rdn1[i1], rdn2[i2]))
          break;
    }
    if (i2 == rdn2.size())
      return false;
  }
  return true;
}

}  // namespace

bool CertPrincipal::ParseDistinguishedName(const void* ber_name_data,
                                           size_t length) {
  DCHECK(ber_name_data);

  // First parse the BER |name_data| into the above structs.
  SecAsn1CoderRef coder = NULL;
  SecAsn1CoderCreate(&coder);
  DCHECK(coder);
  X509Name* name = NULL;
  OSStatus err = SecAsn1Decode(coder, ber_name_data, length, kNameTemplate,
                               &name);
  if (err) {
    OSSTATUS_LOG(ERROR, err) << "SecAsn1Decode";
    SecAsn1CoderRelease(coder);
    return false;
  }

  // Now scan the structs and add the values to my string vectors.
  // I don't store multiple common/locality/state/country names, so use
  // temporary vectors for those.
  std::vector<std::string> common_names, locality_names, state_names,
      country_names;
  std::vector<std::string>* values[] = {
      &common_names, &locality_names,
      &state_names, &country_names,
      &this->street_addresses,
      &this->organization_names,
      &this->organization_unit_names,
      &this->domain_components
  };
  DCHECK(arraysize(kOIDs) == arraysize(values));

  for (int rdn = 0; name[rdn].pairs_list; ++rdn) {
    CSSM_X509_TYPE_VALUE_PAIR* pair;
    for (int pair_index = 0;
         NULL != (pair = name[rdn].pairs_list[0][pair_index].pairs);
         ++pair_index) {
      switch (pair->valueType) {
        case BER_TAG_IA5_STRING:          // ASCII (that means 7-bit!)
        case BER_TAG_PRINTABLE_STRING:    // a subset of ASCII
        case BER_TAG_PKIX_UTF8_STRING:    // UTF-8
          AddTypeValuePair(pair->type, DataToString(pair->value), values);
          break;
        case BER_TAG_T61_STRING:          // T61, pretend it's Latin-1
          AddTypeValuePair(pair->type,
                           Latin1DataToUTF8String(pair->value),
                           values);
          break;
        case BER_TAG_PKIX_BMP_STRING: {        // UTF-16, big-endian
          std::string value;
          UTF16BigEndianToUTF8(
              reinterpret_cast<base::char16*>(pair->value.Data),
              pair->value.Length / sizeof(base::char16),
              &value);
          AddTypeValuePair(pair->type, value, values);
          break;
        }
        case BER_TAG_PKIX_UNIVERSAL_STRING: {  // UTF-32, big-endian
          std::string value;
          UTF32BigEndianToUTF8(reinterpret_cast<char32*>(pair->value.Data),
                               pair->value.Length / sizeof(char32),
                               &value);
          AddTypeValuePair(pair->type, value, values);
          break;
        }
        default:
          DCHECK_EQ(pair->valueType, BER_TAG_UNKNOWN);
          // We don't know what data type this is, but we'll store it as a blob.
          // Displaying the string may not work, but at least it can be compared
          // byte-for-byte by a Matches() call.
          AddTypeValuePair(pair->type, DataToString(pair->value), values);
          break;
      }
    }
  }

  SetSingle(common_names, &this->common_name);
  SetSingle(locality_names, &this->locality_name);
  SetSingle(state_names, &this->state_or_province_name);
  SetSingle(country_names, &this->country_name);

  // Releasing |coder| frees all the memory pointed to via |name|.
  SecAsn1CoderRelease(coder);
  return true;
}

bool CertPrincipal::Matches(const CertPrincipal& against) const {
  return match(common_name, against.common_name) &&
      match(locality_name, against.locality_name) &&
      match(state_or_province_name, against.state_or_province_name) &&
      match(country_name, against.country_name) &&
      match(street_addresses, against.street_addresses) &&
      match(organization_names, against.organization_names) &&
      match(organization_unit_names, against.organization_unit_names) &&
      match(domain_components, against.domain_components);
}

}  // namespace net