Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
5 /*
6 * CMS attributes.
7 */
9 #include "cmslocal.h"
11 #include "secasn1.h"
12 #include "secitem.h"
13 #include "secoid.h"
14 #include "pk11func.h"
15 #include "prtime.h"
16 #include "secerr.h"
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 */
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;
42 PORT_Assert (poolp != NULL);
44 mark = PORT_ArenaMark (poolp);
46 attr = (NSSCMSAttribute *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSAttribute));
47 if (attr == NULL)
48 goto loser;
50 attr->typeTag = SECOID_FindOIDByTag(oidtag);
51 if (attr->typeTag == NULL)
52 goto loser;
54 if (SECITEM_CopyItem(poolp, &(attr->type), &(attr->typeTag->oid)) != SECSuccess)
55 goto loser;
57 if (value != NULL) {
58 if ((copiedvalue = SECITEM_ArenaDupItem(poolp, value)) == NULL)
59 goto loser;
61 if (NSS_CMSArray_Add(poolp, (void ***)&(attr->values), (void *)copiedvalue) != SECSuccess)
62 goto loser;
63 }
65 attr->encoded = encoded;
67 PORT_ArenaUnmark (poolp, mark);
69 return attr;
71 loser:
72 PORT_Assert (mark != NULL);
73 PORT_ArenaRelease (poolp, mark);
74 return NULL;
75 }
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;
86 PORT_Assert (poolp != NULL);
88 mark = PORT_ArenaMark(poolp);
90 if (value == NULL) {
91 PORT_SetError(SEC_ERROR_INVALID_ARGS);
92 goto loser;
93 }
95 if ((copiedvalue = SECITEM_ArenaDupItem(poolp, value)) == NULL)
96 goto loser;
98 if (NSS_CMSArray_Add(poolp, (void ***)&(attr->values), (void *)copiedvalue) != SECSuccess)
99 goto loser;
101 PORT_ArenaUnmark(poolp, mark);
102 return SECSuccess;
104 loser:
105 PORT_Assert (mark != NULL);
106 PORT_ArenaRelease (poolp, mark);
107 return SECFailure;
108 }
110 /*
111 * NSS_CMSAttribute_GetType - return the OID tag
112 */
113 SECOidTag
114 NSS_CMSAttribute_GetType(NSSCMSAttribute *attr)
115 {
116 SECOidData *typetag;
118 typetag = SECOID_FindOID(&(attr->type));
119 if (typetag == NULL)
120 return SEC_OID_UNKNOWN;
122 return typetag->offset;
123 }
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;
137 if (attr == NULL)
138 return NULL;
140 value = attr->values[0];
142 if (value == NULL || value->data == NULL || value->len == 0)
143 return NULL;
145 if (attr->values[1] != NULL)
146 return NULL;
148 return value;
149 }
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;
159 if (attr == NULL)
160 return PR_FALSE;
162 value = NSS_CMSAttribute_GetValue(attr);
164 return (value != NULL && value->len == av->len &&
165 PORT_Memcmp (value->data, av->data, value->len) == 0);
166 }
168 /*
169 * templates and functions for separate ASN.1 encoding of attributes
170 *
171 * used in NSS_CMSAttributeArray_Reorder
172 */
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;
185 PORT_Assert (src_or_dest != NULL);
186 if (src_or_dest == NULL)
187 return NULL;
189 attribute = (NSSCMSAttribute *)src_or_dest;
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 }
198 /* get attribute's typeTag */
199 oiddata = attribute->typeTag;
200 if (oiddata == NULL) {
201 oiddata = SECOID_FindOID(&attribute->type);
202 attribute->typeTag = oiddata;
203 }
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 }
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 }
261 static const SEC_ASN1TemplateChooserPtr cms_attr_chooser
262 = cms_attr_choose_attr_value_template;
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 };
275 const SEC_ASN1Template nss_cms_set_of_attribute_template[] = {
276 { SEC_ASN1_SET_OF, 0, nss_cms_attribute_template },
277 };
279 /* =============================================================================
280 * Attribute Array methods
281 */
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 }
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 }
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;
325 if (attrs == NULL)
326 return NULL;
328 oid = SECOID_FindOIDByTag(oidtag);
329 if (oid == NULL)
330 return NULL;
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 }
339 if (attr1 == NULL)
340 return NULL;
342 if (!only)
343 return attr1;
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 }
352 if (attr2 != NULL)
353 return NULL;
355 return attr1;
356 }
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;
369 mark = PORT_ArenaMark(poolp);
371 /* find oidtag of attr */
372 type = NSS_CMSAttribute_GetType(attr);
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? */
380 /* no, shove it in */
381 if (NSS_CMSArray_Add(poolp, (void ***)attrs, (void *)attr) != SECSuccess)
382 goto loser;
384 PORT_ArenaUnmark(poolp, mark);
385 return SECSuccess;
387 loser:
388 PORT_ArenaRelease(poolp, mark);
389 return SECFailure;
390 }
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;
401 mark = PORT_ArenaMark(poolp);
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 }
420 PORT_ArenaUnmark (poolp, mark);
421 return SECSuccess;
423 loser:
424 PORT_ArenaRelease (poolp, mark);
425 return SECFailure;
426 }