michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * CMS attributes. michael@0: */ michael@0: michael@0: #include "cmslocal.h" michael@0: michael@0: #include "secasn1.h" michael@0: #include "secitem.h" michael@0: #include "secoid.h" michael@0: #include "pk11func.h" michael@0: #include "prtime.h" michael@0: #include "secerr.h" michael@0: michael@0: /* michael@0: * ------------------------------------------------------------------- michael@0: * XXX The following Attribute stuff really belongs elsewhere. michael@0: * The Attribute type is *not* part of CMS but rather X.501. michael@0: * But for now, since CMS is the only customer of attributes, michael@0: * we define them here. Once there is a use outside of CMS, michael@0: * then change the attribute types and functions from internal michael@0: * to external naming convention, and move them elsewhere! michael@0: */ michael@0: michael@0: michael@0: /* michael@0: * NSS_CMSAttribute_Create - create an attribute michael@0: * michael@0: * if value is NULL, the attribute won't have a value. It can be added later michael@0: * with NSS_CMSAttribute_AddValue. michael@0: */ michael@0: NSSCMSAttribute * michael@0: NSS_CMSAttribute_Create(PLArenaPool *poolp, SECOidTag oidtag, SECItem *value, PRBool encoded) michael@0: { michael@0: NSSCMSAttribute *attr; michael@0: SECItem *copiedvalue; michael@0: void *mark; michael@0: michael@0: PORT_Assert (poolp != NULL); michael@0: michael@0: mark = PORT_ArenaMark (poolp); michael@0: michael@0: attr = (NSSCMSAttribute *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSAttribute)); michael@0: if (attr == NULL) michael@0: goto loser; michael@0: michael@0: attr->typeTag = SECOID_FindOIDByTag(oidtag); michael@0: if (attr->typeTag == NULL) michael@0: goto loser; michael@0: michael@0: if (SECITEM_CopyItem(poolp, &(attr->type), &(attr->typeTag->oid)) != SECSuccess) michael@0: goto loser; michael@0: michael@0: if (value != NULL) { michael@0: if ((copiedvalue = SECITEM_ArenaDupItem(poolp, value)) == NULL) michael@0: goto loser; michael@0: michael@0: if (NSS_CMSArray_Add(poolp, (void ***)&(attr->values), (void *)copiedvalue) != SECSuccess) michael@0: goto loser; michael@0: } michael@0: michael@0: attr->encoded = encoded; michael@0: michael@0: PORT_ArenaUnmark (poolp, mark); michael@0: michael@0: return attr; michael@0: michael@0: loser: michael@0: PORT_Assert (mark != NULL); michael@0: PORT_ArenaRelease (poolp, mark); michael@0: return NULL; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSAttribute_AddValue - add another value to an attribute michael@0: */ michael@0: SECStatus michael@0: NSS_CMSAttribute_AddValue(PLArenaPool *poolp, NSSCMSAttribute *attr, SECItem *value) michael@0: { michael@0: SECItem *copiedvalue; michael@0: void *mark; michael@0: michael@0: PORT_Assert (poolp != NULL); michael@0: michael@0: mark = PORT_ArenaMark(poolp); michael@0: michael@0: if (value == NULL) { michael@0: PORT_SetError(SEC_ERROR_INVALID_ARGS); michael@0: goto loser; michael@0: } michael@0: michael@0: if ((copiedvalue = SECITEM_ArenaDupItem(poolp, value)) == NULL) michael@0: goto loser; michael@0: michael@0: if (NSS_CMSArray_Add(poolp, (void ***)&(attr->values), (void *)copiedvalue) != SECSuccess) michael@0: goto loser; michael@0: michael@0: PORT_ArenaUnmark(poolp, mark); michael@0: return SECSuccess; michael@0: michael@0: loser: michael@0: PORT_Assert (mark != NULL); michael@0: PORT_ArenaRelease (poolp, mark); michael@0: return SECFailure; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSAttribute_GetType - return the OID tag michael@0: */ michael@0: SECOidTag michael@0: NSS_CMSAttribute_GetType(NSSCMSAttribute *attr) michael@0: { michael@0: SECOidData *typetag; michael@0: michael@0: typetag = SECOID_FindOID(&(attr->type)); michael@0: if (typetag == NULL) michael@0: return SEC_OID_UNKNOWN; michael@0: michael@0: return typetag->offset; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSAttribute_GetValue - return the first attribute value michael@0: * michael@0: * We do some sanity checking first: michael@0: * - Multiple values are *not* expected. michael@0: * - Empty values are *not* expected. michael@0: */ michael@0: SECItem * michael@0: NSS_CMSAttribute_GetValue(NSSCMSAttribute *attr) michael@0: { michael@0: SECItem *value; michael@0: michael@0: if (attr == NULL) michael@0: return NULL; michael@0: michael@0: value = attr->values[0]; michael@0: michael@0: if (value == NULL || value->data == NULL || value->len == 0) michael@0: return NULL; michael@0: michael@0: if (attr->values[1] != NULL) michael@0: return NULL; michael@0: michael@0: return value; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSAttribute_CompareValue - compare the attribute's first value against data michael@0: */ michael@0: PRBool michael@0: NSS_CMSAttribute_CompareValue(NSSCMSAttribute *attr, SECItem *av) michael@0: { michael@0: SECItem *value; michael@0: michael@0: if (attr == NULL) michael@0: return PR_FALSE; michael@0: michael@0: value = NSS_CMSAttribute_GetValue(attr); michael@0: michael@0: return (value != NULL && value->len == av->len && michael@0: PORT_Memcmp (value->data, av->data, value->len) == 0); michael@0: } michael@0: michael@0: /* michael@0: * templates and functions for separate ASN.1 encoding of attributes michael@0: * michael@0: * used in NSS_CMSAttributeArray_Reorder michael@0: */ michael@0: michael@0: /* michael@0: * helper function for dynamic template determination of the attribute value michael@0: */ michael@0: static const SEC_ASN1Template * michael@0: cms_attr_choose_attr_value_template(void *src_or_dest, PRBool encoding) michael@0: { michael@0: const SEC_ASN1Template *theTemplate; michael@0: NSSCMSAttribute *attribute; michael@0: SECOidData *oiddata; michael@0: PRBool encoded; michael@0: michael@0: PORT_Assert (src_or_dest != NULL); michael@0: if (src_or_dest == NULL) michael@0: return NULL; michael@0: michael@0: attribute = (NSSCMSAttribute *)src_or_dest; michael@0: michael@0: if (encoding && (!attribute->values || !attribute->values[0] || michael@0: attribute->encoded)) { michael@0: /* we're encoding, and the attribute has no value or the attribute michael@0: * value is already encoded. */ michael@0: return SEC_ASN1_GET(SEC_AnyTemplate); michael@0: } michael@0: michael@0: /* get attribute's typeTag */ michael@0: oiddata = attribute->typeTag; michael@0: if (oiddata == NULL) { michael@0: oiddata = SECOID_FindOID(&attribute->type); michael@0: attribute->typeTag = oiddata; michael@0: } michael@0: michael@0: if (oiddata == NULL) { michael@0: /* still no OID tag? OID is unknown then. en/decode value as ANY. */ michael@0: encoded = PR_TRUE; michael@0: theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); michael@0: } else { michael@0: switch (oiddata->offset) { michael@0: case SEC_OID_PKCS9_SMIME_CAPABILITIES: michael@0: case SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE: michael@0: /* these guys need to stay DER-encoded */ michael@0: default: michael@0: /* same goes for OIDs that are not handled here */ michael@0: encoded = PR_TRUE; michael@0: theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); michael@0: break; michael@0: /* otherwise choose proper template */ michael@0: case SEC_OID_PKCS9_EMAIL_ADDRESS: michael@0: case SEC_OID_RFC1274_MAIL: michael@0: case SEC_OID_PKCS9_UNSTRUCTURED_NAME: michael@0: encoded = PR_FALSE; michael@0: theTemplate = SEC_ASN1_GET(SEC_IA5StringTemplate); michael@0: break; michael@0: case SEC_OID_PKCS9_CONTENT_TYPE: michael@0: encoded = PR_FALSE; michael@0: theTemplate = SEC_ASN1_GET(SEC_ObjectIDTemplate); michael@0: break; michael@0: case SEC_OID_PKCS9_MESSAGE_DIGEST: michael@0: encoded = PR_FALSE; michael@0: theTemplate = SEC_ASN1_GET(SEC_OctetStringTemplate); michael@0: break; michael@0: case SEC_OID_PKCS9_SIGNING_TIME: michael@0: encoded = PR_FALSE; michael@0: theTemplate = SEC_ASN1_GET(CERT_TimeChoiceTemplate); michael@0: break; michael@0: /* XXX Want other types here, too */ michael@0: } michael@0: } michael@0: michael@0: if (encoding) { michael@0: /* michael@0: * If we are encoding and we think we have an already-encoded value, michael@0: * then the code which initialized this attribute should have set michael@0: * the "encoded" property to true (and we would have returned early, michael@0: * up above). No devastating error, but that code should be fixed. michael@0: * (It could indicate that the resulting encoded bytes are wrong.) michael@0: */ michael@0: PORT_Assert (!encoded); michael@0: } else { michael@0: /* michael@0: * We are decoding; record whether the resulting value is michael@0: * still encoded or not. michael@0: */ michael@0: attribute->encoded = encoded; michael@0: } michael@0: return theTemplate; michael@0: } michael@0: michael@0: static const SEC_ASN1TemplateChooserPtr cms_attr_chooser michael@0: = cms_attr_choose_attr_value_template; michael@0: michael@0: const SEC_ASN1Template nss_cms_attribute_template[] = { michael@0: { SEC_ASN1_SEQUENCE, michael@0: 0, NULL, sizeof(NSSCMSAttribute) }, michael@0: { SEC_ASN1_OBJECT_ID, michael@0: offsetof(NSSCMSAttribute,type) }, michael@0: { SEC_ASN1_DYNAMIC | SEC_ASN1_SET_OF, michael@0: offsetof(NSSCMSAttribute,values), michael@0: &cms_attr_chooser }, michael@0: { 0 } michael@0: }; michael@0: michael@0: const SEC_ASN1Template nss_cms_set_of_attribute_template[] = { michael@0: { SEC_ASN1_SET_OF, 0, nss_cms_attribute_template }, michael@0: }; michael@0: michael@0: /* ============================================================================= michael@0: * Attribute Array methods michael@0: */ michael@0: michael@0: /* michael@0: * NSS_CMSAttributeArray_Encode - encode an Attribute array as SET OF Attributes michael@0: * michael@0: * If you are wondering why this routine does not reorder the attributes michael@0: * first, and might be tempted to make it do so, see the comment by the michael@0: * call to ReorderAttributes in cmsencode.c. (Or, see who else calls this michael@0: * and think long and hard about the implications of making it always michael@0: * do the reordering.) michael@0: */ michael@0: SECItem * michael@0: NSS_CMSAttributeArray_Encode(PLArenaPool *poolp, NSSCMSAttribute ***attrs, SECItem *dest) michael@0: { michael@0: return SEC_ASN1EncodeItem (poolp, dest, (void *)attrs, nss_cms_set_of_attribute_template); michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSAttributeArray_Reorder - sort attribute array by attribute's DER encoding michael@0: * michael@0: * make sure that the order of the attributes guarantees valid DER (which must be michael@0: * in lexigraphically ascending order for a SET OF); if reordering is necessary it michael@0: * will be done in place (in attrs). michael@0: */ michael@0: SECStatus michael@0: NSS_CMSAttributeArray_Reorder(NSSCMSAttribute **attrs) michael@0: { michael@0: return NSS_CMSArray_SortByDER((void **)attrs, nss_cms_attribute_template, NULL); michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSAttributeArray_FindAttrByOidTag - look through a set of attributes and michael@0: * find one that matches the specified object ID. michael@0: * michael@0: * If "only" is true, then make sure that there is not more than one attribute michael@0: * of the same type. Otherwise, just return the first one found. (XXX Does michael@0: * anybody really want that first-found behavior? It was like that when I found it...) michael@0: */ michael@0: NSSCMSAttribute * michael@0: NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only) michael@0: { michael@0: SECOidData *oid; michael@0: NSSCMSAttribute *attr1, *attr2; michael@0: michael@0: if (attrs == NULL) michael@0: return NULL; michael@0: michael@0: oid = SECOID_FindOIDByTag(oidtag); michael@0: if (oid == NULL) michael@0: return NULL; michael@0: michael@0: while ((attr1 = *attrs++) != NULL) { michael@0: if (attr1->type.len == oid->oid.len && PORT_Memcmp (attr1->type.data, michael@0: oid->oid.data, michael@0: oid->oid.len) == 0) michael@0: break; michael@0: } michael@0: michael@0: if (attr1 == NULL) michael@0: return NULL; michael@0: michael@0: if (!only) michael@0: return attr1; michael@0: michael@0: while ((attr2 = *attrs++) != NULL) { michael@0: if (attr2->type.len == oid->oid.len && PORT_Memcmp (attr2->type.data, michael@0: oid->oid.data, michael@0: oid->oid.len) == 0) michael@0: break; michael@0: } michael@0: michael@0: if (attr2 != NULL) michael@0: return NULL; michael@0: michael@0: return attr1; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSAttributeArray_AddAttr - add an attribute to an michael@0: * array of attributes. michael@0: */ michael@0: SECStatus michael@0: NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr) michael@0: { michael@0: NSSCMSAttribute *oattr; michael@0: void *mark; michael@0: SECOidTag type; michael@0: michael@0: mark = PORT_ArenaMark(poolp); michael@0: michael@0: /* find oidtag of attr */ michael@0: type = NSS_CMSAttribute_GetType(attr); michael@0: michael@0: /* see if we have one already */ michael@0: oattr = NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE); michael@0: PORT_Assert (oattr == NULL); michael@0: if (oattr != NULL) michael@0: goto loser; /* XXX or would it be better to replace it? */ michael@0: michael@0: /* no, shove it in */ michael@0: if (NSS_CMSArray_Add(poolp, (void ***)attrs, (void *)attr) != SECSuccess) michael@0: goto loser; michael@0: michael@0: PORT_ArenaUnmark(poolp, mark); michael@0: return SECSuccess; michael@0: michael@0: loser: michael@0: PORT_ArenaRelease(poolp, mark); michael@0: return SECFailure; michael@0: } michael@0: michael@0: /* michael@0: * NSS_CMSAttributeArray_SetAttr - set an attribute's value in a set of attributes michael@0: */ michael@0: SECStatus michael@0: NSS_CMSAttributeArray_SetAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, SECOidTag type, SECItem *value, PRBool encoded) michael@0: { michael@0: NSSCMSAttribute *attr; michael@0: void *mark; michael@0: michael@0: mark = PORT_ArenaMark(poolp); michael@0: michael@0: /* see if we have one already */ michael@0: attr = NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE); michael@0: if (attr == NULL) { michael@0: /* not found? create one! */ michael@0: attr = NSS_CMSAttribute_Create(poolp, type, value, encoded); michael@0: if (attr == NULL) michael@0: goto loser; michael@0: /* and add it to the list */ michael@0: if (NSS_CMSArray_Add(poolp, (void ***)attrs, (void *)attr) != SECSuccess) michael@0: goto loser; michael@0: } else { michael@0: /* found, shove it in */ michael@0: /* XXX we need a decent memory model @#$#$!#!!! */ michael@0: attr->values[0] = value; michael@0: attr->encoded = encoded; michael@0: } michael@0: michael@0: PORT_ArenaUnmark (poolp, mark); michael@0: return SECSuccess; michael@0: michael@0: loser: michael@0: PORT_ArenaRelease (poolp, mark); michael@0: return SECFailure; michael@0: } michael@0: