|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /* |
|
6 * CMS attributes. |
|
7 */ |
|
8 |
|
9 #include "cmslocal.h" |
|
10 |
|
11 #include "secasn1.h" |
|
12 #include "secitem.h" |
|
13 #include "secoid.h" |
|
14 #include "pk11func.h" |
|
15 #include "prtime.h" |
|
16 #include "secerr.h" |
|
17 |
|
18 /* |
|
19 * ------------------------------------------------------------------- |
|
20 * XXX The following Attribute stuff really belongs elsewhere. |
|
21 * The Attribute type is *not* part of CMS but rather X.501. |
|
22 * But for now, since CMS is the only customer of attributes, |
|
23 * we define them here. Once there is a use outside of CMS, |
|
24 * then change the attribute types and functions from internal |
|
25 * to external naming convention, and move them elsewhere! |
|
26 */ |
|
27 |
|
28 |
|
29 /* |
|
30 * NSS_CMSAttribute_Create - create an attribute |
|
31 * |
|
32 * if value is NULL, the attribute won't have a value. It can be added later |
|
33 * with NSS_CMSAttribute_AddValue. |
|
34 */ |
|
35 NSSCMSAttribute * |
|
36 NSS_CMSAttribute_Create(PLArenaPool *poolp, SECOidTag oidtag, SECItem *value, PRBool encoded) |
|
37 { |
|
38 NSSCMSAttribute *attr; |
|
39 SECItem *copiedvalue; |
|
40 void *mark; |
|
41 |
|
42 PORT_Assert (poolp != NULL); |
|
43 |
|
44 mark = PORT_ArenaMark (poolp); |
|
45 |
|
46 attr = (NSSCMSAttribute *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSAttribute)); |
|
47 if (attr == NULL) |
|
48 goto loser; |
|
49 |
|
50 attr->typeTag = SECOID_FindOIDByTag(oidtag); |
|
51 if (attr->typeTag == NULL) |
|
52 goto loser; |
|
53 |
|
54 if (SECITEM_CopyItem(poolp, &(attr->type), &(attr->typeTag->oid)) != SECSuccess) |
|
55 goto loser; |
|
56 |
|
57 if (value != NULL) { |
|
58 if ((copiedvalue = SECITEM_ArenaDupItem(poolp, value)) == NULL) |
|
59 goto loser; |
|
60 |
|
61 if (NSS_CMSArray_Add(poolp, (void ***)&(attr->values), (void *)copiedvalue) != SECSuccess) |
|
62 goto loser; |
|
63 } |
|
64 |
|
65 attr->encoded = encoded; |
|
66 |
|
67 PORT_ArenaUnmark (poolp, mark); |
|
68 |
|
69 return attr; |
|
70 |
|
71 loser: |
|
72 PORT_Assert (mark != NULL); |
|
73 PORT_ArenaRelease (poolp, mark); |
|
74 return NULL; |
|
75 } |
|
76 |
|
77 /* |
|
78 * NSS_CMSAttribute_AddValue - add another value to an attribute |
|
79 */ |
|
80 SECStatus |
|
81 NSS_CMSAttribute_AddValue(PLArenaPool *poolp, NSSCMSAttribute *attr, SECItem *value) |
|
82 { |
|
83 SECItem *copiedvalue; |
|
84 void *mark; |
|
85 |
|
86 PORT_Assert (poolp != NULL); |
|
87 |
|
88 mark = PORT_ArenaMark(poolp); |
|
89 |
|
90 if (value == NULL) { |
|
91 PORT_SetError(SEC_ERROR_INVALID_ARGS); |
|
92 goto loser; |
|
93 } |
|
94 |
|
95 if ((copiedvalue = SECITEM_ArenaDupItem(poolp, value)) == NULL) |
|
96 goto loser; |
|
97 |
|
98 if (NSS_CMSArray_Add(poolp, (void ***)&(attr->values), (void *)copiedvalue) != SECSuccess) |
|
99 goto loser; |
|
100 |
|
101 PORT_ArenaUnmark(poolp, mark); |
|
102 return SECSuccess; |
|
103 |
|
104 loser: |
|
105 PORT_Assert (mark != NULL); |
|
106 PORT_ArenaRelease (poolp, mark); |
|
107 return SECFailure; |
|
108 } |
|
109 |
|
110 /* |
|
111 * NSS_CMSAttribute_GetType - return the OID tag |
|
112 */ |
|
113 SECOidTag |
|
114 NSS_CMSAttribute_GetType(NSSCMSAttribute *attr) |
|
115 { |
|
116 SECOidData *typetag; |
|
117 |
|
118 typetag = SECOID_FindOID(&(attr->type)); |
|
119 if (typetag == NULL) |
|
120 return SEC_OID_UNKNOWN; |
|
121 |
|
122 return typetag->offset; |
|
123 } |
|
124 |
|
125 /* |
|
126 * NSS_CMSAttribute_GetValue - return the first attribute value |
|
127 * |
|
128 * We do some sanity checking first: |
|
129 * - Multiple values are *not* expected. |
|
130 * - Empty values are *not* expected. |
|
131 */ |
|
132 SECItem * |
|
133 NSS_CMSAttribute_GetValue(NSSCMSAttribute *attr) |
|
134 { |
|
135 SECItem *value; |
|
136 |
|
137 if (attr == NULL) |
|
138 return NULL; |
|
139 |
|
140 value = attr->values[0]; |
|
141 |
|
142 if (value == NULL || value->data == NULL || value->len == 0) |
|
143 return NULL; |
|
144 |
|
145 if (attr->values[1] != NULL) |
|
146 return NULL; |
|
147 |
|
148 return value; |
|
149 } |
|
150 |
|
151 /* |
|
152 * NSS_CMSAttribute_CompareValue - compare the attribute's first value against data |
|
153 */ |
|
154 PRBool |
|
155 NSS_CMSAttribute_CompareValue(NSSCMSAttribute *attr, SECItem *av) |
|
156 { |
|
157 SECItem *value; |
|
158 |
|
159 if (attr == NULL) |
|
160 return PR_FALSE; |
|
161 |
|
162 value = NSS_CMSAttribute_GetValue(attr); |
|
163 |
|
164 return (value != NULL && value->len == av->len && |
|
165 PORT_Memcmp (value->data, av->data, value->len) == 0); |
|
166 } |
|
167 |
|
168 /* |
|
169 * templates and functions for separate ASN.1 encoding of attributes |
|
170 * |
|
171 * used in NSS_CMSAttributeArray_Reorder |
|
172 */ |
|
173 |
|
174 /* |
|
175 * helper function for dynamic template determination of the attribute value |
|
176 */ |
|
177 static const SEC_ASN1Template * |
|
178 cms_attr_choose_attr_value_template(void *src_or_dest, PRBool encoding) |
|
179 { |
|
180 const SEC_ASN1Template *theTemplate; |
|
181 NSSCMSAttribute *attribute; |
|
182 SECOidData *oiddata; |
|
183 PRBool encoded; |
|
184 |
|
185 PORT_Assert (src_or_dest != NULL); |
|
186 if (src_or_dest == NULL) |
|
187 return NULL; |
|
188 |
|
189 attribute = (NSSCMSAttribute *)src_or_dest; |
|
190 |
|
191 if (encoding && (!attribute->values || !attribute->values[0] || |
|
192 attribute->encoded)) { |
|
193 /* we're encoding, and the attribute has no value or the attribute |
|
194 * value is already encoded. */ |
|
195 return SEC_ASN1_GET(SEC_AnyTemplate); |
|
196 } |
|
197 |
|
198 /* get attribute's typeTag */ |
|
199 oiddata = attribute->typeTag; |
|
200 if (oiddata == NULL) { |
|
201 oiddata = SECOID_FindOID(&attribute->type); |
|
202 attribute->typeTag = oiddata; |
|
203 } |
|
204 |
|
205 if (oiddata == NULL) { |
|
206 /* still no OID tag? OID is unknown then. en/decode value as ANY. */ |
|
207 encoded = PR_TRUE; |
|
208 theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); |
|
209 } else { |
|
210 switch (oiddata->offset) { |
|
211 case SEC_OID_PKCS9_SMIME_CAPABILITIES: |
|
212 case SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE: |
|
213 /* these guys need to stay DER-encoded */ |
|
214 default: |
|
215 /* same goes for OIDs that are not handled here */ |
|
216 encoded = PR_TRUE; |
|
217 theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); |
|
218 break; |
|
219 /* otherwise choose proper template */ |
|
220 case SEC_OID_PKCS9_EMAIL_ADDRESS: |
|
221 case SEC_OID_RFC1274_MAIL: |
|
222 case SEC_OID_PKCS9_UNSTRUCTURED_NAME: |
|
223 encoded = PR_FALSE; |
|
224 theTemplate = SEC_ASN1_GET(SEC_IA5StringTemplate); |
|
225 break; |
|
226 case SEC_OID_PKCS9_CONTENT_TYPE: |
|
227 encoded = PR_FALSE; |
|
228 theTemplate = SEC_ASN1_GET(SEC_ObjectIDTemplate); |
|
229 break; |
|
230 case SEC_OID_PKCS9_MESSAGE_DIGEST: |
|
231 encoded = PR_FALSE; |
|
232 theTemplate = SEC_ASN1_GET(SEC_OctetStringTemplate); |
|
233 break; |
|
234 case SEC_OID_PKCS9_SIGNING_TIME: |
|
235 encoded = PR_FALSE; |
|
236 theTemplate = SEC_ASN1_GET(CERT_TimeChoiceTemplate); |
|
237 break; |
|
238 /* XXX Want other types here, too */ |
|
239 } |
|
240 } |
|
241 |
|
242 if (encoding) { |
|
243 /* |
|
244 * If we are encoding and we think we have an already-encoded value, |
|
245 * then the code which initialized this attribute should have set |
|
246 * the "encoded" property to true (and we would have returned early, |
|
247 * up above). No devastating error, but that code should be fixed. |
|
248 * (It could indicate that the resulting encoded bytes are wrong.) |
|
249 */ |
|
250 PORT_Assert (!encoded); |
|
251 } else { |
|
252 /* |
|
253 * We are decoding; record whether the resulting value is |
|
254 * still encoded or not. |
|
255 */ |
|
256 attribute->encoded = encoded; |
|
257 } |
|
258 return theTemplate; |
|
259 } |
|
260 |
|
261 static const SEC_ASN1TemplateChooserPtr cms_attr_chooser |
|
262 = cms_attr_choose_attr_value_template; |
|
263 |
|
264 const SEC_ASN1Template nss_cms_attribute_template[] = { |
|
265 { SEC_ASN1_SEQUENCE, |
|
266 0, NULL, sizeof(NSSCMSAttribute) }, |
|
267 { SEC_ASN1_OBJECT_ID, |
|
268 offsetof(NSSCMSAttribute,type) }, |
|
269 { SEC_ASN1_DYNAMIC | SEC_ASN1_SET_OF, |
|
270 offsetof(NSSCMSAttribute,values), |
|
271 &cms_attr_chooser }, |
|
272 { 0 } |
|
273 }; |
|
274 |
|
275 const SEC_ASN1Template nss_cms_set_of_attribute_template[] = { |
|
276 { SEC_ASN1_SET_OF, 0, nss_cms_attribute_template }, |
|
277 }; |
|
278 |
|
279 /* ============================================================================= |
|
280 * Attribute Array methods |
|
281 */ |
|
282 |
|
283 /* |
|
284 * NSS_CMSAttributeArray_Encode - encode an Attribute array as SET OF Attributes |
|
285 * |
|
286 * If you are wondering why this routine does not reorder the attributes |
|
287 * first, and might be tempted to make it do so, see the comment by the |
|
288 * call to ReorderAttributes in cmsencode.c. (Or, see who else calls this |
|
289 * and think long and hard about the implications of making it always |
|
290 * do the reordering.) |
|
291 */ |
|
292 SECItem * |
|
293 NSS_CMSAttributeArray_Encode(PLArenaPool *poolp, NSSCMSAttribute ***attrs, SECItem *dest) |
|
294 { |
|
295 return SEC_ASN1EncodeItem (poolp, dest, (void *)attrs, nss_cms_set_of_attribute_template); |
|
296 } |
|
297 |
|
298 /* |
|
299 * NSS_CMSAttributeArray_Reorder - sort attribute array by attribute's DER encoding |
|
300 * |
|
301 * make sure that the order of the attributes guarantees valid DER (which must be |
|
302 * in lexigraphically ascending order for a SET OF); if reordering is necessary it |
|
303 * will be done in place (in attrs). |
|
304 */ |
|
305 SECStatus |
|
306 NSS_CMSAttributeArray_Reorder(NSSCMSAttribute **attrs) |
|
307 { |
|
308 return NSS_CMSArray_SortByDER((void **)attrs, nss_cms_attribute_template, NULL); |
|
309 } |
|
310 |
|
311 /* |
|
312 * NSS_CMSAttributeArray_FindAttrByOidTag - look through a set of attributes and |
|
313 * find one that matches the specified object ID. |
|
314 * |
|
315 * If "only" is true, then make sure that there is not more than one attribute |
|
316 * of the same type. Otherwise, just return the first one found. (XXX Does |
|
317 * anybody really want that first-found behavior? It was like that when I found it...) |
|
318 */ |
|
319 NSSCMSAttribute * |
|
320 NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only) |
|
321 { |
|
322 SECOidData *oid; |
|
323 NSSCMSAttribute *attr1, *attr2; |
|
324 |
|
325 if (attrs == NULL) |
|
326 return NULL; |
|
327 |
|
328 oid = SECOID_FindOIDByTag(oidtag); |
|
329 if (oid == NULL) |
|
330 return NULL; |
|
331 |
|
332 while ((attr1 = *attrs++) != NULL) { |
|
333 if (attr1->type.len == oid->oid.len && PORT_Memcmp (attr1->type.data, |
|
334 oid->oid.data, |
|
335 oid->oid.len) == 0) |
|
336 break; |
|
337 } |
|
338 |
|
339 if (attr1 == NULL) |
|
340 return NULL; |
|
341 |
|
342 if (!only) |
|
343 return attr1; |
|
344 |
|
345 while ((attr2 = *attrs++) != NULL) { |
|
346 if (attr2->type.len == oid->oid.len && PORT_Memcmp (attr2->type.data, |
|
347 oid->oid.data, |
|
348 oid->oid.len) == 0) |
|
349 break; |
|
350 } |
|
351 |
|
352 if (attr2 != NULL) |
|
353 return NULL; |
|
354 |
|
355 return attr1; |
|
356 } |
|
357 |
|
358 /* |
|
359 * NSS_CMSAttributeArray_AddAttr - add an attribute to an |
|
360 * array of attributes. |
|
361 */ |
|
362 SECStatus |
|
363 NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr) |
|
364 { |
|
365 NSSCMSAttribute *oattr; |
|
366 void *mark; |
|
367 SECOidTag type; |
|
368 |
|
369 mark = PORT_ArenaMark(poolp); |
|
370 |
|
371 /* find oidtag of attr */ |
|
372 type = NSS_CMSAttribute_GetType(attr); |
|
373 |
|
374 /* see if we have one already */ |
|
375 oattr = NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE); |
|
376 PORT_Assert (oattr == NULL); |
|
377 if (oattr != NULL) |
|
378 goto loser; /* XXX or would it be better to replace it? */ |
|
379 |
|
380 /* no, shove it in */ |
|
381 if (NSS_CMSArray_Add(poolp, (void ***)attrs, (void *)attr) != SECSuccess) |
|
382 goto loser; |
|
383 |
|
384 PORT_ArenaUnmark(poolp, mark); |
|
385 return SECSuccess; |
|
386 |
|
387 loser: |
|
388 PORT_ArenaRelease(poolp, mark); |
|
389 return SECFailure; |
|
390 } |
|
391 |
|
392 /* |
|
393 * NSS_CMSAttributeArray_SetAttr - set an attribute's value in a set of attributes |
|
394 */ |
|
395 SECStatus |
|
396 NSS_CMSAttributeArray_SetAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, SECOidTag type, SECItem *value, PRBool encoded) |
|
397 { |
|
398 NSSCMSAttribute *attr; |
|
399 void *mark; |
|
400 |
|
401 mark = PORT_ArenaMark(poolp); |
|
402 |
|
403 /* see if we have one already */ |
|
404 attr = NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE); |
|
405 if (attr == NULL) { |
|
406 /* not found? create one! */ |
|
407 attr = NSS_CMSAttribute_Create(poolp, type, value, encoded); |
|
408 if (attr == NULL) |
|
409 goto loser; |
|
410 /* and add it to the list */ |
|
411 if (NSS_CMSArray_Add(poolp, (void ***)attrs, (void *)attr) != SECSuccess) |
|
412 goto loser; |
|
413 } else { |
|
414 /* found, shove it in */ |
|
415 /* XXX we need a decent memory model @#$#$!#!!! */ |
|
416 attr->values[0] = value; |
|
417 attr->encoded = encoded; |
|
418 } |
|
419 |
|
420 PORT_ArenaUnmark (poolp, mark); |
|
421 return SECSuccess; |
|
422 |
|
423 loser: |
|
424 PORT_ArenaRelease (poolp, mark); |
|
425 return SECFailure; |
|
426 } |
|
427 |