]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
1 | /* |
2 | * Copyright (c) 2008-2009 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | * | |
23 | */ | |
24 | ||
25 | #include <libDER/oids.h> | |
26 | #include <libDER/DER_Encode.h> | |
27 | ||
28 | #include <security_asn1/SecAsn1Types.h> | |
29 | #include <security_asn1/csrTemplates.h> | |
30 | #include <security_asn1/certExtensionTemplates.h> | |
31 | #include <security_asn1/secasn1.h> | |
32 | #include <security_asn1/SecAsn1Types.h> | |
33 | #include <security_asn1/oidsalg.h> | |
34 | #include <security_asn1/nameTemplates.h> | |
35 | ||
36 | #include <TargetConditionals.h> | |
37 | #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) | |
38 | // ENABLE_CMS 0 | |
39 | OSStatus SecCmsArraySortByDER(void **objs, const SecAsn1Template *objtemplate, void **objs2); | |
40 | #else | |
41 | // ENABLE_CMS 1 | |
42 | #include <security_smime/cmspriv.h> | |
43 | #endif | |
44 | ||
45 | #include <Security/SecInternal.h> | |
46 | #include <Security/SecCertificatePriv.h> | |
47 | #include <Security/SecIdentity.h> | |
48 | #include <Security/SecCertificateInternal.h> | |
49 | #include <Security/SecItem.h> | |
50 | #include <Security/SecKey.h> | |
51 | #include <Security/SecRSAKey.h> | |
52 | #include <Security/SecKeyPriv.h> | |
53 | #include <CommonCrypto/CommonDigest.h> | |
427c49bc | 54 | #include <CommonCrypto/CommonDigestSPI.h> |
b1ab9ed8 A |
55 | #include <CoreFoundation/CFNumber.h> |
56 | #include <CoreFoundation/CFString.h> | |
57 | ||
58 | #include <AssertMacros.h> | |
59 | ||
60 | #include "SecCertificateRequest.h" | |
61 | ||
62 | CFTypeRef kSecOidCommonName = CFSTR("CN"); | |
63 | CFTypeRef kSecOidCountryName = CFSTR("C"); | |
64 | CFTypeRef kSecOidStateProvinceName = CFSTR("ST"); | |
65 | CFTypeRef kSecOidLocalityName = CFSTR("L"); | |
66 | CFTypeRef kSecOidOrganization = CFSTR("O"); | |
67 | CFTypeRef kSecOidOrganizationalUnit = CFSTR("OU"); | |
68 | //CFTypeRef kSecOidEmailAddress = CFSTR("1.2.840.113549.1.9.1"); | |
69 | // keep natural order: C > ST > L > O > OU > CN > Email | |
70 | ||
71 | const unsigned char SecASN1PrintableString = SEC_ASN1_PRINTABLE_STRING; | |
72 | const unsigned char SecASN1UTF8String = SEC_ASN1_UTF8_STRING; | |
73 | ||
427c49bc | 74 | static uint8_t * mod128_oid_encoding_ptr(uint8_t *ptr, uint32_t src, bool final) |
b1ab9ed8 A |
75 | { |
76 | if (src > 128) | |
427c49bc | 77 | ptr = mod128_oid_encoding_ptr(ptr, src / 128, false); |
b1ab9ed8 A |
78 | |
79 | unsigned char octet = src % 128; | |
80 | if (!final) | |
81 | octet |= 128; | |
427c49bc A |
82 | *ptr++ = octet; |
83 | ||
84 | return ptr; | |
b1ab9ed8 A |
85 | } |
86 | ||
427c49bc | 87 | static uint8_t * oid_der_data(PRArenaPool *poolp, CFStringRef oid_string, size_t *oid_data_len) |
b1ab9ed8 | 88 | { |
427c49bc A |
89 | /* estimate encoded length from base 10 (4 bits) to base 128 (7 bits) */ |
90 | size_t tmp_oid_length = ((CFStringGetLength(oid_string) * 4) / 7) + 1; | |
91 | uint8_t *tmp_oid_data = PORT_ArenaAlloc(poolp, tmp_oid_length); | |
92 | uint8_t *tmp_oid_data_ptr = tmp_oid_data; | |
93 | ||
b1ab9ed8 | 94 | CFArrayRef oid = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, |
427c49bc | 95 | oid_string, CFSTR(".")); |
b1ab9ed8 A |
96 | CFIndex i = 0, count = CFArrayGetCount(oid); |
97 | SInt32 first_digit = 0, digit; | |
98 | for (i = 0; i < count; i++) { | |
99 | CFStringRef oid_octet = CFArrayGetValueAtIndex(oid, i); | |
100 | SInt32 oid_octet_int_value = CFStringGetIntValue(oid_octet); | |
101 | require(abs(oid_octet_int_value) != INT32_MAX, out); | |
102 | if (i == 0) | |
103 | first_digit = oid_octet_int_value; | |
104 | else { | |
105 | if (i == 1) | |
106 | digit = 40 * first_digit + oid_octet_int_value; | |
107 | else | |
108 | digit = oid_octet_int_value; | |
427c49bc | 109 | tmp_oid_data_ptr = mod128_oid_encoding_ptr(tmp_oid_data_ptr, digit, true); |
b1ab9ed8 A |
110 | } |
111 | } | |
112 | CFReleaseSafe(oid); | |
427c49bc A |
113 | |
114 | *oid_data_len = tmp_oid_data_ptr - tmp_oid_data; | |
115 | return tmp_oid_data; | |
b1ab9ed8 | 116 | out: |
b1ab9ed8 A |
117 | return NULL; |
118 | } | |
119 | ||
427c49bc | 120 | |
b1ab9ed8 A |
121 | /* |
122 | Get challenge password conversion and apply this: | |
123 | ||
124 | ASCII ? => PrintableString subset: [A-Za-z0-9 '()+,-./:=?] ? | |
125 | ||
126 | PrintableString > IA5String > UTF8String | |
127 | ||
128 | Consider using IA5String for email address | |
129 | */ | |
130 | ||
131 | static inline bool printable_string(CFStringRef string) | |
132 | { | |
133 | bool result = true; | |
134 | ||
135 | CFCharacterSetRef printable_charset = | |
136 | CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault, | |
137 | CFSTR("ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
138 | "abcdefghijklmnopqrstuvwxyz" | |
139 | "0123456789 '()+,-./:=?")); | |
140 | CFCharacterSetRef not_printable_charset = | |
141 | CFCharacterSetCreateInvertedSet(kCFAllocatorDefault, printable_charset); | |
142 | CFRange found; | |
143 | if (CFStringFindCharacterFromSet(string, not_printable_charset, | |
144 | CFRangeMake(0, CFStringGetLength(string)), 0, &found)) | |
145 | result = false; | |
146 | ||
147 | CFReleaseSafe(printable_charset); | |
148 | CFReleaseSafe(not_printable_charset); | |
149 | ||
150 | return result; | |
151 | } | |
152 | ||
153 | static bool make_nss_atv(PRArenaPool *poolp, | |
154 | const void * oid, const void * value, const unsigned char type_in, NSS_ATV *nss_atv) | |
155 | { | |
156 | size_t length = 0; | |
157 | char *buffer = NULL; | |
158 | unsigned char type = type_in; | |
159 | if (CFGetTypeID(value) == CFStringGetTypeID()) { | |
160 | length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value), | |
161 | kCFStringEncodingUTF8); | |
162 | buffer = PORT_ArenaAlloc(poolp, length); | |
163 | /* TODO: Switch to using CFStringGetBytes,since this code will do the wrong thing for embedded 0's */ | |
164 | if (!CFStringGetCString(value, buffer, length, kCFStringEncodingASCII)) { | |
165 | if (!CFStringGetCString(value, buffer, length, kCFStringEncodingUTF8)) | |
166 | return false; | |
167 | if (type && type != SecASN1UTF8String) | |
168 | return false; | |
169 | type = SecASN1UTF8String; | |
170 | } | |
171 | else { | |
172 | if (!type || type == SecASN1PrintableString) { | |
173 | if (!printable_string(value)) | |
174 | type = SEC_ASN1_IA5_STRING; | |
175 | else | |
176 | type = SEC_ASN1_PRINTABLE_STRING; | |
177 | } | |
178 | } | |
179 | length = strlen(buffer); | |
180 | } | |
181 | else if (CFGetTypeID(value) == CFDataGetTypeID()) { | |
182 | /* will remain valid for the duration of the operation, still maybe copy into pool */ | |
183 | length = CFDataGetLength(value); | |
184 | buffer = (char *)CFDataGetBytePtr(value); | |
185 | } | |
186 | size_t oid_length = 0; | |
187 | uint8_t *oid_data = NULL; | |
188 | if (CFGetTypeID(oid) == CFStringGetTypeID()) { | |
189 | if (CFEqual(kSecOidCommonName, oid)) { | |
190 | oid_length = oidCommonName.length; oid_data = oidCommonName.data; | |
191 | } else if (CFEqual(kSecOidCountryName, oid)) { | |
192 | oid_length = oidCountryName.length; oid_data = oidCountryName.data; | |
193 | } else if (CFEqual(kSecOidStateProvinceName, oid)) { | |
194 | oid_length = oidStateOrProvinceName.length; oid_data = oidStateOrProvinceName.data; | |
195 | } else if (CFEqual(kSecOidLocalityName, oid)) { | |
196 | oid_length = oidLocalityName.length; oid_data = oidLocalityName.data; | |
197 | } else if (CFEqual(kSecOidOrganization, oid)) { | |
198 | oid_length = oidOrganizationName.length; oid_data = oidOrganizationName.data; | |
199 | } else if (CFEqual(kSecOidOrganizationalUnit, oid)) { | |
200 | oid_length = oidOrganizationalUnitName.length; oid_data = oidOrganizationalUnitName.data; | |
201 | } else { | |
427c49bc A |
202 | oid_data = oid_der_data(poolp, oid, &oid_length); |
203 | require(oid_data, out); | |
b1ab9ed8 A |
204 | } |
205 | } else if (CFGetTypeID(oid) == CFDataGetTypeID()) { | |
206 | /* will remain valid for the duration of the operation, still maybe copy into pool */ | |
207 | oid_length = CFDataGetLength(oid); | |
208 | oid_data = (uint8_t *)CFDataGetBytePtr(oid); | |
209 | } | |
210 | NSS_ATV stage_nss_atv = { { oid_length, oid_data }, | |
211 | { { length, (uint8_t*)buffer }, type } }; | |
212 | *nss_atv = stage_nss_atv; | |
213 | return true; | |
214 | out: | |
215 | return false; | |
216 | } | |
217 | ||
218 | static NSS_RDN **make_subject(PRArenaPool *poolp, CFArrayRef subject) | |
219 | { | |
220 | if (!subject) | |
221 | return NULL; | |
222 | CFIndex rdn_ix, rdn_count = CFArrayGetCount(subject); | |
223 | NSS_RDN **rdnps = PORT_ArenaZNewArray(poolp, NSS_RDN *, rdn_count + 1); | |
224 | NSS_RDN *rdns = PORT_ArenaZNewArray(poolp, NSS_RDN, rdn_count); | |
225 | for (rdn_ix = 0; rdn_ix < rdn_count; rdn_ix++) { | |
226 | rdnps[rdn_ix] = &rdns[rdn_ix]; | |
227 | CFArrayRef rdn = CFArrayGetValueAtIndex(subject, rdn_ix); | |
228 | CFIndex atv_ix, atv_count = CFArrayGetCount(rdn); | |
229 | rdns[rdn_ix].atvs = PORT_ArenaZNewArray(poolp, NSS_ATV *, atv_count + 1); | |
230 | NSS_ATV *atvs = PORT_ArenaZNewArray(poolp, NSS_ATV, atv_count); | |
231 | for (atv_ix = 0; atv_ix < atv_count; atv_ix++) { | |
232 | rdns[rdn_ix].atvs[atv_ix] = &atvs[atv_ix]; | |
233 | CFArrayRef atv = CFArrayGetValueAtIndex(rdn, atv_ix); | |
234 | if ((CFArrayGetCount(atv) != 2) | |
235 | || !make_nss_atv(poolp, CFArrayGetValueAtIndex(atv, 0), | |
236 | CFArrayGetValueAtIndex(atv, 1), 0, &atvs[atv_ix])) | |
237 | return NULL; | |
238 | } | |
239 | } | |
240 | return rdnps; | |
241 | } | |
242 | ||
243 | struct make_general_names_context { | |
244 | PRArenaPool *poolp; | |
245 | SecAsn1Item *names; | |
246 | uint32_t count; | |
247 | uint32_t capacity; | |
248 | }; | |
249 | ||
250 | static void make_general_names(const void *key, const void *value, void *context) | |
251 | { | |
252 | struct make_general_names_context *gn = (struct make_general_names_context *)context; | |
253 | require(value,out); | |
254 | CFArrayRef gn_values = NULL; | |
255 | CFStringRef gn_value = NULL; | |
256 | CFIndex entry_ix, entry_count = 0; | |
257 | if (CFGetTypeID(value) == CFArrayGetTypeID()) { | |
258 | gn_values = (CFArrayRef)value; | |
259 | entry_count = CFArrayGetCount(value); | |
260 | } else if (CFGetTypeID(value) == CFStringGetTypeID()) { | |
261 | gn_value = (CFStringRef)value; | |
262 | entry_count = 1; | |
263 | } | |
264 | ||
265 | require(entry_count > 0, out); | |
266 | ||
267 | require(key,out); | |
268 | require(CFGetTypeID(key) == CFStringGetTypeID(), out); | |
269 | ||
270 | if (!gn->names || (gn->count == gn->capacity)) { | |
271 | uint32_t capacity = gn->capacity; | |
272 | if (capacity) | |
273 | capacity *= 2; | |
274 | else | |
275 | capacity = 10; | |
276 | ||
277 | void * new_array = PORT_ArenaZNewArray(gn->poolp, SecAsn1Item, capacity); | |
278 | if (gn->names) | |
279 | memcpy(new_array, gn->names, gn->capacity); | |
280 | gn->names = new_array; | |
281 | gn->capacity = capacity; | |
282 | } | |
283 | ||
284 | NSS_GeneralName general_name_item = { { }, -1 }; | |
285 | if (kCFCompareEqualTo == CFStringCompare(CFSTR("dNSName"), key, kCFCompareCaseInsensitive)) | |
286 | general_name_item.tag = NGT_DNSName; | |
287 | else if (kCFCompareEqualTo == CFStringCompare(CFSTR("rfc822Name"), key, kCFCompareCaseInsensitive)) | |
288 | general_name_item.tag = NGT_RFC822Name; | |
289 | else if (kCFCompareEqualTo == CFStringCompare(CFSTR("uniformResourceIdentifier"), key, kCFCompareCaseInsensitive)) | |
290 | general_name_item.tag = NGT_URI; | |
291 | else if (kCFCompareEqualTo == CFStringCompare(CFSTR("ntPrincipalName"), key, kCFCompareCaseInsensitive)) | |
292 | { | |
293 | /* | |
294 | NT Principal in SubjectAltName is defined in the context of Smartcards: | |
295 | ||
296 | http://www.oid-info.com/get/1.3.6.1.4.1.311.20.2.3 | |
297 | http://support.microsoft.com/default.aspx?scid=kb;en-us;281245 | |
298 | ||
299 | Subject Alternative Name = Other Name: Principal Name= (UPN). For example: | |
300 | UPN = user1@name.com | |
301 | The UPN OtherName OID is : "1.3.6.1.4.1.311.20.2.3" | |
302 | The UPN OtherName value: Must be ASN1-encoded UTF8 string | |
303 | Subject = Distinguished name of user. This field is a mandatory extension, but the population of this field is optional. | |
304 | */ | |
305 | ||
306 | /* OtherName ::= SEQUENCE { | |
307 | type-id OBJECT IDENTIFIER, | |
308 | value [0] EXPLICIT ANY DEFINED BY type-id | |
309 | } */ | |
310 | uint8_t nt_principal_oid[] = { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x03 }; | |
311 | typedef struct { | |
312 | SecAsn1Oid typeId; | |
313 | SecAsn1Item value; | |
314 | } nt_principal_other_name; | |
315 | nt_principal_other_name name = {}; | |
316 | ||
317 | const SecAsn1Template my_other_name_template[] = { | |
318 | { SEC_ASN1_SEQUENCE, | |
319 | 0, NULL, sizeof(nt_principal_other_name) }, | |
320 | { SEC_ASN1_OBJECT_ID, | |
321 | offsetof(nt_principal_other_name,typeId), }, | |
322 | { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0, offsetof(nt_principal_other_name,value), kSecAsn1UTF8StringTemplate, }, | |
323 | { 0, } | |
324 | }; | |
325 | const SecAsn1Template my_other_name_template_cons[] = { | |
326 | { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | NGT_OtherName, | |
327 | 0, my_other_name_template, sizeof(nt_principal_other_name) } | |
328 | }; | |
329 | ||
330 | size_t length = 0; | |
331 | char *buffer = NULL; | |
332 | ||
333 | require(gn_value, out); | |
334 | require(CFGetTypeID(gn_value) == CFStringGetTypeID(), out); | |
335 | length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value), | |
336 | kCFStringEncodingUTF8); | |
337 | buffer = PORT_ArenaAlloc(gn->poolp, length); | |
338 | if (!CFStringGetCString(value, buffer, length, kCFStringEncodingUTF8)) | |
339 | goto out; | |
340 | ||
341 | name.typeId.Length = sizeof(nt_principal_oid); | |
342 | name.typeId.Data = nt_principal_oid; | |
343 | name.value.Length = strlen(buffer); | |
344 | name.value.Data = (uint8_t*)buffer; | |
345 | SEC_ASN1EncodeItem(gn->poolp, &gn->names[gn->count], &name, my_other_name_template_cons); | |
346 | gn->count++; | |
347 | ||
348 | /* We already encoded the value for the general name */ | |
349 | goto out; | |
350 | } | |
351 | else | |
352 | goto out; | |
353 | ||
354 | if (gn_values) { | |
355 | for (entry_ix = 0; entry_ix < entry_count; entry_ix++) { | |
356 | CFTypeRef entry_value = CFArrayGetValueAtIndex(gn_values, entry_ix); | |
357 | CFIndex buffer_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength((CFStringRef)entry_value), | |
358 | kCFStringEncodingUTF8); /* we only allow ASCII => only expect IA5Strings */ | |
359 | char *buffer = (char *)PORT_ArenaZNewArray(gn->poolp, uint8_t, buffer_size); | |
360 | require(CFStringGetCString((CFStringRef)entry_value, buffer, buffer_size, kCFStringEncodingASCII), out); | |
361 | general_name_item.item.Data = (uint8_t*)buffer; | |
362 | general_name_item.item.Length = strlen(buffer); | |
363 | SEC_ASN1EncodeItem(gn->poolp, &gn->names[gn->count], &general_name_item, kSecAsn1GeneralNameTemplate); | |
364 | gn->count++; | |
365 | } | |
366 | } else if (gn_value) { | |
367 | CFIndex buffer_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(gn_value), | |
368 | kCFStringEncodingUTF8); | |
369 | char *buffer = (char *)PORT_ArenaZNewArray(gn->poolp, uint8_t, buffer_size); | |
370 | require(CFStringGetCString(gn_value, buffer, buffer_size, kCFStringEncodingASCII), out); | |
371 | general_name_item.item.Data = (uint8_t*)buffer; | |
372 | general_name_item.item.Length = strlen(buffer); | |
373 | SEC_ASN1EncodeItem(gn->poolp, &gn->names[gn->count], &general_name_item, kSecAsn1GeneralNameTemplate); | |
374 | gn->count++; | |
375 | } | |
376 | out: | |
377 | return; | |
378 | } | |
379 | ||
380 | static SecAsn1Item make_subjectAltName_extension(PRArenaPool *poolp, CFDictionaryRef subjectAltNames) | |
381 | { | |
382 | SecAsn1Item subjectAltExt = {}; | |
383 | ||
384 | struct make_general_names_context context = { poolp, NULL, 0 }; | |
385 | CFDictionaryApplyFunction(subjectAltNames, make_general_names, &context); | |
386 | ||
387 | // all general names in a sequence: | |
388 | uint32_t ix; | |
389 | SecAsn1Item **general_names = PORT_ArenaZNewArray(poolp, SecAsn1Item *, context.count + 1); | |
390 | for (ix = 0; ix < context.count; ix++) | |
391 | general_names[ix] = &context.names[ix]; | |
392 | NSS_GeneralNames gnames = { general_names }; | |
393 | SEC_ASN1EncodeItem(poolp, &subjectAltExt, &gnames, kSecAsn1GeneralNamesTemplate); | |
394 | ||
395 | return subjectAltExt; | |
396 | } | |
397 | ||
398 | CFTypeRef kSecCSRChallengePassword = CFSTR("csrChallengePassword"); | |
399 | CFTypeRef kSecSubjectAltName = CFSTR("subjectAltName"); | |
400 | CFTypeRef kSecCertificateKeyUsage = CFSTR("keyUsage"); | |
401 | CFTypeRef kSecCSRBasicContraintsPathLen = CFSTR("basicConstraints"); | |
427c49bc | 402 | CFTypeRef kSecCertificateExtensions = CFSTR("certificateExtensions"); |
b1ab9ed8 A |
403 | |
404 | static const uint8_t pkcs9ExtensionsRequested[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 14 }; | |
405 | static const uint8_t pkcs9ChallengePassword[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 7 }; | |
406 | ||
407 | static const uint8_t encoded_asn1_true = 0xFF; | |
408 | static const SecAsn1Item asn1_true = | |
409 | { sizeof(encoded_asn1_true), (uint8_t*)&encoded_asn1_true }; | |
410 | ||
411 | static inline uint32_t highest_bit(uint32_t n) | |
412 | { | |
413 | return ((n) >> 16 ? ((n)>>=16, 16) : 0) + \ | |
414 | ((n) >> 8 ? ((n)>>=8, 8) : 0) + \ | |
415 | ((n) >> 4 ? ((n)>>=4, 4) : 0) + \ | |
416 | ((n) >> 2 ? ((n)>>=2, 2) : 0) + \ | |
417 | ((n) >> 1 ? ((n)>>=1, 1) : 0) + \ | |
418 | (n); | |
419 | } | |
420 | ||
427c49bc A |
421 | struct add_custom_extension_args { |
422 | PLArenaPool *poolp; | |
423 | NSS_CertExtension *csr_extension; | |
424 | uint32_t num_extensions; | |
425 | uint32_t max_extensions; | |
426 | }; | |
427 | ||
428 | static void add_custom_extension(const void *key, const void *value, void *context) | |
429 | { | |
430 | struct add_custom_extension_args *args = (struct add_custom_extension_args *)context; | |
431 | size_t der_data_len; | |
432 | ||
433 | require(args->num_extensions < args->max_extensions, out); | |
434 | ||
435 | uint8_t * der_data = oid_der_data(args->poolp, key, &der_data_len); | |
436 | SecAsn1Item encoded_value = {}; | |
437 | ||
438 | if (CFGetTypeID(value) == CFStringGetTypeID()) { | |
439 | CFIndex buffer_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value), kCFStringEncodingUTF8); | |
440 | char *buffer = (char *)PORT_ArenaZNewArray(args->poolp, uint8_t, buffer_size); | |
441 | if (!CFStringGetCString(value, buffer, buffer_size, kCFStringEncodingUTF8)) | |
442 | goto out; | |
443 | ||
444 | SecAsn1Item buffer_item = { strlen(buffer), (uint8_t*)buffer }; | |
445 | SEC_ASN1EncodeItem(args->poolp, &encoded_value, &buffer_item, kSecAsn1UTF8StringTemplate); | |
446 | } else if (CFGetTypeID(value) == CFDataGetTypeID()) { | |
447 | SecAsn1Item data_item = { CFDataGetLength(value), (uint8_t*)CFDataGetBytePtr(value) }; | |
448 | SEC_ASN1EncodeItem(args->poolp, &encoded_value, &data_item, kSecAsn1OctetStringTemplate); | |
449 | } else | |
450 | goto out; | |
451 | ||
452 | ||
453 | if (der_data && encoded_value.Length) { | |
454 | args->csr_extension[args->num_extensions].value = encoded_value; | |
455 | args->csr_extension[args->num_extensions].extnId.Length = der_data_len; | |
456 | args->csr_extension[args->num_extensions].extnId.Data = der_data; | |
457 | args->num_extensions++; | |
458 | } | |
459 | out: | |
460 | return; | |
461 | } | |
462 | ||
b1ab9ed8 A |
463 | static |
464 | NSS_CertExtension ** | |
465 | extensions_from_parameters(PRArenaPool *poolp, CFDictionaryRef parameters) | |
466 | { | |
427c49bc A |
467 | uint32_t num_extensions = 0, max_extensions = 10; |
468 | NSS_CertExtension **csr_extensions = PORT_ArenaZNewArray(poolp, NSS_CertExtension *, max_extensions + 1); /* NULL terminated array */ | |
b1ab9ed8 A |
469 | NSS_CertExtension *csr_extension = PORT_ArenaZNewArray(poolp, NSS_CertExtension, max_extensions); |
470 | ||
471 | CFNumberRef basic_contraints_num = CFDictionaryGetValue(parameters, kSecCSRBasicContraintsPathLen); | |
472 | if (basic_contraints_num) { | |
473 | NSS_BasicConstraints basic_contraints = { asn1_true, {} }; | |
474 | uint8_t path_len; | |
475 | ||
476 | int basic_contraints_path_len = 0; | |
477 | require(CFNumberGetValue(basic_contraints_num, kCFNumberIntType, &basic_contraints_path_len), out); | |
478 | if (basic_contraints_path_len >= 0 && basic_contraints_path_len < 256) { | |
427c49bc | 479 | path_len = (uint8_t)basic_contraints_path_len; |
b1ab9ed8 A |
480 | basic_contraints.pathLenConstraint.Length = sizeof(path_len); |
481 | basic_contraints.pathLenConstraint.Data = &path_len; | |
482 | } | |
483 | ||
484 | csr_extension[num_extensions].extnId.Data = oidBasicConstraints.data; | |
485 | csr_extension[num_extensions].extnId.Length = oidBasicConstraints.length; | |
486 | csr_extension[num_extensions].critical = asn1_true; | |
487 | ||
488 | SEC_ASN1EncodeItem(poolp, &csr_extension[num_extensions].value, &basic_contraints, | |
489 | kSecAsn1BasicConstraintsTemplate); | |
490 | require(num_extensions++ < max_extensions, out); | |
491 | } | |
492 | ||
493 | CFDictionaryRef subject_alternate_names = CFDictionaryGetValue(parameters, kSecSubjectAltName); | |
494 | if (subject_alternate_names) { | |
495 | require(CFGetTypeID(subject_alternate_names) == CFDictionaryGetTypeID(), out); | |
496 | csr_extension[num_extensions].value = make_subjectAltName_extension(poolp, subject_alternate_names); | |
497 | /* set up subjectAltName cert request value */ | |
498 | csr_extension[num_extensions].extnId.Length = oidSubjectAltName.length; | |
499 | csr_extension[num_extensions].extnId.Data = oidSubjectAltName.data; | |
500 | require(num_extensions++ < max_extensions, out); | |
501 | } | |
502 | ||
503 | CFNumberRef key_usage_requested = CFDictionaryGetValue(parameters, kSecCertificateKeyUsage); | |
504 | SecAsn1Item key_usage_asn1_value = { 0 }; | |
505 | if (key_usage_requested) { | |
506 | int key_usage_value; | |
507 | require(CFNumberGetValue(key_usage_requested, kCFNumberIntType, &key_usage_value), out); | |
508 | if (key_usage_value > 0) { | |
509 | uint32_t key_usage_value_be = 0, key_usage_mask = 1<<31; | |
510 | uint32_t key_usage_value_max_bitlen = 9, key_usage_value_bitlen = 0; | |
511 | while(key_usage_value_max_bitlen) { | |
512 | if (key_usage_value & 1) { | |
513 | key_usage_value_be |= key_usage_mask; | |
514 | key_usage_value_bitlen = 10 - key_usage_value_max_bitlen; | |
515 | } | |
516 | key_usage_value >>= 1; | |
517 | key_usage_value_max_bitlen--; | |
518 | key_usage_mask >>= 1; | |
519 | } | |
520 | ||
521 | SecAsn1Item key_usage_input = { key_usage_value_bitlen, | |
522 | ((uint8_t*)&key_usage_value_be) + 3 - (key_usage_value_bitlen >> 3) }; | |
523 | SEC_ASN1EncodeItem(poolp, &key_usage_asn1_value, &key_usage_input, kSecAsn1BitStringTemplate); | |
524 | ||
525 | csr_extension[num_extensions].extnId.Data = oidKeyUsage.data; | |
526 | csr_extension[num_extensions].extnId.Length = oidKeyUsage.length; | |
527 | csr_extension[num_extensions].critical = asn1_true; | |
528 | csr_extension[num_extensions].value = key_usage_asn1_value; | |
529 | require(num_extensions++ < max_extensions, out); | |
530 | } | |
531 | } | |
532 | ||
427c49bc A |
533 | CFDictionaryRef custom_extension_requested = CFDictionaryGetValue(parameters, kSecCertificateExtensions); |
534 | if (custom_extension_requested) { | |
535 | require(CFGetTypeID(custom_extension_requested) == CFDictionaryGetTypeID(), out); | |
536 | struct add_custom_extension_args args = { | |
537 | poolp, | |
538 | csr_extension, | |
539 | num_extensions, | |
540 | max_extensions | |
541 | }; | |
542 | CFDictionaryApplyFunction(custom_extension_requested, add_custom_extension, &args); | |
543 | num_extensions = args.num_extensions; | |
544 | } | |
545 | ||
b1ab9ed8 A |
546 | /* extensions requested (subjectAltName, keyUsage) sequence of extension sequences */ |
547 | uint32_t ix = 0; | |
427c49bc | 548 | for (ix = 0; ix < num_extensions; ix++) |
b1ab9ed8 A |
549 | csr_extensions[ix] = csr_extension[ix].extnId.Length ? &csr_extension[ix] : NULL; |
550 | ||
551 | out: | |
552 | return csr_extensions; | |
553 | } | |
554 | ||
427c49bc A |
555 | |
556 | ||
b1ab9ed8 A |
557 | static |
558 | NSS_Attribute **nss_attributes_from_parameters_dict(PRArenaPool *poolp, CFDictionaryRef parameters) | |
559 | { | |
560 | /* A challenge-password attribute must have a single attribute value. | |
561 | ||
562 | ChallengePassword attribute values generated in accordance with this | |
563 | version of this document SHOULD use the PrintableString encoding | |
564 | whenever possible. If internationalization issues make this | |
565 | impossible, the UTF8String alternative SHOULD be used. PKCS #9- | |
566 | attribute processing systems MUST be able to recognize and process | |
567 | all string types in DirectoryString values. | |
568 | ||
569 | Upperbound of 255 defined for all PKCS#9 attributes. | |
570 | ||
571 | pkcs-9 OBJECT IDENTIFIER ::= {iso(1) member-body(2) us(840) | |
572 | rsadsi(113549) pkcs(1) 9} | |
573 | pkcs-9-at-challengePassword OBJECT IDENTIFIER ::= {pkcs-9 7} | |
574 | ||
575 | */ | |
576 | if (!parameters) | |
577 | return NULL; | |
578 | uint32_t num_attrs = 0; | |
579 | ||
580 | CFStringRef challenge = CFDictionaryGetValue(parameters, kSecCSRChallengePassword); | |
581 | NSS_Attribute challenge_password_attr = {}; | |
582 | if (challenge) { | |
583 | CFIndex buffer_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(challenge), | |
584 | kCFStringEncodingUTF8); /* we only allow UTF8 or ASCII */ | |
585 | char *buffer = (char *)PORT_ArenaZNewArray(poolp, uint8_t, buffer_size); | |
586 | bool utf8 = false; | |
587 | if (!CFStringGetCString(challenge, buffer, buffer_size, kCFStringEncodingASCII)) { | |
588 | if (!CFStringGetCString(challenge, buffer, buffer_size, kCFStringEncodingUTF8)) | |
589 | return NULL; | |
590 | utf8 = true; | |
591 | } else | |
592 | if (!printable_string(challenge)) | |
593 | utf8 = true; | |
594 | ||
595 | SecAsn1Item *challenge_password_value = PORT_ArenaZNewArray(poolp, SecAsn1Item, 1); | |
596 | SecAsn1Item challenge_password_raw = { strlen(buffer), (uint8_t*)buffer }; | |
597 | SEC_ASN1EncodeItem(poolp, challenge_password_value, &challenge_password_raw, | |
598 | utf8 ? kSecAsn1UTF8StringTemplate : kSecAsn1PrintableStringTemplate); | |
599 | SecAsn1Item **challenge_password_values = PORT_ArenaZNewArray(poolp, SecAsn1Item *, 2); | |
600 | challenge_password_values[0] = challenge_password_value; | |
601 | challenge_password_attr.attrType.Length = sizeof(pkcs9ChallengePassword); | |
602 | challenge_password_attr.attrType.Data = (uint8_t*)&pkcs9ChallengePassword; | |
603 | challenge_password_attr.attrValue = challenge_password_values; | |
604 | num_attrs++; | |
605 | } | |
606 | ||
607 | NSS_CertExtension **extensions = extensions_from_parameters(poolp, parameters); | |
608 | NSS_Attribute extensions_requested_attr = {}; | |
609 | if (extensions) { | |
610 | SecAsn1Item *extensions_requested_value = PORT_ArenaZNewArray(poolp, SecAsn1Item, 1); | |
611 | SEC_ASN1EncodeItem(poolp, extensions_requested_value, &extensions, kSecAsn1SequenceOfCertExtensionTemplate); | |
612 | SecAsn1Item **extensions_requested_values = PORT_ArenaZNewArray(poolp, SecAsn1Item *, 2); | |
613 | extensions_requested_values[0] = extensions_requested_value; | |
614 | extensions_requested_values[1] = NULL; | |
615 | extensions_requested_attr.attrType.Length = sizeof(pkcs9ExtensionsRequested); | |
616 | extensions_requested_attr.attrType.Data = (uint8_t*)pkcs9ExtensionsRequested; | |
617 | extensions_requested_attr.attrValue = extensions_requested_values; | |
618 | num_attrs++; | |
619 | } | |
620 | ||
621 | NSS_Attribute **attributes_ptr = PORT_ArenaZNewArray(poolp, NSS_Attribute *, num_attrs + 1); | |
622 | NSS_Attribute *attributes = PORT_ArenaZNewArray(poolp, NSS_Attribute, num_attrs); | |
623 | if (challenge_password_attr.attrType.Length) { | |
624 | --num_attrs; | |
625 | attributes[num_attrs] = challenge_password_attr; | |
626 | attributes_ptr[num_attrs] = &attributes[num_attrs]; | |
627 | } | |
628 | if (extensions_requested_attr.attrType.Length) { | |
629 | --num_attrs; | |
630 | attributes[num_attrs] = extensions_requested_attr; | |
631 | attributes_ptr[num_attrs] = &attributes[num_attrs]; | |
632 | } | |
633 | return attributes_ptr; | |
634 | #if 0 | |
635 | out: | |
636 | return NULL; | |
637 | #endif | |
638 | } | |
639 | ||
640 | static const uint8_t encoded_null[2] = { SEC_ASN1_NULL, 0 }; | |
641 | static const SecAsn1Item asn1_null = { sizeof(encoded_null), (uint8_t*)encoded_null }; | |
642 | ||
643 | CFDataRef SecGenerateCertificateRequestWithParameters(SecRDN *subject, | |
644 | CFDictionaryRef parameters, SecKeyRef publicKey, SecKeyRef privateKey) | |
645 | { | |
646 | CFDataRef csr = NULL; | |
647 | PRArenaPool *poolp = PORT_NewArena(1024); | |
648 | CFDictionaryRef pubkey_attrs = NULL; | |
649 | ||
650 | if (!poolp) | |
651 | return NULL; | |
652 | ||
653 | NSSCertRequest certReq; | |
654 | memset(&certReq, 0, sizeof(certReq)); | |
655 | ||
656 | /* version */ | |
657 | unsigned char version = 0; | |
658 | certReq.reqInfo.version.Length = sizeof(version); | |
659 | certReq.reqInfo.version.Data = &version; | |
660 | ||
661 | /* subject */ | |
662 | unsigned atv_num = 0, num = 0; | |
663 | SecRDN *one_rdn; | |
664 | SecATV *one_atv; | |
665 | for (one_rdn = subject; *one_rdn; one_rdn++) { | |
666 | for (one_atv = *one_rdn; one_atv->oid; one_atv++) | |
667 | atv_num++; | |
668 | atv_num++; /* one more */ | |
669 | num++; | |
670 | } | |
671 | NSS_ATV atvs[atv_num]; | |
672 | NSS_ATV *atvps[atv_num]; | |
673 | NSS_RDN rdns[num]; | |
674 | NSS_RDN *rdnps[num+1]; | |
675 | atv_num = 0; | |
676 | unsigned rdn_num = 0; | |
677 | for (one_rdn = subject; *one_rdn; one_rdn++) { | |
678 | rdns[rdn_num].atvs = &atvps[atv_num]; | |
679 | rdnps[rdn_num] = &rdns[rdn_num]; | |
680 | rdn_num++; | |
681 | for (one_atv = *one_rdn; one_atv->oid; one_atv++) { | |
682 | if (!make_nss_atv(poolp, one_atv->oid, one_atv->value, | |
683 | one_atv->type, &atvs[atv_num])) | |
684 | return NULL; | |
685 | atvps[atv_num] = &atvs[atv_num]; | |
686 | atv_num++; | |
687 | } | |
688 | atvps[atv_num++] = NULL; | |
689 | } | |
690 | rdnps[rdn_num] = NULL; | |
691 | certReq.reqInfo.subject.rdns = rdnps; | |
692 | ||
693 | /* public key info */ | |
694 | certReq.reqInfo.subjectPublicKeyInfo.algorithm.algorithm.Length = oidRsa.length; | |
695 | certReq.reqInfo.subjectPublicKeyInfo.algorithm.algorithm.Data = oidRsa.data; | |
696 | certReq.reqInfo.subjectPublicKeyInfo.algorithm.parameters = asn1_null; | |
697 | ||
698 | pubkey_attrs = SecKeyCopyAttributeDictionary(publicKey); | |
699 | CFDataRef pkcs1_pubkey = (CFDataRef)CFDictionaryGetValue(pubkey_attrs, kSecValueData); | |
700 | uint8_t signature[8 * CFDataGetLength(pkcs1_pubkey)]; | |
701 | size_t signature_length = sizeof(signature); | |
702 | ||
703 | certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Length = 8 * CFDataGetLength(pkcs1_pubkey); | |
704 | certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Data = (uint8_t*)CFDataGetBytePtr(pkcs1_pubkey); | |
705 | ||
706 | certReq.reqInfo.attributes = nss_attributes_from_parameters_dict(poolp, parameters); | |
707 | SecCmsArraySortByDER((void **)certReq.reqInfo.attributes, kSecAsn1AttributeTemplate, NULL); | |
708 | ||
709 | /* encode request info by itself to calculate signature */ | |
710 | SecAsn1Item reqinfo = {}; | |
711 | SEC_ASN1EncodeItem(poolp, &reqinfo, &certReq.reqInfo, kSecAsn1CertRequestInfoTemplate); | |
b1ab9ed8 A |
712 | |
713 | /* calculate signature */ | |
714 | uint8_t reqinfo_hash[CC_SHA1_DIGEST_LENGTH]; | |
427c49bc | 715 | CCDigest(kCCDigestSHA1, reqinfo.Data, (CC_LONG)reqinfo.Length, reqinfo_hash); |
b1ab9ed8 A |
716 | require_noerr_quiet(SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1, |
717 | reqinfo_hash, sizeof(reqinfo_hash), signature, &signature_length), out); | |
718 | ||
719 | /* signature and info */ | |
720 | certReq.signatureAlgorithm.algorithm.Length = oidSha1Rsa.length; | |
721 | certReq.signatureAlgorithm.algorithm.Data = oidSha1Rsa.data; | |
722 | certReq.signatureAlgorithm.parameters = asn1_null; | |
723 | certReq.signature.Data = signature; | |
724 | certReq.signature.Length = signature_length * 8; | |
725 | ||
726 | /* encode csr */ | |
727 | SecAsn1Item cert_request = {}; | |
728 | require_quiet(SEC_ASN1EncodeItem(poolp, &cert_request, &certReq, | |
729 | kSecAsn1CertRequestTemplate), out); | |
730 | csr = CFDataCreate(kCFAllocatorDefault, cert_request.Data, cert_request.Length); | |
731 | ||
732 | out: | |
733 | if (poolp) | |
734 | PORT_FreeArena(poolp, PR_TRUE); | |
735 | CFReleaseSafe(pubkey_attrs); | |
736 | return csr; | |
737 | } | |
738 | ||
739 | CFDataRef SecGenerateCertificateRequest(CFArrayRef subject, | |
740 | CFDictionaryRef parameters, SecKeyRef publicKey, SecKeyRef privateKey) | |
741 | { | |
742 | CFDataRef csr = NULL; | |
743 | PRArenaPool *poolp = PORT_NewArena(1024); | |
744 | CFDictionaryRef pubkey_attrs = NULL; | |
745 | ||
746 | if (!poolp) | |
747 | return NULL; | |
748 | ||
749 | NSSCertRequest certReq; | |
750 | memset(&certReq, 0, sizeof(certReq)); | |
751 | ||
752 | /* version */ | |
753 | unsigned char version = 0; | |
754 | certReq.reqInfo.version.Length = sizeof(version); | |
755 | certReq.reqInfo.version.Data = &version; | |
756 | ||
757 | /* subject */ | |
758 | certReq.reqInfo.subject.rdns = make_subject(poolp, (CFArrayRef)subject); | |
759 | ||
760 | /* public key info */ | |
761 | certReq.reqInfo.subjectPublicKeyInfo.algorithm.algorithm.Length = oidRsa.length; | |
762 | certReq.reqInfo.subjectPublicKeyInfo.algorithm.algorithm.Data = oidRsa.data; | |
763 | certReq.reqInfo.subjectPublicKeyInfo.algorithm.parameters = asn1_null; | |
764 | ||
765 | pubkey_attrs = SecKeyCopyAttributeDictionary(publicKey); | |
766 | CFDataRef pkcs1_pubkey = (CFDataRef)CFDictionaryGetValue(pubkey_attrs, kSecValueData); | |
767 | uint8_t signature[8 * CFDataGetLength(pkcs1_pubkey)]; | |
768 | size_t signature_length = sizeof(signature); | |
769 | ||
770 | certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Length = 8 * CFDataGetLength(pkcs1_pubkey); | |
771 | certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Data = (uint8_t*)CFDataGetBytePtr(pkcs1_pubkey); | |
772 | ||
773 | certReq.reqInfo.attributes = nss_attributes_from_parameters_dict(poolp, parameters); | |
774 | SecCmsArraySortByDER((void **)certReq.reqInfo.attributes, kSecAsn1AttributeTemplate, NULL); | |
775 | ||
776 | /* encode request info by itself to calculate signature */ | |
777 | SecAsn1Item reqinfo = {}; | |
778 | SEC_ASN1EncodeItem(poolp, &reqinfo, &certReq.reqInfo, kSecAsn1CertRequestInfoTemplate); | |
b1ab9ed8 A |
779 | |
780 | /* calculate signature */ | |
781 | uint8_t reqinfo_hash[CC_SHA1_DIGEST_LENGTH]; | |
427c49bc | 782 | CCDigest(kCCDigestSHA1, reqinfo.Data, reqinfo.Length, reqinfo_hash); |
b1ab9ed8 A |
783 | require_noerr_quiet(SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1, |
784 | reqinfo_hash, sizeof(reqinfo_hash), signature, &signature_length), out); | |
785 | ||
786 | /* signature and info */ | |
787 | certReq.signatureAlgorithm.algorithm.Length = oidSha1Rsa.length; | |
788 | certReq.signatureAlgorithm.algorithm.Data = oidSha1Rsa.data; | |
789 | certReq.signatureAlgorithm.parameters = asn1_null; | |
790 | certReq.signature.Data = signature; | |
791 | certReq.signature.Length = signature_length * 8; | |
792 | ||
793 | /* encode csr */ | |
794 | SecAsn1Item cert_request = {}; | |
795 | require_quiet(SEC_ASN1EncodeItem(poolp, &cert_request, &certReq, | |
796 | kSecAsn1CertRequestTemplate), out); | |
797 | csr = CFDataCreate(kCFAllocatorDefault, cert_request.Data, cert_request.Length); | |
798 | ||
799 | out: | |
800 | if (poolp) | |
801 | PORT_FreeArena(poolp, PR_TRUE); | |
802 | CFReleaseSafe(pubkey_attrs); | |
803 | return csr; | |
804 | } | |
805 | ||
806 | bool SecVerifyCertificateRequest(CFDataRef csr, SecKeyRef *publicKey, | |
807 | CFStringRef *challenge, CFDataRef *subject, CFDataRef *extensions) | |
808 | { | |
809 | PRArenaPool *poolp = PORT_NewArena(1024); | |
810 | SecKeyRef candidatePublicKey = NULL; | |
811 | bool valid = false; | |
812 | NSSCertRequest certReq; | |
813 | memset(&certReq, 0, sizeof(certReq)); | |
814 | SecAsn1Item csr_item = { CFDataGetLength(csr), (uint8_t*)CFDataGetBytePtr(csr) }; | |
815 | require_noerr_quiet(SEC_ASN1DecodeItem(poolp, &certReq, kSecAsn1CertRequestTemplate, | |
816 | &csr_item), out); | |
817 | ||
818 | /* signature and info */ | |
819 | require(certReq.signatureAlgorithm.algorithm.Length == oidSha1Rsa.length, out); | |
820 | require_noerr(memcmp(oidSha1Rsa.data, certReq.signatureAlgorithm.algorithm.Data, | |
821 | oidSha1Rsa.length), out); | |
822 | require(certReq.signatureAlgorithm.parameters.Length == asn1_null.Length, out); | |
823 | require_noerr(memcmp(asn1_null.Data, certReq.signatureAlgorithm.parameters.Data, | |
824 | asn1_null.Length), out); | |
825 | ||
826 | /* encode request info by itself to calculate signature */ | |
827 | SecAsn1Item reqinfo = {}; | |
828 | SEC_ASN1EncodeItem(poolp, &reqinfo, &certReq.reqInfo, kSecAsn1CertRequestInfoTemplate); | |
829 | ||
830 | /* calculate signature */ | |
831 | uint8_t reqinfo_hash[CC_SHA1_DIGEST_LENGTH]; | |
832 | require(reqinfo.Length<=UINT32_MAX, out); | |
427c49bc | 833 | CCDigest(kCCDigestSHA1, reqinfo.Data, (CC_LONG)reqinfo.Length, reqinfo_hash); |
b1ab9ed8 A |
834 | |
835 | /* @@@ check for version 0 */ | |
836 | ||
837 | require(candidatePublicKey = SecKeyCreateRSAPublicKey(kCFAllocatorDefault, | |
838 | certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Data, | |
839 | certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Length / 8, | |
840 | kSecKeyEncodingPkcs1), out); | |
841 | ||
842 | require_noerr_quiet(SecKeyRawVerify(candidatePublicKey, kSecPaddingPKCS1SHA1, | |
843 | reqinfo_hash, sizeof(reqinfo_hash), | |
844 | certReq.signature.Data, certReq.signature.Length / 8), out); | |
845 | ||
846 | SecAsn1Item subject_item = { 0 }, extensions_item = { 0 }, challenge_item = { 0 }; | |
847 | require_quiet(SEC_ASN1EncodeItem(poolp, &subject_item, | |
848 | &certReq.reqInfo.subject, kSecAsn1NameTemplate), out); | |
849 | ||
850 | if (*certReq.reqInfo.attributes) { | |
851 | uint32_t ix; | |
852 | for (ix = 0; certReq.reqInfo.attributes[ix]; ix++) { | |
853 | NSS_Attribute *attr = certReq.reqInfo.attributes[ix]; | |
854 | if ( (sizeof(pkcs9ChallengePassword) == attr->attrType.Length) && | |
855 | !memcmp(pkcs9ChallengePassword, attr->attrType.Data, sizeof(pkcs9ChallengePassword))) | |
856 | challenge_item = *attr->attrValue[0]; | |
857 | else if ( (sizeof(pkcs9ExtensionsRequested) == attr->attrType.Length) && | |
858 | !memcmp(pkcs9ExtensionsRequested, attr->attrType.Data, sizeof(pkcs9ExtensionsRequested))) | |
859 | extensions_item = *attr->attrValue[0]; | |
860 | } | |
861 | } | |
862 | ||
863 | if (subject && subject_item.Length) | |
864 | *subject = CFDataCreate(kCFAllocatorDefault, subject_item.Data, subject_item.Length); | |
865 | if (extensions && extensions_item.Length) | |
866 | *extensions = CFDataCreate(kCFAllocatorDefault, extensions_item.Data, extensions_item.Length); | |
867 | if (challenge && challenge_item.Length) { | |
868 | SecAsn1Item string = { 0 }; | |
869 | SECStatus rv = SEC_ASN1DecodeItem(poolp, &string, kSecAsn1UTF8StringTemplate, &challenge_item); | |
870 | if (rv) | |
871 | rv = SEC_ASN1DecodeItem(poolp, &string, kSecAsn1PrintableStringTemplate, &challenge_item); | |
872 | if (!rv) | |
873 | *challenge = CFStringCreateWithBytes(kCFAllocatorDefault, string.Data, string.Length, kCFStringEncodingUTF8, false); | |
874 | else | |
875 | *challenge = NULL; | |
876 | } | |
877 | if (publicKey) { | |
878 | *publicKey = candidatePublicKey; | |
879 | candidatePublicKey = NULL; | |
880 | } | |
881 | valid = true; | |
882 | out: | |
883 | CFReleaseSafe(candidatePublicKey); | |
884 | if (poolp) | |
885 | PORT_FreeArena(poolp, PR_TRUE); | |
886 | return valid; | |
887 | } | |
888 | ||
889 | #define HIDIGIT(v) (((v) / 10) + '0') | |
890 | #define LODIGIT(v) (((v) % 10) + '0') | |
891 | ||
892 | static OSStatus | |
893 | DER_CFDateToUTCTime(PRArenaPool *poolp, CFAbsoluteTime date, SecAsn1Item * utcTime) | |
894 | { | |
895 | CFGregorianDate gdate = CFAbsoluteTimeGetGregorianDate(date, NULL /* GMT */); | |
896 | unsigned char *d; | |
897 | SInt8 second; | |
898 | ||
899 | utcTime->Length = 13; | |
900 | utcTime->Data = d = PORT_ArenaAlloc(poolp, 13); | |
901 | if (!utcTime->Data) | |
902 | return SECFailure; | |
903 | ||
904 | /* UTC time does not handle the years before 1950 */ | |
905 | if (gdate.year < 1950) | |
906 | return SECFailure; | |
907 | ||
908 | /* remove the century since it's added to the year by the | |
909 | CFAbsoluteTimeGetGregorianDate routine, but is not needed for UTC time */ | |
910 | gdate.year %= 100; | |
911 | second = gdate.second; | |
912 | ||
913 | d[0] = HIDIGIT(gdate.year); | |
914 | d[1] = LODIGIT(gdate.year); | |
915 | d[2] = HIDIGIT(gdate.month); | |
916 | d[3] = LODIGIT(gdate.month); | |
917 | d[4] = HIDIGIT(gdate.day); | |
918 | d[5] = LODIGIT(gdate.day); | |
919 | d[6] = HIDIGIT(gdate.hour); | |
920 | d[7] = LODIGIT(gdate.hour); | |
921 | d[8] = HIDIGIT(gdate.minute); | |
922 | d[9] = LODIGIT(gdate.minute); | |
923 | d[10] = HIDIGIT(second); | |
924 | d[11] = LODIGIT(second); | |
925 | d[12] = 'Z'; | |
926 | return SECSuccess; | |
927 | } | |
928 | ||
929 | SecCertificateRef | |
930 | SecGenerateSelfSignedCertificate(CFArrayRef subject, CFDictionaryRef parameters, | |
931 | SecKeyRef publicKey, SecKeyRef privateKey) | |
932 | { | |
933 | SecCertificateRef cert = NULL; | |
934 | PRArenaPool *poolp = PORT_NewArena(1024); | |
935 | CFDictionaryRef pubkey_attrs = NULL; | |
936 | if (!poolp) | |
937 | return NULL; | |
938 | ||
939 | NSS_Certificate cert_tmpl; | |
940 | memset(&cert_tmpl, 0, sizeof(cert_tmpl)); | |
941 | ||
942 | /* version */ | |
943 | unsigned char version = 2; | |
944 | cert_tmpl.tbs.version.Length = sizeof(version); | |
945 | cert_tmpl.tbs.version.Data = &version; | |
946 | ||
947 | /* serialno */ | |
948 | unsigned char serialNumber = 1; | |
949 | cert_tmpl.tbs.serialNumber.Length = sizeof(serialNumber); | |
950 | cert_tmpl.tbs.serialNumber.Data = &serialNumber; | |
951 | ||
952 | /* subject/issuer */ | |
953 | cert_tmpl.tbs.issuer.rdns = make_subject(poolp, (CFArrayRef)subject); | |
954 | cert_tmpl.tbs.subject.rdns = cert_tmpl.tbs.issuer.rdns; | |
955 | ||
956 | DER_CFDateToUTCTime(poolp, CFAbsoluteTimeGetCurrent(), &cert_tmpl.tbs.validity.notBefore.item); | |
957 | cert_tmpl.tbs.validity.notBefore.tag = SEC_ASN1_UTC_TIME; | |
958 | DER_CFDateToUTCTime(poolp, CFAbsoluteTimeGetCurrent() + 3600*24*365, &cert_tmpl.tbs.validity.notAfter.item); | |
959 | cert_tmpl.tbs.validity.notAfter.tag = SEC_ASN1_UTC_TIME; | |
960 | ||
961 | /* extensions */ | |
962 | cert_tmpl.tbs.extensions = extensions_from_parameters(poolp, parameters); | |
963 | ||
964 | /* @@@ we only handle rsa keys */ | |
965 | pubkey_attrs = SecKeyCopyAttributeDictionary(publicKey); | |
966 | CFTypeRef key_type = CFDictionaryGetValue(pubkey_attrs, kSecAttrKeyType); | |
967 | if (key_type && CFEqual(key_type, kSecAttrKeyTypeRSA)) { | |
968 | /* public key data and algorithm */ | |
969 | cert_tmpl.tbs.subjectPublicKeyInfo.algorithm.algorithm = CSSMOID_RSA; | |
970 | cert_tmpl.tbs.subjectPublicKeyInfo.algorithm.parameters = asn1_null; | |
971 | ||
972 | CFDataRef pkcs1_pubkey = (CFDataRef)CFDictionaryGetValue(pubkey_attrs, kSecValueData); | |
973 | cert_tmpl.tbs.subjectPublicKeyInfo.subjectPublicKey.Length = 8 * CFDataGetLength(pkcs1_pubkey); | |
974 | cert_tmpl.tbs.subjectPublicKeyInfo.subjectPublicKey.Data = (uint8_t*)CFDataGetBytePtr(pkcs1_pubkey); | |
975 | ||
976 | /* signature algorithm */ | |
977 | cert_tmpl.tbs.signature.algorithm = CSSMOID_SHA1WithRSA; | |
978 | cert_tmpl.tbs.signature.parameters = asn1_null; | |
979 | cert_tmpl.signatureAlgorithm.algorithm = CSSMOID_SHA1WithRSA; | |
980 | cert_tmpl.signatureAlgorithm.parameters = asn1_null; | |
981 | ||
982 | /* encode request info by itself to calculate signature */ | |
983 | SecAsn1Item tbscert = {}; | |
984 | SEC_ASN1EncodeItem(poolp, &tbscert, &cert_tmpl.tbs, kSecAsn1TBSCertificateTemplate); | |
985 | ||
986 | /* calculate signature */ | |
987 | uint8_t tbscert_hash[CC_SHA1_DIGEST_LENGTH]; | |
427c49bc | 988 | CCDigest(kCCDigestSHA1, tbscert.Data, tbscert.Length, tbscert_hash); |
b1ab9ed8 A |
989 | uint8_t signature[8 * CFDataGetLength(pkcs1_pubkey)]; |
990 | size_t signature_length = sizeof(signature); | |
991 | require_noerr_quiet(SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1, | |
992 | tbscert_hash, sizeof(tbscert_hash), signature, &signature_length), out); | |
993 | ||
994 | /* signature */ | |
995 | cert_tmpl.signature.Data = signature; | |
996 | cert_tmpl.signature.Length = signature_length * 8; | |
997 | ||
998 | /* encode cert */ | |
999 | SecAsn1Item signed_cert = {}; | |
1000 | require_quiet(SEC_ASN1EncodeItem(poolp, &signed_cert, &cert_tmpl, | |
1001 | kSecAsn1SignedCertTemplate), out); | |
1002 | cert = SecCertificateCreateWithBytes(kCFAllocatorDefault, | |
1003 | signed_cert.Data, signed_cert.Length); | |
1004 | } | |
1005 | out: | |
1006 | if (poolp) | |
1007 | PORT_FreeArena(poolp, PR_TRUE); | |
1008 | CFReleaseSafe(pubkey_attrs); | |
1009 | return cert; | |
1010 | } | |
1011 | ||
1012 | ||
1013 | SecCertificateRef | |
1014 | SecIdentitySignCertificate(SecIdentityRef issuer, CFDataRef serialno, | |
1015 | SecKeyRef publicKey, CFTypeRef subject, CFTypeRef extensions) | |
1016 | { | |
1017 | SecCertificateRef cert = NULL; | |
1018 | SecKeyRef privateKey = NULL; | |
1019 | ||
1020 | PRArenaPool *poolp = PORT_NewArena(1024); | |
1021 | CFDictionaryRef pubkey_attrs = NULL; | |
1022 | if (!poolp) | |
1023 | return NULL; | |
1024 | ||
1025 | NSS_Certificate cert_tmpl; | |
1026 | memset(&cert_tmpl, 0, sizeof(cert_tmpl)); | |
1027 | ||
1028 | /* version */ | |
1029 | unsigned char version = 2; | |
1030 | cert_tmpl.tbs.version.Length = sizeof(version); | |
1031 | cert_tmpl.tbs.version.Data = &version; | |
1032 | ||
1033 | /* serialno */ | |
1034 | cert_tmpl.tbs.serialNumber.Length = CFDataGetLength(serialno); | |
1035 | cert_tmpl.tbs.serialNumber.Data = (uint8_t*)CFDataGetBytePtr(serialno); | |
1036 | ||
1037 | /* subject/issuer */ | |
1038 | if (CFArrayGetTypeID() == CFGetTypeID(subject)) | |
1039 | cert_tmpl.tbs.subject.rdns = make_subject(poolp, (CFArrayRef)subject); | |
1040 | else if (CFDataGetTypeID() == CFGetTypeID(subject)) { | |
1041 | SecAsn1Item subject_item = { CFDataGetLength(subject), (uint8_t*)CFDataGetBytePtr(subject) }; | |
1042 | require_noerr_quiet(SEC_ASN1DecodeItem(poolp, &cert_tmpl.tbs.subject.rdns, kSecAsn1NameTemplate, &subject_item), out); | |
1043 | } else | |
1044 | goto out; | |
1045 | ||
1046 | SecCertificateRef issuer_cert = NULL; | |
1047 | require_noerr(SecIdentityCopyCertificate(issuer, &issuer_cert), out); | |
427c49bc | 1048 | CFDataRef issuer_name = SecCertificateCopySubjectSequence(issuer_cert); |
b1ab9ed8 A |
1049 | SecAsn1Item issuer_item = { CFDataGetLength(issuer_name), (uint8_t*)CFDataGetBytePtr(issuer_name) }; |
1050 | require_noerr_action_quiet(SEC_ASN1DecodeItem(poolp, &cert_tmpl.tbs.issuer.rdns, | |
1051 | kSecAsn1NameTemplate, &issuer_item), out, CFReleaseNull(issuer_name)); | |
1052 | CFReleaseNull(issuer_name); | |
1053 | ||
1054 | DER_CFDateToUTCTime(poolp, CFAbsoluteTimeGetCurrent(), &cert_tmpl.tbs.validity.notBefore.item); | |
1055 | cert_tmpl.tbs.validity.notBefore.tag = SEC_ASN1_UTC_TIME; | |
1056 | DER_CFDateToUTCTime(poolp, CFAbsoluteTimeGetCurrent() + 3600*24*365, &cert_tmpl.tbs.validity.notAfter.item); | |
1057 | cert_tmpl.tbs.validity.notAfter.tag = SEC_ASN1_UTC_TIME; | |
1058 | ||
1059 | /* extensions */ | |
1060 | if (extensions) { | |
1061 | if (CFDataGetTypeID() == CFGetTypeID(extensions)) { | |
1062 | SecAsn1Item requested_extensions = { CFDataGetLength(extensions), (uint8_t*)CFDataGetBytePtr(extensions) }; | |
1063 | //NSS_CertExtension **requested_extensions_decoded; | |
1064 | require_noerr_quiet(SEC_ASN1DecodeItem(poolp, &cert_tmpl.tbs.extensions, | |
1065 | kSecAsn1SequenceOfCertExtensionTemplate, &requested_extensions), out); | |
1066 | } else if (CFDictionaryGetTypeID() == CFGetTypeID(extensions)) { | |
1067 | cert_tmpl.tbs.extensions = extensions_from_parameters(poolp, extensions); | |
1068 | } | |
1069 | } | |
1070 | ||
1071 | /* @@@ we only handle rsa keys */ | |
1072 | pubkey_attrs = SecKeyCopyAttributeDictionary(publicKey); | |
1073 | CFTypeRef key_type = CFDictionaryGetValue(pubkey_attrs, kSecAttrKeyType); | |
1074 | if (key_type && CFEqual(key_type, kSecAttrKeyTypeRSA)) { | |
1075 | /* public key data and algorithm */ | |
1076 | cert_tmpl.tbs.subjectPublicKeyInfo.algorithm.algorithm = CSSMOID_RSA; | |
1077 | cert_tmpl.tbs.subjectPublicKeyInfo.algorithm.parameters = asn1_null; | |
1078 | ||
1079 | CFDataRef pkcs1_pubkey = (CFDataRef)CFDictionaryGetValue(pubkey_attrs, kSecValueData); | |
1080 | cert_tmpl.tbs.subjectPublicKeyInfo.subjectPublicKey.Length = 8 * CFDataGetLength(pkcs1_pubkey); | |
1081 | cert_tmpl.tbs.subjectPublicKeyInfo.subjectPublicKey.Data = (uint8_t*)CFDataGetBytePtr(pkcs1_pubkey); | |
1082 | ||
1083 | /* signature algorithm */ | |
1084 | cert_tmpl.tbs.signature.algorithm = CSSMOID_SHA1WithRSA; | |
1085 | cert_tmpl.tbs.signature.parameters = asn1_null; | |
1086 | cert_tmpl.signatureAlgorithm.algorithm = CSSMOID_SHA1WithRSA; | |
1087 | cert_tmpl.signatureAlgorithm.parameters = asn1_null; | |
1088 | ||
1089 | /* encode request info by itself to calculate signature */ | |
1090 | SecAsn1Item tbscert = {}; | |
1091 | SEC_ASN1EncodeItem(poolp, &tbscert, &cert_tmpl.tbs, kSecAsn1TBSCertificateTemplate); | |
1092 | ||
1093 | /* calculate signature */ | |
1094 | uint8_t tbscert_hash[CC_SHA1_DIGEST_LENGTH]; | |
427c49bc | 1095 | CCDigest(kCCDigestSHA1, tbscert.Data, tbscert.Length, tbscert_hash); |
b1ab9ed8 A |
1096 | uint8_t signature[8 * CFDataGetLength(pkcs1_pubkey)]; |
1097 | size_t signature_length = sizeof(signature); | |
1098 | ||
1099 | require_noerr_quiet(SecIdentityCopyPrivateKey(issuer, &privateKey), out); | |
1100 | require_noerr_quiet(SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1, | |
1101 | tbscert_hash, sizeof(tbscert_hash), signature, &signature_length), out); | |
1102 | ||
1103 | /* signature */ | |
1104 | cert_tmpl.signature.Data = signature; | |
1105 | cert_tmpl.signature.Length = signature_length * 8; | |
1106 | ||
1107 | /* encode cert */ | |
1108 | SecAsn1Item signed_cert = {}; | |
1109 | require_quiet(SEC_ASN1EncodeItem(poolp, &signed_cert, &cert_tmpl, | |
1110 | kSecAsn1SignedCertTemplate), out); | |
1111 | cert = SecCertificateCreateWithBytes(kCFAllocatorDefault, | |
1112 | signed_cert.Data, signed_cert.Length); | |
1113 | } | |
1114 | out: | |
1115 | CFReleaseSafe(privateKey); | |
1116 | if (poolp) | |
1117 | PORT_FreeArena(poolp, PR_TRUE); | |
1118 | CFReleaseSafe(pubkey_attrs); | |
1119 | return cert; | |
1120 | } | |
1121 | ||
1122 | CFDataRef | |
1123 | SecGenerateCertificateRequestSubject(SecCertificateRef ca_certificate, CFArrayRef subject) | |
1124 | { | |
1125 | CFMutableDataRef sequence = NULL; | |
1126 | PRArenaPool *poolp = PORT_NewArena(1024); | |
1127 | if (!poolp) | |
1128 | return NULL; | |
1129 | ||
1130 | /* | |
1131 | Going agains the spec here: | |
1132 | ||
1133 | 3.2.3. GetCertInitial | |
1134 | ||
1135 | The messageData for this type consists of a DER-encoded | |
1136 | IssuerAndSubject (Section 3.2.3.1). The issuer is set to the | |
1137 | issuerName from the certification authority from which we are issued | |
1138 | certificates. The Subject is set to the SubjectName we used when | |
1139 | requesting the certificate. | |
1140 | ||
1141 | That clearly says use the issuer of the cert issuing certificate. Since | |
1142 | it is combined with the subject of the to-be-issued certificate, that | |
1143 | seems a mistake. If we take the subject of the issuer and the subject | |
1144 | of the certificate we're interested in, we get the issuer and subject | |
1145 | the certificate to be returned will have. | |
1146 | ||
1147 | */ | |
1148 | CFDataRef issuer_sequence = SecCertificateCopySubjectSequence(ca_certificate); | |
1149 | SecAsn1Item subject_item = { 0 }; | |
1150 | SecAsn1Item issuer_item = { CFDataGetLength(issuer_sequence), (uint8_t*)CFDataGetBytePtr(issuer_sequence) }; | |
1151 | NSS_Name nss_subject = { make_subject(poolp, subject) }; | |
1152 | require_quiet(SEC_ASN1EncodeItem(poolp, &subject_item, &nss_subject, kSecAsn1NameTemplate), out); | |
1153 | ||
1154 | DERSize sequence_length = DERLengthOfLength(subject_item.Length + issuer_item.Length); | |
1155 | DERSize seq_len_length = subject_item.Length + issuer_item.Length + 1 /* SEQUENCE */ + | |
1156 | sequence_length; | |
1157 | sequence = CFDataCreateMutable(kCFAllocatorDefault, 0); | |
1158 | CFDataSetLength(sequence, seq_len_length); | |
1159 | uint8_t *sequence_ptr = CFDataGetMutableBytePtr(sequence); | |
1160 | *sequence_ptr++ = 0x30; //ASN1_CONSTR_SEQUENCE; | |
1161 | require_noerr_quiet(DEREncodeLength(subject_item.Length + issuer_item.Length, sequence_ptr, &sequence_length), out); | |
1162 | sequence_ptr += sequence_length; | |
1163 | memcpy(sequence_ptr, issuer_item.Data, issuer_item.Length); | |
1164 | memcpy(sequence_ptr + issuer_item.Length, subject_item.Data, subject_item.Length); | |
1165 | ||
1166 | out: | |
1167 | CFReleaseSafe(issuer_sequence); | |
1168 | if (poolp) | |
1169 | PORT_FreeArena(poolp, PR_TRUE); | |
1170 | return sequence; | |
1171 | } |