Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -8,19 +8,25 @@ STATIC_LIB = ${OBJFW_STATIC_LIB} FRAMEWORK = ${OBJFW_FRAMEWORK} LIB_MAJOR = ${OBJFW_LIB_MAJOR} LIB_MINOR = ${OBJFW_LIB_MINOR} -SRCS = OFApplication.m \ +SRCS = OFASN1Boolean.m \ + OFASN1Integer.m \ + OFASN1Null.m \ + OFASN1UTF8String.m \ + OFASN1Value.m \ + OFApplication.m \ OFArray.m \ OFAutoreleasePool.m \ OFBlock.m \ OFCharacterSet.m \ OFColor.m \ OFConstantString.m \ OFCountedSet.m \ OFData.m \ + OFData+ASN1DERValue.m \ OFData+CryptoHashing.m \ OFData+MessagePackValue.m \ OFDate.m \ OFDictionary.m \ OFEnumerator.m \ ADDED src/OFASN1Boolean.h Index: src/OFASN1Boolean.h ================================================================== --- src/OFASN1Boolean.h +++ src/OFASN1Boolean.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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. + */ + +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! + * @brief An ASN.1 boolean. + */ +@interface OFASN1Boolean: OFASN1Value +{ + bool _booleanValue; +} + +/*! + * @brief The boolean value. + */ +@property (readonly, nonatomic) bool booleanValue; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Boolean.m Index: src/OFASN1Boolean.m ================================================================== --- src/OFASN1Boolean.m +++ src/OFASN1Boolean.m @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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 "OFASN1Boolean.h" +#import "OFData.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" + +@implementation OFASN1Boolean +@synthesize booleanValue = _booleanValue; + +- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + self = [super initWithTagClass: tagClass + tagNumber: tagNumber + constructed: constructed + DEREncodedContents: DEREncodedContents]; + + @try { + unsigned char value; + + if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL || + _tagNumber != OF_ASN1_TAG_NUMBER_BOOLEAN || _constructed) + @throw [OFInvalidArgumentException exception]; + + if ([_DEREncodedContents count] != 1) + @throw [OFInvalidFormatException exception]; + + value = *(unsigned char *)[_DEREncodedContents itemAtIndex: 0]; + + if (value != 0 && value != 0xFF) + @throw [OFInvalidFormatException exception]; + + _booleanValue = value; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} +@end ADDED src/OFASN1Integer.h Index: src/OFASN1Integer.h ================================================================== --- src/OFASN1Integer.h +++ src/OFASN1Integer.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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. + */ + +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! + * @brief An ASN.1 integer. + */ +@interface OFASN1Integer: OFASN1Value +{ + intmax_t _integerValue; +} + +/*! + * @brief The integer value. + */ +@property (readonly, nonatomic) intmax_t integerValue; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Integer.m Index: src/OFASN1Integer.m ================================================================== --- src/OFASN1Integer.m +++ src/OFASN1Integer.m @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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 "OFASN1Integer.h" +#import "OFData.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" + +@implementation OFASN1Integer +@synthesize integerValue = _integerValue; + +- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + self = [super initWithTagClass: tagClass + tagNumber: tagNumber + constructed: constructed + DEREncodedContents: DEREncodedContents]; + + @try { + const unsigned char *items; + size_t count; + uintmax_t value; + + if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL || + _tagNumber != OF_ASN1_TAG_NUMBER_INTEGER || _constructed) + @throw [OFInvalidArgumentException exception]; + + /* TODO: Support for big numbers */ + items = [_DEREncodedContents items]; + count = [_DEREncodedContents count]; + value = 0; + + if (count > sizeof(uintmax_t) && + (count != sizeof(uintmax_t) + 1 || items[0] != 0)) + @throw [OFOutOfRangeException exception]; + + if (count >= 2 && ((items[0] == 0 && !(items[1] & 0x80)) || + (items[0] == 0xFF && items[1] & 0x80))) + @throw [OFInvalidFormatException exception]; + + if (count >= 1 && items[0] & 0x80) + value = ~(uintmax_t)0; + + while (count--) + value = (value << 8) | *items++; + + _integerValue = value; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} +@end ADDED src/OFASN1Null.h Index: src/OFASN1Null.h ================================================================== --- src/OFASN1Null.h +++ src/OFASN1Null.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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. + */ + +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! + * @brief An ASN.1 null value. + */ +@interface OFASN1Null: OFASN1Value +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Null.m Index: src/OFASN1Null.m ================================================================== --- src/OFASN1Null.m +++ src/OFASN1Null.m @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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 "OFASN1Null.h" +#import "OFData.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" + +@implementation OFASN1Null +- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + self = [super initWithTagClass: tagClass + tagNumber: tagNumber + constructed: constructed + DEREncodedContents: DEREncodedContents]; + + @try { + if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL || + _tagNumber != OF_ASN1_TAG_NUMBER_NULL || _constructed) + @throw [OFInvalidArgumentException exception]; + + if ([_DEREncodedContents count] != 0) + @throw [OFInvalidFormatException exception]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} +@end ADDED src/OFASN1UTF8String.h Index: src/OFASN1UTF8String.h ================================================================== --- src/OFASN1UTF8String.h +++ src/OFASN1UTF8String.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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. + */ + +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/*! + * @brief An ASN.1 UTF-8 string. + */ +@interface OFASN1UTF8String: OFASN1Value +{ + OFString *_stringValue; +} + +/*! + * @brief The string value. + */ +@property (readonly, nonatomic) OFString *stringValue; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1UTF8String.m Index: src/OFASN1UTF8String.m ================================================================== --- src/OFASN1UTF8String.m +++ src/OFASN1UTF8String.m @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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 "OFASN1UTF8String.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" + +@implementation OFASN1UTF8String +@synthesize stringValue = _stringValue; + +- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + self = [super initWithTagClass: tagClass + tagNumber: tagNumber + constructed: constructed + DEREncodedContents: DEREncodedContents]; + + @try { + if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL || + _tagNumber != OF_ASN1_TAG_NUMBER_UTF8_STRING || + _constructed) + @throw [OFInvalidArgumentException exception]; + + _stringValue = [[OFString alloc] + initWithUTF8String: [_DEREncodedContents items] + length: [_DEREncodedContents count]]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_stringValue release]; + + [super dealloc]; +} +@end ADDED src/OFASN1Value.h Index: src/OFASN1Value.h ================================================================== --- src/OFASN1Value.h +++ src/OFASN1Value.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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. + */ + +#import "OFObject.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFData; + +/*! + * @brief ASN.1 tag class. + */ +typedef enum { + /*! Universal */ + OF_ASN1_TAG_CLASS_UNIVERSAL = 0x0, + /*! Application */ + OF_ASN1_TAG_CLASS_APPLICATION = 0x1, + /*! Context specific */ + OF_ASN1_TAG_CLASS_CONTEXT_SPECIFIC = 0x2, + /*! Private */ + OF_ASN1_TAG_CLASS_PRIVATE = 0x3 +} of_asn1_tag_class_t; + +/*! + * @brief ASN.1 tag number. + */ +typedef enum { + /*! Boolean */ + OF_ASN1_TAG_NUMBER_BOOLEAN = 0x01, + /*! Integer */ + OF_ASN1_TAG_NUMBER_INTEGER = 0x02, + /*! Null */ + OF_ASN1_TAG_NUMBER_NULL = 0x05, + /*! UTF-8 string */ + OF_ASN1_TAG_NUMBER_UTF8_STRING = 0x0C, + /*! Sequence */ + OF_ASN1_TAG_NUMBER_SEQUENCE = 0x10 +} of_asn1_tag_number_t; + +/*! + * @brief A class representing an ASN.1 value. + */ +@interface OFASN1Value: OFObject +{ + of_asn1_tag_class_t _tagClass; + of_asn1_tag_number_t _tagNumber; + bool _constructed; + OFData *_DEREncodedContents; +} + +/*! + * @brief The tag class of the value's type. + */ +@property (readonly, nonatomic) of_asn1_tag_class_t tagClass; + +/*! + * @brief The tag number of the value's type. + */ +@property (readonly, nonatomic) of_asn1_tag_number_t tagNumber; + +/*! + * @brief Whether the value if of a constructed type. + */ +@property (readonly, nonatomic, getter=isConstructed) bool constructed; + +/*! + * @brief The DER-encoded contents octets of the value. + */ +@property (readonly, nonatomic) OFData *DEREncodedContents; + +/*! + * @brief Creates a new ASN.1 value with the specified arguments. + * + * @param tagClass The tag class of the value's type + * @param tagNumber The tag number of the value's type + * @param constructed Whether the value if of a constructed type + * @param DEREncodedContents The DER-encoded contents octets of the value. + * @return A new ASN.1 value + */ ++ (instancetype)valueWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; + +- (instancetype)init OF_UNAVAILABLE; + +/*! + * @brief Initializes an already allocated ASN.1 value with the specified + * arguments. + * + * @param tagClass The tag class of the value's type + * @param tagNumber The tag number of the value's type + * @param constructed Whether the value if of a constructed type + * @param DEREncodedContents The DER-encoded contents octets of the value. + * @return An initialized ASN.1 value + */ +- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents + OF_DESIGNATED_INITIALIZER; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Value.m Index: src/OFASN1Value.m ================================================================== --- src/OFASN1Value.m +++ src/OFASN1Value.m @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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 "OFASN1Value.h" +#import "OFData.h" + +#import "OFInvalidFormatException.h" + +@implementation OFASN1Value +@synthesize tagClass = _tagClass, tagNumber = _tagNumber; +@synthesize constructed = _constructed; +@synthesize DEREncodedContents = _DEREncodedContents; + ++ (instancetype)valueWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + return [[[self alloc] + initWithTagClass: tagClass + tagNumber: tagNumber + constructed: constructed + DEREncodedContents: DEREncodedContents] autorelease]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + self = [super init]; + + @try { + if ([DEREncodedContents itemSize] != 1) + @throw [OFInvalidFormatException exception]; + + _tagClass = tagClass; + _tagNumber = tagNumber; + _constructed = constructed; + _DEREncodedContents = [DEREncodedContents copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_DEREncodedContents release]; + + [super dealloc]; +} + +- (bool)isEqual: (id)object +{ + OFASN1Value *value; + + if (![object isKindOfClass: [OFASN1Value class]]) + return false; + + value = object; + + if (value->_tagClass != _tagClass) + return false; + if (value->_tagNumber != _tagNumber) + return false; + if (value->_constructed != _constructed) + return false; + if (![value->_DEREncodedContents isEqual: _DEREncodedContents]) + return false; + + return true; +} + +- (uint32_t)hash +{ + uint32_t hash; + + OF_HASH_INIT(hash); + + OF_HASH_ADD(hash, _tagClass & 0xFF); + OF_HASH_ADD(hash, _tagNumber & 0xFF); + OF_HASH_ADD(hash, _constructed); + OF_HASH_ADD_HASH(hash, [_DEREncodedContents hash]); + + OF_HASH_FINALIZE(hash); + + return hash; +} + +- (id)copy +{ + return [self retain]; +} +@end ADDED src/OFData+ASN1DERValue.h Index: src/OFData+ASN1DERValue.h ================================================================== --- src/OFData+ASN1DERValue.h +++ src/OFData+ASN1DERValue.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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. + */ + +#import "OFData.h" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif +extern int _OFData_ASN1DERValue_reference; +#ifdef __cplusplus +} +#endif + +@interface OFData (ASN1DERValue) +/*! + * @brief The data interpreted as ASN.1 in DER representation and parsed as an + * object. + * + * This is either an OFArray (for a sequence), an OFSet (for a set) or an + * OFASN1Value. + */ +@property (readonly, nonatomic) id ASN1DERValue; + +/*! + * @brief Parses the ASN.1 DER representation and returns it as an object. + * + * This is either an OFArray (for a sequence), an OFSet (for a set) or an + * OFASN1Value. + * + * @param depthLimit The maximum depth the parser should accept (defaults to 32 + * if not specified, 0 means no limit (insecure!)) + * @return The ASN.1 DER representation as an object + */ +- (id)ASN1DERValueWithDepthLimit: (size_t)depthLimit; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFData+ASN1DERValue.m Index: src/OFData+ASN1DERValue.m ================================================================== --- src/OFData+ASN1DERValue.m +++ src/OFData+ASN1DERValue.m @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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+ASN1DERValue.h" +#import "OFASN1Boolean.h" +#import "OFASN1Integer.h" +#import "OFASN1Null.h" +#import "OFASN1UTF8String.h" +#import "OFASN1Value.h" +#import "OFArray.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" +#import "OFTruncatedDataException.h" + +enum { + ASN1_TAG_CONSTRUCTED_MASK = 0x20 +}; + +int _OFData_ASN1DERValue_reference; + +static size_t parseObject(OFData *self, id *object, size_t depthLimit); + +static OFArray * +parseSequence(OFData *contents, size_t depthLimit) +{ + size_t count = [contents count]; + OFMutableArray *ret; + + if (depthLimit == 0) + @throw [OFOutOfRangeException exception]; + + ret = [OFMutableArray array]; + + while (count > 0) { + id object; + size_t objectLength; + + objectLength = parseObject(contents, &object, depthLimit); + + count -= objectLength; + contents = [contents subdataWithRange: + of_range(objectLength, count)]; + + [ret addObject: object]; + } + + [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: + of_range(bytesConsumed, contentsLength)]; + bytesConsumed += contentsLength; + + switch (tag) { + case OF_ASN1_TAG_NUMBER_BOOLEAN: + valueClass = [OFASN1Boolean class]; + break; + case OF_ASN1_TAG_NUMBER_INTEGER: + valueClass = [OFASN1Integer class]; + break; + case OF_ASN1_TAG_NUMBER_NULL: + valueClass = [OFASN1Null class]; + break; + case OF_ASN1_TAG_NUMBER_UTF8_STRING: + valueClass = [OFASN1UTF8String class]; + break; + case OF_ASN1_TAG_NUMBER_SEQUENCE | ASN1_TAG_CONSTRUCTED_MASK: + *object = parseSequence(contents, depthLimit - 1); + return bytesConsumed; + default: + valueClass = [OFASN1Value class]; + break; + } + + *object = [valueClass valueWithTagClass: tag >> 6 + tagNumber: tag & 0x1F + constructed: tag & ASN1_TAG_CONSTRUCTED_MASK + DEREncodedContents: contents]; + return bytesConsumed; +} + +@implementation OFData (ASN1DERValue) +- (id)ASN1DERValue +{ + return [self ASN1DERValueWithDepthLimit: 32]; +} + +- (id)ASN1DERValueWithDepthLimit: (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 Index: src/OFData+MessagePackValue.m ================================================================== --- src/OFData+MessagePackValue.m +++ src/OFData+MessagePackValue.m @@ -569,8 +569,8 @@ [object retain]; objc_autoreleasePoolPop(pool); - return object; + return [object autorelease]; } @end Index: src/OFData.h ================================================================== --- src/OFData.h +++ src/OFData.h @@ -323,7 +323,8 @@ @end OF_ASSUME_NONNULL_END #import "OFMutableData.h" +#import "OFData+ASN1DERValue.h" #import "OFData+CryptoHashing.h" #import "OFData+MessagePackValue.h" Index: src/OFData.m ================================================================== --- src/OFData.m +++ src/OFData.m @@ -46,10 +46,11 @@ /* References for static linking */ void _references_to_categories_of_OFData(void) { + _OFData_ASN1DERValue_reference = 1; _OFData_CryptoHashing_reference = 1; _OFData_MessagePackValue_reference = 1; } @implementation OFData Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -12,10 +12,11 @@ SRCS = ForwardingTests.m \ OFArrayTests.m \ ${OFBLOCKTESTS_M} \ OFCharacterSetTests.m \ OFDataTests.m \ + OFDataASN1DERValueTests.m \ OFDateTests.m \ OFDictionaryTests.m \ OFInvocationTests.m \ OFJSONTests.m \ OFListTests.m \ ADDED tests/OFDataASN1DERValueTests.m Index: tests/OFDataASN1DERValueTests.m ================================================================== --- tests/OFDataASN1DERValueTests.m +++ tests/OFDataASN1DERValueTests.m @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * 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.h" +#import "OFASN1Boolean.h" +#import "OFASN1Integer.h" +#import "OFASN1Null.h" +#import "OFASN1UTF8String.h" +#import "OFArray.h" +#import "OFString.h" +#import "OFAutoreleasePool.h" + +#import "TestsAppDelegate.h" + +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" +#import "OFTruncatedDataException.h" + +static OFString *module = @"OFData+ASN1DERValue"; + +@implementation TestsAppDelegate (OFDataASN1DERValueTests) +- (void)dataASN1DERValueTests +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFArray *array; + + TEST(@"Parsing of boolean", + ![[[OFData dataWithItems: "\x01\x01\x00" + count: 3] ASN1DERValue] booleanValue] && + [[[OFData dataWithItems: "\x01\x01\xFF" + count: 3] ASN1DERValue] booleanValue]) + + EXPECT_EXCEPTION(@"Detection of invalid boolean #1", + OFInvalidFormatException, [[OFData dataWithItems: "\x01\x01\x01" + count: 3] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of invalid boolean #2", + OFInvalidFormatException, [[OFData dataWithItems: "\x01\x02\x00\x00" + count: 4] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of invalid boolean #3", + OFInvalidFormatException, [[OFData dataWithItems: "\x01\x00" + count: 2] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of truncated boolean", + OFTruncatedDataException, [[OFData dataWithItems: "\x01\x01" + count: 2] ASN1DERValue]) + + TEST(@"Parsing of integer", + [[[OFData dataWithItems: "\x02\x00" + count: 2] ASN1DERValue] integerValue] == 0 && + [[[OFData dataWithItems: "\x02\x01\x01" + count: 3] ASN1DERValue] integerValue] == 1 && + [[[OFData dataWithItems: "\x02\x02\x01\x04" + count: 4] ASN1DERValue] integerValue] == 260 && + [[[OFData dataWithItems: "\x02\x01\xFF" + count: 3] ASN1DERValue] integerValue] == -1 && + [[[OFData dataWithItems: "\x02\x03\xFF\x00\x00" + count: 5] ASN1DERValue] integerValue] == -65536 && + (uintmax_t)[[[OFData dataWithItems: "\x02\x09\x00\xFF\xFF\xFF\xFF" + "\xFF\xFF\xFF\xFF" + count: 11] ASN1DERValue] + integerValue] == UINTMAX_MAX) + + EXPECT_EXCEPTION(@"Detecting of invalid integer #1", + OFInvalidFormatException, [[OFData dataWithItems: "\x02\x02\x00\x00" + count: 4] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detecting of invalid integer #2", + OFInvalidFormatException, [[OFData dataWithItems: "\x02\x02\x00\x7F" + count: 4] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detecting of invalid integer #3", + OFInvalidFormatException, [[OFData dataWithItems: "\x02\x02\xFF\x80" + count: 4] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of out of range integer", + OFOutOfRangeException, + [[OFData dataWithItems: "\x02\x09\x01" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + count: 11] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of truncated integer", + OFTruncatedDataException, [[OFData dataWithItems: "\x02\x02\x00" + count: 3] ASN1DERValue]) + + TEST(@"Parsing of NULL", + [[[OFData dataWithItems: "\x05\x00" + count: 2] ASN1DERValue] + isKindOfClass: [OFASN1Null class]]) + + EXPECT_EXCEPTION(@"Detection of invalid NULL", + OFInvalidFormatException, [[OFData dataWithItems: "\x05\x01\x00" + count: 3] ASN1DERValue]) + + TEST(@"Parsing of UTF-8 string", + [[[[OFData dataWithItems: "\x0C\x0EHällo Wörld!" + count: 16] ASN1DERValue] stringValue] + isEqual: @"Hällo Wörld!"] && + [[[[OFData dataWithItems: "\x0C\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxx" + count: 131] ASN1DERValue] stringValue] + isEqual: @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + @"xxxxxxxxxxxxxxxx"]) + + EXPECT_EXCEPTION(@"Detection of out of range UTF-8 string", + OFOutOfRangeException, + [[OFData dataWithItems: "\x0C\x89" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01" + count: 11] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of truncated UTF-8 string", + OFTruncatedDataException, [[OFData dataWithItems: "\x0C\x01" + count: 2] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of truncated length", + OFTruncatedDataException, [[OFData dataWithItems: "\x0C\x83\x01\x01" + count: 4] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of invalid / inefficient length #1", + OFInvalidFormatException, [[OFData dataWithItems: "\x0C\x81\x7F" + count: 3] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of invalid / inefficient length #2", + OFInvalidFormatException, + [[OFData dataWithItems: "\x0C\x82\x00\x80xxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxx" + count: 132] ASN1DERValue]) + + TEST(@"Parsing of sequence", + (array = [[OFData dataWithItems: "\x30\x00" + count: 2] ASN1DERValue]) && + [array isKindOfClass: [OFArray class]] && [array count] == 0 && + (array = [[OFData dataWithItems: "\x30\x09\x02\x01\x7B\x0C\x04Test" + count: 11] ASN1DERValue]) && + [array isKindOfClass: [OFArray class]] && [array count] == 2 && + [[array objectAtIndex: 0] integerValue] == 123 && + [[[array objectAtIndex: 1] stringValue] isEqual: @"Test"]) + + EXPECT_EXCEPTION(@"Parsing of truncated sequence #1", + OFTruncatedDataException, [[OFData dataWithItems: "\x30\x01" + count: 2] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Parsing of truncated sequence #2", + OFTruncatedDataException, + [[OFData dataWithItems: "\x30\x04\x02\x01\x01\x00\x00" + count: 7] ASN1DERValue]) + + [pool drain]; +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -104,10 +104,14 @@ @end @interface TestsAppDelegate (OFDataTests) - (void)dataTests; @end + +@interface TestsAppDelegate (OFDataASN1DERValueTests) +- (void)dataASN1DERValueTests; +@end @interface TestsAppDelegate (OFDateTests) - (void)dateTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -388,10 +388,11 @@ [self blockTests]; #endif [self stringTests]; [self characterSetTests]; [self dataTests]; + [self dataASN1DERValueTests]; [self arrayTests]; [self dictionaryTests]; [self listTests]; [self setTests]; [self dateTests];