/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#import "OFData+ASN1DERParsing.h"
#import "OFASN1BitString.h"
#import "OFASN1Enumerated.h"
#import "OFASN1IA5String.h"
#import "OFASN1NumericString.h"
#import "OFASN1ObjectIdentifier.h"
#import "OFASN1OctetString.h"
#import "OFASN1PrintableString.h"
#import "OFASN1Value.h"
#import "OFArray.h"
#import "OFNull.h"
#import "OFNumber.h"
#import "OFSet.h"
#import "OFString.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFOutOfRangeException.h"
#import "OFTruncatedDataException.h"
enum {
tagConstructedMask = 0x20
};
int _OFData_ASN1DERParsing_reference;
static size_t parseObject(OFData *self, id *object, size_t depthLimit);
long long
OFASN1DERIntegerParse(const unsigned char *buffer, size_t length)
{
unsigned long long value = 0;
/* TODO: Support for big numbers */
if (length > sizeof(unsigned long long) &&
(length != sizeof(unsigned long long) + 1 || buffer[0] != 0))
@throw [OFOutOfRangeException exception];
if (length >= 2 && ((buffer[0] == 0 && !(buffer[1] & 0x80)) ||
(buffer[0] == 0xFF && buffer[1] & 0x80)))
@throw [OFInvalidFormatException exception];
if (length >= 1 && buffer[0] & 0x80)
value = ~0ull;
while (length--)
value = (value << 8) | *buffer++;
return value;
}
static OFArray *
parseSequence(OFData *contents, size_t depthLimit)
{
OFMutableArray *ret = [OFMutableArray array];
size_t count = contents.count;
if (depthLimit == 0)
@throw [OFOutOfRangeException exception];
while (count > 0) {
id object;
size_t objectLength;
objectLength = parseObject(contents, &object, depthLimit);
count -= objectLength;
contents = [contents subdataWithRange:
OFMakeRange(objectLength, count)];
[ret addObject: object];
}
[ret makeImmutable];
return ret;
}
static OFSet *
parseSet(OFData *contents, size_t depthLimit)
{
OFMutableSet *ret = [OFMutableSet set];
size_t count = contents.count;
OFData *previousObjectData = nil;
if (depthLimit == 0)
@throw [OFOutOfRangeException exception];
while (count > 0) {
id object;
size_t objectLength;
OFData *objectData;
objectLength = parseObject(contents, &object, depthLimit);
objectData = [contents subdataWithRange:
OFMakeRange(0, objectLength)];
if (previousObjectData != nil &&
[objectData compare: previousObjectData] !=
OFOrderedDescending)
@throw [OFInvalidFormatException exception];
count -= objectLength;
contents = [contents subdataWithRange:
OFMakeRange(objectLength, count)];
[ret addObject: object];
previousObjectData = objectData;
}
[ret makeImmutable];
return ret;
}
static size_t
parseObject(OFData *self, id *object, size_t depthLimit)
{
const unsigned char *items = self.items;
size_t count = self.count;
unsigned char tag;
size_t contentsLength, bytesConsumed = 0;
Class valueClass;
OFData *contents;
if (count < 2)
@throw [OFTruncatedDataException exception];
tag = *items++;
contentsLength = *items++;
bytesConsumed += 2;
if (contentsLength > 127) {
uint_fast8_t lengthLength = contentsLength & 0x7F;
if (lengthLength > sizeof(size_t))
@throw [OFOutOfRangeException exception];
if (count - bytesConsumed < lengthLength)
@throw [OFTruncatedDataException exception];
if (lengthLength == 0 ||
(lengthLength == 1 && items[0] < 0x80) ||
(lengthLength >= 2 && items[0] == 0))
@throw [OFInvalidFormatException exception];
contentsLength = 0;
for (uint_fast8_t i = 0; i < lengthLength; i++)
contentsLength = (contentsLength << 8) | *items++;
bytesConsumed += lengthLength;
if (contentsLength <= 127)
@throw [OFInvalidFormatException exception];
}
if (count - bytesConsumed < contentsLength)
@throw [OFTruncatedDataException exception];
contents = [self subdataWithRange:
OFMakeRange(bytesConsumed, contentsLength)];
bytesConsumed += contentsLength;
switch (tag & ~tagConstructedMask) {
case OFASN1TagNumberBoolean:;
unsigned char boolValue;
if (tag & tagConstructedMask)
@throw [OFInvalidFormatException exception];
if (contents.count != 1)
@throw [OFInvalidFormatException exception];
boolValue = *(unsigned char *)[contents itemAtIndex: 0];
if (boolValue != 0 && boolValue != 0xFF)
@throw [OFInvalidFormatException exception];
*object = [OFNumber numberWithBool: boolValue];
return bytesConsumed;
case OFASN1TagNumberInteger:
if (tag & tagConstructedMask)
@throw [OFInvalidFormatException exception];
*object = [OFNumber numberWithLongLong:
OFASN1DERIntegerParse(contents.items, contents.count)];
return bytesConsumed;
case OFASN1TagNumberBitString:
valueClass = [OFASN1BitString class];
break;
case OFASN1TagNumberOctetString:
valueClass = [OFASN1OctetString class];
break;
case OFASN1TagNumberNull:
if (tag & tagConstructedMask)
@throw [OFInvalidFormatException exception];
if (contents.count != 0)
@throw [OFInvalidFormatException exception];
*object = [OFNull null];
return bytesConsumed;
case OFASN1TagNumberObjectIdentifier:
valueClass = [OFASN1ObjectIdentifier class];
break;
case OFASN1TagNumberEnumerated:
valueClass = [OFASN1Enumerated class];
break;
case OFASN1TagNumberUTF8String:
if (tag & tagConstructedMask)
@throw [OFInvalidFormatException exception];
*object = [OFString stringWithUTF8String: contents.items
length: contents.count];
return bytesConsumed;
case OFASN1TagNumberSequence:
if (!(tag & tagConstructedMask))
@throw [OFInvalidFormatException exception];
*object = parseSequence(contents, depthLimit - 1);
return bytesConsumed;
case OFASN1TagNumberSet:
if (!(tag & tagConstructedMask))
@throw [OFInvalidFormatException exception];
*object = parseSet(contents, depthLimit - 1);
return bytesConsumed;
case OFASN1TagNumberNumericString:
valueClass = [OFASN1NumericString class];
break;
case OFASN1TagNumberPrintableString:
valueClass = [OFASN1PrintableString class];
break;
case OFASN1TagNumberIA5String:
valueClass = [OFASN1IA5String class];
break;
default:
valueClass = [OFASN1Value class];
break;
}
*object = [[[valueClass alloc]
initWithTagClass: tag >> 6
tagNumber: tag & 0x1F
constructed: tag & tagConstructedMask
DEREncodedContents: contents] autorelease];
return bytesConsumed;
}
@implementation OFData (ASN1DERParsing)
- (id)objectByParsingASN1DER
{
return [self objectByParsingASN1DERWithDepthLimit: 32];
}
- (id)objectByParsingASN1DERWithDepthLimit: (size_t)depthLimit
{
void *pool = objc_autoreleasePoolPush();
id object;
if (self.itemSize != 1)
@throw [OFInvalidArgumentException exception];
if (parseObject(self, &object, depthLimit) != self.count)
@throw [OFInvalidFormatException exception];
[object retain];
objc_autoreleasePoolPop(pool);
return [object autorelease];
}
@end