/**************************************************************************** * * * Certificate Attribute Add/Delete Routines * * Copyright Peter Gutmann 1996-2004 * * * ****************************************************************************/ #if defined( INC_ALL ) #include "cert.h" #include "certattr.h" #include "asn1.h" #else #include "cert/cert.h" #include "cert/certattr.h" #include "misc/asn1.h" #endif /* Compiler-specific includes */ /* Prototypes for functions in ext.c */ ATTRIBUTE_LIST *findAttributeStart( ATTRIBUTE_LIST *attributeListPtr ); /**************************************************************************** * * * Utility Functions * * * ****************************************************************************/ /* Check the validity of an attribute field */ typedef enum { CHECKATTR_INFO_NONE, /* No special return info */ CHECKATTR_INFO_ZEROLENGTH, /* Zero-length data, e.g.int, bool, DN placeholder */ CHECKATTR_INFO_NEWLENGTH, /* Data length change, value in newDataLength */ CHECKATTR_INFO_LAST /* Last possible return info type */ } CHECKATTR_INFO_TYPE; static int checkAttributeField( const ATTRIBUTE_LIST *attributeListPtr, const ATTRIBUTE_INFO *attributeInfoPtr, const CRYPT_ATTRIBUTE_TYPE fieldID, const CRYPT_ATTRIBUTE_TYPE subFieldID, const void *data, const int dataLength, const int flags, CHECKATTR_INFO_TYPE *infoType, int *newDataLength, CRYPT_ERRTYPE_TYPE *errorType ) { assert( attributeListPtr == NULL || \ isReadPtr( attributeListPtr, sizeof( ATTRIBUTE_LIST ) ) ); assert( isReadPtr( attributeInfoPtr, sizeof( ATTRIBUTE_INFO ) ) ); assert( fieldID >= CRYPT_CERTINFO_FIRST_EXTENSION && \ fieldID <= CRYPT_CERTINFO_LAST ); assert( dataLength == CRYPT_UNUSED || \ ( dataLength > 0 && dataLength <= MAX_ATTRIBUTE_SIZE ) ); assert( dataLength == CRYPT_UNUSED || isReadPtr( data, dataLength ) ); assert( isWritePtr( infoType, sizeof( CHECKATTR_INFO_TYPE ) ) ); assert( isWritePtr( newDataLength, sizeof( int ) ) ); assert( !( flags & ATTR_FLAG_INVALID ) ); /* Clear return values */ *infoType = CHECKATTR_INFO_NONE; *newDataLength = 0; /* Make sure that a valid field has been specified, and that this field isn't already present as a non-default entry unless it's a field for which multiple values are allowed */ if( attributeInfoPtr == NULL ) return( CRYPT_ARGERROR_VALUE ); if( attributeListPtr != NULL && \ findAttributeField( attributeListPtr, fieldID, subFieldID ) != NULL ) { /* If it's not multivalued, we can't have any duplicate fields */ if( !( ( attributeInfoPtr->flags & FL_MULTIVALUED ) || \ ( flags & ATTR_FLAG_MULTIVALUED ) ) ) { if( errorType != NULL ) *errorType = CRYPT_ERRTYPE_ATTR_PRESENT; return( CRYPT_ERROR_INITED ); } } /* If it's a blob field, don't do any type checking. This is a special case that differs from FIELDTYPE_BLOB in that it corresponds to an ASN.1 value that's mis-encoded by one or more implementations, so we have to accept absolutely anything at this point */ if( flags & ATTR_FLAG_BLOB ) return( CRYPT_OK ); switch( attributeInfoPtr->fieldType ) { case FIELDTYPE_IDENTIFIER: /* It's an identifier, make sure that all parameters are correct */ assert( dataLength == CRYPT_UNUSED ); if( *( ( int * ) data ) != CRYPT_UNUSED ) return( CRYPT_ARGERROR_NUM1 ); /* Tell the caller that this is a special-case entry with zero-length data */ *infoType = CHECKATTR_INFO_ZEROLENGTH; return( CRYPT_OK ); case FIELDTYPE_DN: /* When creating a new cert, this is a special-case field that's used as a placeholder to indicate that a DN structure is being instantiated. When reading an encoded cert, this is the decoded DN structure */ assert( dataLength == CRYPT_UNUSED ); /* Tell the caller that this is a special-case entry with zero-length data */ *infoType = CHECKATTR_INFO_ZEROLENGTH; return( CRYPT_OK ); case BER_OBJECT_IDENTIFIER: { const BYTE *oidPtr = data; BYTE binaryOID[ MAX_OID_SIZE + 8 ]; /* If it's a BER/DER-encoded OID, make sure that it's valid ASN.1 */ if( oidPtr[ 0 ] == BER_OBJECT_IDENTIFIER ) { if( dataLength >= 3 && sizeofOID( oidPtr ) == dataLength ) return( CRYPT_OK ); } else { int status; /* It's a text OID, check the syntax and make sure that the length is valid */ status = textToOID( data, dataLength, binaryOID, MAX_OID_SIZE ); if( !cryptStatusError( status ) ) { /* The binary form of the OID differs in length from the string form, tell the caller that the data length has changed */ *infoType = CHECKATTR_INFO_NEWLENGTH; *newDataLength = status; return( CRYPT_OK ); } } if( errorType != NULL ) *errorType = CRYPT_ERRTYPE_ATTR_VALUE; return( CRYPT_ARGERROR_STR1 ); } case BER_BOOLEAN: assert( dataLength == CRYPT_UNUSED ); /* BOOLEAN data is accepted as zero/nonzero so it's always valid, however we let the caller know that this is non-string data with no storage requirements */ *infoType = CHECKATTR_INFO_ZEROLENGTH; return( CRYPT_OK ); case BER_INTEGER: case BER_ENUMERATED: case BER_BITSTRING: case BER_NULL: case FIELDTYPE_CHOICE: { int value = *( ( int * ) data ); /* Check that the range is valid */ if( value < attributeInfoPtr->lowRange || \ value > attributeInfoPtr->highRange ) { if( errorType != NULL ) *errorType = CRYPT_ERRTYPE_ATTR_VALUE; return( CRYPT_ARGERROR_NUM1 ); } /* Let the caller know that this is non-string data with no storage requirements */ *infoType = CHECKATTR_INFO_ZEROLENGTH; return( CRYPT_OK ); } } /* It's some sort of string value, perform a general data size check */ if( dataLength < attributeInfoPtr->lowRange || \ dataLength > attributeInfoPtr->highRange ) { if( errorType != NULL ) *errorType = CRYPT_ERRTYPE_ATTR_SIZE; return( CRYPT_ARGERROR_NUM1 ); } /* If we're not checking the payload in order to handle CAs who stuff any old rubbish into the fields, exit now unless it's a blob field, for which we need to find at least valid ASN.1 data */ if( ( flags & ATTR_FLAG_BLOB_PAYLOAD ) && \ ( attributeInfoPtr->fieldType != FIELDTYPE_BLOB ) ) return( CRYPT_OK ); switch( attributeInfoPtr->fieldType ) { case FIELDTYPE_BLOB: /* It's a blob field, make sure that it's a valid ASN.1 object */ if( cryptStatusError( checkObjectEncoding( data, dataLength ) ) ) { if( errorType != NULL ) *errorType = CRYPT_ERRTYPE_ATTR_VALUE; return( CRYPT_ARGERROR_STR1 ); } return( CRYPT_OK ); case BER_STRING_NUMERIC: { const char *dataPtr = data; int i; /* Make sure that it's a numeric string */ for( i = 0; i < dataLength; i++ ) { if( !isDigit( dataPtr[ i ] ) ) { if( errorType != NULL ) *errorType = CRYPT_ERRTYPE_ATTR_VALUE; return( CRYPT_ARGERROR_STR1 ); } } return( CRYPT_OK ); } case BER_STRING_IA5: case BER_STRING_ISO646: case BER_STRING_PRINTABLE: /* Make sure that it's an ASCII string of the correct type */ if( !checkTextStringData( data, dataLength, ( attributeInfoPtr->fieldType == BER_STRING_PRINTABLE ) ? \ TRUE : FALSE ) ) { if( errorType != NULL ) *errorType = CRYPT_ERRTYPE_ATTR_VALUE; return( CRYPT_ARGERROR_STR1 ); } return( CRYPT_OK ); } return( CRYPT_OK ); } /**************************************************************************** * * * Add Attribute Data * * * ****************************************************************************/ /* Add a blob-type attribute to a list of attributes */ int addAttribute( const ATTRIBUTE_TYPE attributeType, ATTRIBUTE_LIST **listHeadPtr, const BYTE *oid, const BOOLEAN criticalFlag, const void *data, const int dataLength, const int flags ) { ATTRIBUTE_LIST *newElement, *insertPoint = NULL; const int oidLength = sizeofOID( oid ); assert( isWritePtr( listHeadPtr, sizeof( ATTRIBUTE_LIST * ) ) ); assert( isReadPtr( oid, oidLength ) ); assert( criticalFlag == TRUE || criticalFlag == FALSE ); assert( isReadPtr( data, dataLength ) ); assert( ( flags & ( ATTR_FLAG_IGNORED | ATTR_FLAG_BLOB ) ) || \ !cryptStatusError( checkObjectEncoding( data, dataLength ) ) ); assert( dataLength > 0 && dataLength <= MAX_ATTRIBUTE_SIZE ); assert( flags == ATTR_FLAG_NONE || flags == ATTR_FLAG_BLOB || \ flags == ( ATTR_FLAG_BLOB | ATTR_FLAG_IGNORED ) ); /* If this attribute type is already handled as a non-blob attribute, don't allow it to be added as a blob as well. This avoids problems with the same attribute being added twice, once as a blob and once as a non-blob. In addition it forces the caller to use the (recommended) normal attribute handling mechanism, which allows for proper type checking */ if( !( flags & ATTR_FLAG_BLOB ) && \ oidToAttribute( attributeType, oid ) != NULL ) return( CRYPT_ERROR_PERMISSION ); /* Find the correct place in the list to insert the new element */ if( *listHeadPtr != NULL ) { ATTRIBUTE_LIST *prevElement = NULL; for( insertPoint = *listHeadPtr; insertPoint != NULL; insertPoint = insertPoint->next ) { /* Make sure that this blob attribute isn't already present */ if( isBlobAttribute( insertPoint ) && \ sizeofOID( insertPoint->oid ) == oidLength && \ !memcmp( insertPoint->oid, oid, oidLength ) ) return( CRYPT_ERROR_INITED ); prevElement = insertPoint; } insertPoint = prevElement; } /* Allocate memory for the new element and copy the information across. The data is stored in storage ... storage + dataLength, the OID in storage + dataLength ... storage + dataLength + oidLength */ if( ( newElement = ( ATTRIBUTE_LIST * ) \ clAlloc( "addAttribute", sizeof( ATTRIBUTE_LIST ) + \ dataLength + oidLength ) ) == NULL ) return( CRYPT_ERROR_MEMORY ); initVarStruct( newElement, ATTRIBUTE_LIST, dataLength + oidLength ); newElement->oid = newElement->storage + dataLength; memcpy( newElement->oid, oid, oidLength ); newElement->flags = ( flags & ATTR_FLAG_IGNORED ) | \ ( criticalFlag ? ATTR_FLAG_CRITICAL : ATTR_FLAG_NONE ); memcpy( newElement->value, data, dataLength ); newElement->valueLength = dataLength; insertDoubleListElements( listHeadPtr, insertPoint, newElement, newElement ); return( CRYPT_OK ); } /* Add an attribute field to a list of attributes at the appropriate location */ int addAttributeField( ATTRIBUTE_LIST **attributeListPtr, const CRYPT_ATTRIBUTE_TYPE fieldID, const CRYPT_ATTRIBUTE_TYPE subFieldID, const void *data, const int dataLength, const int flags, CRYPT_ATTRIBUTE_TYPE *errorLocus, CRYPT_ERRTYPE_TYPE *errorType ) { const ATTRIBUTE_TYPE attributeType = \ ( fieldID >= CRYPT_CERTINFO_FIRST_CMS ) ? \ ATTRIBUTE_CMS : ATTRIBUTE_CERTIFICATE; CRYPT_ATTRIBUTE_TYPE attributeID; const ATTRIBUTE_INFO *attributeInfoPtr = fieldIDToAttribute( attributeType, fieldID, subFieldID, &attributeID ); ATTRIBUTE_LIST *newElement, *insertPoint, *prevElement = NULL; CHECKATTR_INFO_TYPE infoType; int storageSize, newDataLength, status; assert( isWritePtr( attributeListPtr, sizeof( ATTRIBUTE_LIST * ) ) ); assert( fieldID >= CRYPT_CERTINFO_FIRST_EXTENSION && \ fieldID <= CRYPT_CERTINFO_LAST ); assert( dataLength == CRYPT_UNUSED || \ ( dataLength > 0 && dataLength <= MAX_ATTRIBUTE_SIZE ) ); assert( dataLength == CRYPT_UNUSED || isReadPtr( data, dataLength ) ); assert( !( flags & ATTR_FLAG_INVALID ) ); assert( isReadPtr( attributeInfoPtr, sizeof( ATTRIBUTE_INFO ) ) ); /* Check the field's validity */ status = checkAttributeField( *attributeListPtr, attributeInfoPtr, fieldID, subFieldID, data, dataLength, flags, &infoType, &newDataLength, errorType ); if( cryptStatusError( status ) ) { if( errorType != NULL && *errorType != CRYPT_ERRTYPE_NONE ) /* If we encountered an error that sets the error type, record the locus */ *errorLocus = fieldID; return( status ); } assert( infoType == CHECKATTR_INFO_ZEROLENGTH || dataLength > 0 ); /* Find the location at which to insert this attribute field (this assumes that the fieldIDs are defined in sorted order) */ insertPoint = *attributeListPtr; while( insertPoint != NULL && \ insertPoint->fieldID != CRYPT_ATTRIBUTE_NONE && \ insertPoint->fieldID <= fieldID ) { assert( !isValidAttributeField( insertPoint->next ) || \ insertPoint->attributeID <= insertPoint->next->attributeID ); /* If it's a composite field that can have multiple fields with the same field ID (e.g. a GeneralName), exit if the overall field ID is greater (the component belongs to a different field entirely) or if the field ID is the same and the subfield ID is greater (if the component belongs to the same field) */ if( subFieldID != CRYPT_ATTRIBUTE_NONE && \ insertPoint->fieldID == fieldID && \ insertPoint->subFieldID > subFieldID ) break; prevElement = insertPoint; insertPoint = insertPoint->next; } insertPoint = prevElement; /* Allocate memory for the new element and copy the information across. If it's a simple type we can assign it to the simple value in the element itself, otherwise we copy it into the storage in the element. Something that encodes to NULL isn't really a numeric type, but we class it as such so that any attempt to read it returns CRYPT_UNUSED as the value */ switch( infoType ) { case CHECKATTR_INFO_NONE: /* No special-case length handling */ storageSize = dataLength; break; case CHECKATTR_INFO_ZEROLENGTH: /* Zero-length data, e.g. integer, boolean, DN placeholder */ storageSize = 0; break; case CHECKATTR_INFO_NEWLENGTH: /* The length has changed due to data en/decoding, use the en/decoded length for the storage size */ storageSize = newDataLength; break; default: retIntError(); } if( ( newElement = ( ATTRIBUTE_LIST * ) \ clAlloc( "addAttributeField", sizeof( ATTRIBUTE_LIST ) + \ storageSize ) ) == NULL ) return( CRYPT_ERROR_MEMORY ); initVarStruct( newElement, ATTRIBUTE_LIST, storageSize ); newElement->attributeID = attributeID; newElement->fieldID = fieldID; newElement->subFieldID = subFieldID; newElement->flags = flags; newElement->fieldType = attributeInfoPtr->fieldType; switch( attributeInfoPtr->fieldType ) { case BER_INTEGER: case BER_ENUMERATED: case BER_BITSTRING: case BER_BOOLEAN: case BER_NULL: case FIELDTYPE_CHOICE: newElement->intValue = *( ( int * ) data ); if( attributeInfoPtr->fieldType == BER_BOOLEAN ) /* Force it to the correct type if it's a boolean */ newElement->intValue = ( newElement->intValue ) ? TRUE : FALSE; if( attributeInfoPtr->fieldType == FIELDTYPE_CHOICE ) /* For encoding purposes the subfield ID is set to the ID of the CHOICE selection */ newElement->subFieldID = newElement->intValue; break; case BER_OBJECT_IDENTIFIER: /* If it's a BER/DER-encoded OID copy it in as is, otherwise convert it from the text form. In the latter case the amount of storage allocated is the space required by the text form which is more than the BER/DER-encoded form, but we can't tell in advance how much we actually need to allocate until we've performed the decoding */ if( ( ( BYTE * ) data )[ 0 ] == BER_OBJECT_IDENTIFIER ) { memcpy( newElement->value, data, dataLength ); newElement->valueLength = dataLength; } else { newElement->valueLength = textToOID( data, dataLength, newElement->value, storageSize ); assert( !cryptStatusError( newElement->valueLength ) ); } break; case FIELDTYPE_DN: /* When creating a new cert, this is a placeholder to indicate that a DN structure is being instantiated. When reading an encoded cert, this is the decoded DN structure */ newElement->value = ( *( ( int * ) data ) == CRYPT_UNUSED ) ? \ NULL : ( void * ) data; break; case FIELDTYPE_IDENTIFIER: /* This is a placeholder entry with no explicit value */ newElement->intValue = CRYPT_UNUSED; break; default: assert( dataLength > 0 ); memcpy( newElement->value, data, dataLength ); newElement->valueLength = dataLength; break; } insertDoubleListElement( attributeListPtr, insertPoint, newElement ); return( CRYPT_OK ); } /**************************************************************************** * * * Delete Attribute Data * * * ****************************************************************************/ /* Delete an attribute/attribute field from a list of attributes, updating the list cursor at the same time. This is a somewhat ugly kludge, it's not really possible to do this cleanly since deleting attributes affects the attribute cursor */ int deleteAttributeField( ATTRIBUTE_LIST **attributeListPtr, ATTRIBUTE_LIST **listCursorPtr, ATTRIBUTE_LIST *listItem, const void *dnCursor ) { ATTRIBUTE_LIST *listPrevPtr = listItem->prev; ATTRIBUTE_LIST *listNextPtr = listItem->next; BOOLEAN deletedDN = FALSE; assert( isWritePtr( attributeListPtr, sizeof( ATTRIBUTE_LIST * ) ) ); assert( isWritePtr( *attributeListPtr, sizeof( ATTRIBUTE_LIST ) ) ); assert( listCursorPtr == NULL || \ isWritePtr( listCursorPtr, sizeof( ATTRIBUTE_LIST * ) ) ); assert( isWritePtr( listItem, sizeof( ATTRIBUTE_LIST ) ) ); /* If we're about to delete the field that's pointed to by the attribute cursor, advance the cursor to the next field. If there's no next field, move it to the previous field. This behaviour is the most logically consistent, it means that we can do things like deleting an entire attribute list by repeatedly deleting a field */ if( listCursorPtr != NULL && *listCursorPtr == listItem ) *listCursorPtr = ( listNextPtr != NULL ) ? listNextPtr : listPrevPtr; /* Remove the item from the list */ deleteDoubleListElement( attributeListPtr, listItem ); /* Clear all data in the item and free the memory */ if( listItem->fieldType == FIELDTYPE_DN ) { /* If we've deleted the DN at the current cursor position, remember this so that we can warn the caller */ if( dnCursor != NULL && dnCursor == &listItem->value ) deletedDN = TRUE; deleteDN( ( void ** ) &listItem->value ); } endVarStruct( listItem, ATTRIBUTE_LIST ); clFree( "deleteAttributeField", listItem ); /* If we deleted the DN at the current cursor position, return a special-case code to let the caller know */ return( deletedDN ? OK_SPECIAL : CRYPT_OK ); } int deleteAttribute( ATTRIBUTE_LIST **attributeListPtr, ATTRIBUTE_LIST **listCursorPtr, ATTRIBUTE_LIST *listItem, const void *dnCursor ) { CRYPT_ATTRIBUTE_TYPE attributeID; ATTRIBUTE_LIST *attributeListCursor; int status = CRYPT_OK; assert( isWritePtr( attributeListPtr, sizeof( ATTRIBUTE_LIST * ) ) ); assert( isWritePtr( *attributeListPtr, sizeof( ATTRIBUTE_LIST ) ) ); assert( isWritePtr( listItem, sizeof( ATTRIBUTE_LIST ) ) ); /* If it's a blob-type attribute, everything is contained in this one list item so we only need to destroy that */ if( isBlobAttribute( listItem ) ) return( deleteAttributeField( attributeListPtr, listCursorPtr, listItem, NULL ) ); /* If it's a field that denotes an entire (constructed) attribute, it won't have an entry in the list, so we find the first field of the constructed attribute that's present in the list and start deleting from that point */ if( isCompleteAttribute( listItem ) ) { for( attributeListCursor = *attributeListPtr; attributeListCursor != NULL && \ attributeListCursor->attributeID != listItem->intValue; attributeListCursor = attributeListCursor->next ); } else /* The list item is a field in the attribute, find the start of the fields in this attribute */ attributeListCursor = findAttributeStart( listItem ); assert( isWritePtr( attributeListCursor, sizeof( ATTRIBUTE_LIST ) ) ); attributeID = attributeListCursor->attributeID; /* It's an item with multiple fields, destroy each field separately */ while( attributeListCursor != NULL && \ attributeListCursor->attributeID == attributeID ) { ATTRIBUTE_LIST *itemToFree = attributeListCursor; int localStatus; attributeListCursor = attributeListCursor->next; localStatus = deleteAttributeField( attributeListPtr, listCursorPtr, itemToFree, dnCursor ); if( cryptStatusError( localStatus ) && status != OK_SPECIAL ) /* Remember the error code, giving priority to DN cursor- modification notifications */ status = localStatus; } return( status ); } /* Delete a complete set of attributes */ void deleteAttributes( ATTRIBUTE_LIST **attributeListPtr ) { ATTRIBUTE_LIST *attributeListCursor = *attributeListPtr; assert( isWritePtr( attributeListPtr, sizeof( ATTRIBUTE_LIST * ) ) ); /* If the list was empty, return now */ if( attributeListCursor == NULL ) return; /* Destroy any remaining list items */ while( attributeListCursor != NULL ) { ATTRIBUTE_LIST *itemToFree = attributeListCursor; attributeListCursor = attributeListCursor->next; deleteAttributeField( attributeListPtr, NULL, itemToFree, NULL ); } assert( *attributeListPtr == NULL ); }