Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -8,18 +8,30 @@ STATIC_LIB = ${OBJFW_STATIC_LIB} FRAMEWORK = ${OBJFW_FRAMEWORK} LIB_MAJOR = ${OBJFW_LIB_MAJOR} LIB_MINOR = ${OBJFW_LIB_MINOR} -SRCS = OFApplication.m \ +SRCS = OFASN1BitString.m \ + OFASN1Boolean.m \ + OFASN1Enumerated.m \ + OFASN1IA5String.m \ + OFASN1Integer.m \ + OFASN1NumericString.m \ + OFASN1ObjectIdentifier.m \ + OFASN1OctetString.m \ + OFASN1PrintableString.m \ + OFASN1UTF8String.m \ + OFASN1Value.m \ + OFApplication.m \ OFArray.m \ OFBlock.m \ OFCharacterSet.m \ OFColor.m \ OFConstantString.m \ OFCountedSet.m \ OFData.m \ + OFData+ASN1DERParsing.m \ OFData+CryptographicHashing.m \ OFData+MessagePackParsing.m \ OFDate.m \ OFDictionary.m \ OFEnumerator.m \ @@ -159,10 +171,11 @@ platform/GCC4.7/OFAtomic.h \ platform/PowerPC/OFAtomic.h \ platform/macOS/OFAtomic.h \ platform/x86/OFAtomic.h INCLUDES := ${SRCS:.m=.h} \ + OFASN1DERRepresentation.h \ OFArchiveEntry.h \ OFCollection.h \ OFCryptographicHash.h \ OFJSONRepresentation.h \ OFKernelEventObserver.h \ ADDED src/OFASN1BitString.h Index: src/OFASN1BitString.h ================================================================== --- /dev/null +++ src/OFASN1BitString.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1DERRepresentation.h" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFData; + +/** + * @brief An ASN.1 BitString. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1BitString: OFObject +{ + OFData *_bitStringValue; + size_t _bitStringLength; +} + +/** + * @brief The BitString value. + */ +@property (readonly, nonatomic) OFData *bitStringValue; + +/** + * @brief The length of the BitString in bits. + */ +@property (readonly, nonatomic) size_t bitStringLength; + +/** + * @brief Creates an ASN.1 BitString with the specified BitString value and + * length. + * + * @param bitString The value of the BitString + * @param length The length of the BitString in bits + * @return A new, autoreleased OFASN1BitString + */ ++ (instancetype)bitStringWithBitString: (OFData *)bitString + length: (size_t)length; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated ASN.1 BitString with the specified + * BitString value and length. + * + * @param bitString The value of the BitString + * @param length The length of the BitString in bits + * @return An initialized OFASN1BitString + */ +- (instancetype)initWithBitString: (OFData *)bitString + length: (size_t)length OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 BitString 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 OFASN1BitString + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1BitString.m Index: src/OFASN1BitString.m ================================================================== --- /dev/null +++ src/OFASN1BitString.m @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2008-2021 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 "OFASN1BitString.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" + +@implementation OFASN1BitString +@synthesize bitStringValue = _bitStringValue; +@synthesize bitStringLength = _bitStringLength; + ++ (instancetype)bitStringWithBitString: (OFData *)bitString + length: (size_t)length +{ + return [[[self alloc] initWithBitString: bitString + length: length] autorelease]; +} + +- (instancetype)initWithBitString: (OFData *)bitString length: (size_t)length +{ + self = [super init]; + + @try { + if (bitString.count * bitString.itemSize != + OFRoundUpToPowerOf2(8, length) / 8) + @throw [OFInvalidFormatException exception]; + + _bitStringValue = [bitString copy]; + _bitStringLength = length; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + void *pool = objc_autoreleasePoolPush(); + OFData *bitString; + size_t length; + + @try { + unsigned char unusedBits; + size_t count = DEREncodedContents.count; + + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberBitString || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1 || count == 0) + @throw [OFInvalidFormatException exception]; + + unusedBits = + *(unsigned char *)[DEREncodedContents itemAtIndex: 0]; + + if (unusedBits > 7) + @throw [OFInvalidFormatException exception]; + + /* + * Can't have any bits of the last byte unused if we have no + * byte. + */ + if (count == 1 && unusedBits != 0) + @throw [OFInvalidFormatException exception]; + + if (SIZE_MAX / 8 < count - 1) + @throw [OFOutOfRangeException exception]; + + length = (count - 1) * 8; + bitString = [DEREncodedContents subdataWithRange: + OFMakeRange(1, count - 1)]; + + if (unusedBits != 0) + length -= unusedBits; + } @catch (id e) { + [self release]; + @throw e; + } + + self = [self initWithBitString: bitString length: length]; + + objc_autoreleasePoolPop(pool); + + return self; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_bitStringValue release]; + + [super dealloc]; +} + +- (OFData *)ASN1DERRepresentation +{ + size_t bitStringValueCount = _bitStringValue.count; + size_t roundedUpLength = OFRoundUpToPowerOf2(8, _bitStringLength); + unsigned char unusedBits = roundedUpLength - _bitStringLength; + unsigned char header[] = { + OFASN1TagNumberBitString, + bitStringValueCount + 1, + unusedBits + }; + OFMutableData *data; + + if (bitStringValueCount + 1 > UINT8_MAX || + bitStringValueCount != roundedUpLength / 8) + @throw [OFInvalidFormatException exception]; + + data = [OFMutableData + dataWithCapacity: sizeof(header) + bitStringValueCount]; + [data addItems: header count: sizeof(header)]; + [data addItems: _bitStringValue.items count: bitStringValueCount]; + + [data makeImmutable]; + + return data; +} + +- (bool)isEqual: (id)object +{ + OFASN1BitString *bitString; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1BitString class]]) + return false; + + bitString = object; + + if (![bitString->_bitStringValue isEqual: _bitStringValue]) + return false; + if (bitString->_bitStringLength != _bitStringLength) + return false; + + return true; +} + +- (unsigned long)hash +{ + return _bitStringValue.hash + (unsigned long)_bitStringLength; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _bitStringValue, _bitStringLength]; +} +@end ADDED src/OFASN1Boolean.h Index: src/OFASN1Boolean.h ================================================================== --- /dev/null +++ src/OFASN1Boolean.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1DERRepresentation.h" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +/** + * @brief An ASN.1 Boolean. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1Boolean: OFObject +{ + bool _boolValue; +} + +/** + * @brief The value of the Boolean. + */ +@property (readonly, nonatomic) bool boolValue; + +/** + * @brief Creates an ASN.1 Boolean with the specified Boolean value. + * + * @param bool_ The value of the Boolean + * @return A new, autoreleased OFASN1Boolean + */ ++ (instancetype)booleanWithBool: (bool)bool_; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated ASN.1 Boolean with the specified + * Boolean value. + * + * @param bool_ The value of the Boolean + * @return An initialized OFASN1Boolean + */ +- (instancetype)initWithBool: (bool)bool_ OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 Boolean 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 OFASN1Boolean + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Boolean.m Index: src/OFASN1Boolean.m ================================================================== --- /dev/null +++ src/OFASN1Boolean.m @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2008-2021 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 "OFString.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" + +@implementation OFASN1Boolean +@synthesize boolValue = _boolValue; + ++ (instancetype)booleanWithBool: (bool)bool_ +{ + return [[[self alloc] initWithBool: bool_] autorelease]; +} + +- (instancetype)initWithBool: (bool)bool_ +{ + self = [super init]; + + _boolValue = bool_; + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + unsigned char value; + + @try { + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberBoolean || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1 || + DEREncodedContents.count != 1) + @throw [OFInvalidFormatException exception]; + + value = *(unsigned char *)[DEREncodedContents itemAtIndex: 0]; + + if (value != 0 && value != 0xFF) + @throw [OFInvalidFormatException exception]; + } @catch (id e) { + [self release]; + @throw e; + } + + return [self initWithBool: !!value]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (OFData *)ASN1DERRepresentation +{ + char buffer[] = { + OFASN1TagNumberBoolean, + 1, + (_boolValue ? 0xFF : 0x00) + }; + + return [OFData dataWithItems: buffer count: sizeof(buffer)]; +} + +- (bool)isEqual: (id)object +{ + OFASN1Boolean *boolean; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1Boolean class]]) + return false; + + boolean = object; + + if (boolean->_boolValue != _boolValue) + return false; + + return true; +} + +- (unsigned long)hash +{ + return _boolValue; +} + +- (OFString *)description +{ + return (_boolValue + ? @"" + : @""); +} +@end ADDED src/OFASN1DERRepresentation.h Index: src/OFASN1DERRepresentation.h ================================================================== --- /dev/null +++ src/OFASN1DERRepresentation.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008-2021 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 + +@class OFData; + +/** + * @protocol OFASN1DERRepresentation \ + * OFASN1DERRepresentation.h ObjFW/OFASN1DERRepresentation.h + * + * @brief A protocol implemented by classes that support encoding to ASN.1 DER + * representation. + */ +@protocol OFASN1DERRepresentation +/** + * @brief The object in ASN.1 DER representation. + */ +@property (readonly, nonatomic) OFData *ASN1DERRepresentation; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Enumerated.h Index: src/OFASN1Enumerated.h ================================================================== --- /dev/null +++ src/OFASN1Enumerated.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +/** + * @brief An ASN.1 Enumerated. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1Enumerated: OFObject +{ + long long _longLongValue; +} + +/** + * @brief The integer value. + */ +@property (readonly, nonatomic) long long longLongValue; + +/** + * @brief Creates an ASN.1 Enumerated with the specified integer value. + * + * @param value The `long long` value of the Enumerated + * @return A new, autoreleased OFASN1Enumerated + */ ++ (instancetype)enumeratedWithLongLong: (long long)value; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated ASN.1 Enumerated with the specified + * integer value. + * + * @param value The `long long` value of the Enumerated + * @return An initialized OFASN1Enumerated + */ +- (instancetype)initWithLongLong: (long long)value OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 Enumerated 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 OFASN1Enumerated + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Enumerated.m Index: src/OFASN1Enumerated.m ================================================================== --- /dev/null +++ src/OFASN1Enumerated.m @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2008-2021 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 "OFASN1Enumerated.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" + +extern long long OFASN1DERIntegerParse(const unsigned char *buffer, + size_t length); + +@implementation OFASN1Enumerated +@synthesize longLongValue = _longLongValue; + ++ (instancetype)enumeratedWithLongLong: (long long)value +{ + return [[[self alloc] initWithLongLong: value] autorelease]; +} + +- (instancetype)initWithLongLong: (long long)value +{ + self = [super init]; + + _longLongValue = value; + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + long long value; + + @try { + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberEnumerated || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1) + @throw [OFInvalidArgumentException exception]; + + value = OFASN1DERIntegerParse( + DEREncodedContents.items, DEREncodedContents.count); + } @catch (id e) { + [self release]; + @throw e; + } + + return [self initWithLongLong: value]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (bool)isEqual: (id)object +{ + OFASN1Enumerated *enumerated; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1Enumerated class]]) + return false; + + enumerated = object; + + if (enumerated->_longLongValue != _longLongValue) + return false; + + return true; +} + +- (unsigned long)hash +{ + return (unsigned long)_longLongValue; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _longLongValue]; +} +@end ADDED src/OFASN1IA5String.h Index: src/OFASN1IA5String.h ================================================================== --- /dev/null +++ src/OFASN1IA5String.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/** + * @brief An ASN.1 IA5String. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1IA5String: OFObject +{ + OFString *_IA5StringValue; +} + +/** + * @brief The IA5String value. + */ +@property (readonly, nonatomic) OFString *IA5StringValue; + +/** + * @brief The string value. + */ +@property (readonly, nonatomic) OFString *stringValue; + +/** + * @brief Creates an IA5String with the specified string value. + * + * @param string The string value of the IA5String + * @return A new, autoreleased OFASN1IA5String + */ ++ (instancetype)stringWithString: (OFString *)string; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated IA5String with the specified string + * value. + * + * @param string The string value of the IA5String + * @return An initialized OFASN1IA5String + */ +- (instancetype)initWithString: (OFString *)string OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 IA5String 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 OFASN1IA5String + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1IA5String.m Index: src/OFASN1IA5String.m ================================================================== --- /dev/null +++ src/OFASN1IA5String.m @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2008-2021 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 "OFASN1IA5String.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" + +@implementation OFASN1IA5String +@synthesize IA5StringValue = _IA5StringValue; + ++ (instancetype)stringWithString: (OFString *)string +{ + return [[[self alloc] initWithString: string] autorelease]; +} + +- (instancetype)initWithString: (OFString *)string +{ + self = [super init]; + + @try { + _IA5StringValue = [string copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + void *pool = objc_autoreleasePoolPush(); + OFString *IA5String; + + @try { + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberIA5String || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1) + @throw [OFInvalidArgumentException exception]; + + IA5String= [OFString + stringWithCString: DEREncodedContents.items + encoding: OFStringEncodingASCII + length: DEREncodedContents.count]; + } @catch (id e) { + [self release]; + @throw e; + } + + self = [self initWithString: IA5String]; + + objc_autoreleasePoolPop(pool); + + return self; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_IA5StringValue release]; + + [super dealloc]; +} + +- (OFString *)stringValue +{ + return self.IA5StringValue; +} + +- (bool)isEqual: (id)object +{ + OFASN1IA5String *IA5String; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1IA5String class]]) + return false; + + IA5String = object; + + if (![IA5String->_IA5StringValue isEqual: _IA5StringValue]) + return false; + + return true; +} + +- (unsigned long)hash +{ + return _IA5StringValue.hash; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _IA5StringValue]; +} +@end ADDED src/OFASN1Integer.h Index: src/OFASN1Integer.h ================================================================== --- /dev/null +++ src/OFASN1Integer.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +/** + * @brief An ASN.1 Integer. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1Integer: OFObject +{ + long long _longLongValue; +} + +/** + * @brief The Integer value. + */ +@property (readonly, nonatomic) long long longLongValue; + +/** + * @brief Creates an ASN.1 Integer with the specified integer value. + * + * @param value The `long long` value of the Integer + * @return A new, autoreleased OFASN1Integer + */ ++ (instancetype)integerWithLongLong: (long long)value; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated ASN.1 Integer with the specified + * integer value. + * + * @param value The `long long` value of the Integer + * @return An initialized OFASN1Integer + */ +- (instancetype)initWithLongLong: (long long)value OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 Integer 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 OFASN1Integer + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Integer.m Index: src/OFASN1Integer.m ================================================================== --- /dev/null +++ src/OFASN1Integer.m @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2008-2021 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 "OFString.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" + +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; +} + +@implementation OFASN1Integer +@synthesize longLongValue = _longLongValue; + ++ (instancetype)integerWithLongLong: (long long)value +{ + return [[[self alloc] initWithLongLong: value] autorelease]; +} + +- (instancetype)initWithLongLong: (long long)value +{ + self = [super init]; + + _longLongValue = value; + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + long long value; + + @try { + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberInteger || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1) + @throw [OFInvalidArgumentException exception]; + + value = OFASN1DERIntegerParse( + DEREncodedContents.items, DEREncodedContents.count); + } @catch (id e) { + [self release]; + @throw e; + } + + return [self initWithLongLong: value]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (bool)isEqual: (id)object +{ + OFASN1Integer *integer; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1Integer class]]) + return false; + + integer = object; + + if (integer->_longLongValue != _longLongValue) + return false; + + return true; +} + +- (unsigned long)hash +{ + return (unsigned long)_longLongValue; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _longLongValue]; +} +@end ADDED src/OFASN1NumericString.h Index: src/OFASN1NumericString.h ================================================================== --- /dev/null +++ src/OFASN1NumericString.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/** + * @brief An ASN.1 NumericString. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1NumericString: OFObject +{ + OFString *_numericStringValue; +} + +/** + * @brief The NumericString value. + */ +@property (readonly, nonatomic) OFString *numericStringValue; + +/** + * @brief The string value. + */ +@property (readonly, nonatomic) OFString *stringValue; + +/** + * @brief Creates an NumericString with the specified string value. + * + * @param string The string value of the NumericString + * @return A new, autoreleased OFASN1NumericString + */ ++ (instancetype)stringWithString: (OFString *)string; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated NumericString with the specified + * string value. + * + * @param string The string value of the NumericString + * @return An initialized OFASN1NumericString + */ +- (instancetype)initWithString: (OFString *)string OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 NumericString 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 NumericString + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1NumericString.m Index: src/OFASN1NumericString.m ================================================================== --- /dev/null +++ src/OFASN1NumericString.m @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2008-2021 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 "OFASN1NumericString.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidEncodingException.h" + +@implementation OFASN1NumericString +@synthesize numericStringValue = _numericStringValue; + ++ (instancetype)stringWithString: (OFString *)string +{ + return [[[self alloc] initWithString: string] autorelease]; +} + +- (instancetype)initWithString: (OFString *)string +{ + self = [super init]; + + @try { + void *pool = objc_autoreleasePoolPush(); + const char *cString = string.UTF8String; + size_t length = string.UTF8StringLength; + + for (size_t i = 0; i < length; i++) + if (!OFASCIIIsDigit(cString[i]) && cString[i] != ' ') + @throw [OFInvalidEncodingException exception]; + + _numericStringValue = [string copy]; + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + void *pool = objc_autoreleasePoolPush(); + OFString *numericString; + + @try { + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberNumericString || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1) + @throw [OFInvalidArgumentException exception]; + + numericString = [OFString + stringWithCString: DEREncodedContents.items + encoding: OFStringEncodingASCII + length: DEREncodedContents.count]; + } @catch (id e) { + [self release]; + @throw e; + } + + self = [self initWithString: numericString]; + + objc_autoreleasePoolPop(pool); + + return self; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_numericStringValue release]; + + [super dealloc]; +} + +- (OFString *)stringValue +{ + return self.numericStringValue; +} + +- (bool)isEqual: (id)object +{ + OFASN1NumericString *numericString; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1NumericString class]]) + return false; + + numericString = object; + + if (![numericString->_numericStringValue isEqual: _numericStringValue]) + return false; + + return true; +} + +- (unsigned long)hash +{ + return _numericStringValue.hash; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _numericStringValue]; +} +@end ADDED src/OFASN1ObjectIdentifier.h Index: src/OFASN1ObjectIdentifier.h ================================================================== --- /dev/null +++ src/OFASN1ObjectIdentifier.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFArray OF_GENERIC(ObjetType); +@class OFNumber; + +/** + * @brief An ASN.1 ObjectIdentifier. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1ObjectIdentifier: OFObject +{ + OFArray OF_GENERIC(OFNumber *) *_subidentifiers; +} + +/** + * @brief The subidentifiers of the ObjectIdentifier. + */ +@property (readonly, nonatomic) OFArray OF_GENERIC(OFNumber *) *subidentifiers; + +/** + * @brief Creates an ASN.1 ObjectIdentifier with the specified subidentifiers. + * + * @param subidentifiers The subidentifiers of the ASN.1 ObjectIdentifier + * @return A new, autoreleased OFASN1ObjectIdentifier + */ ++ (instancetype)objectIdentifierWithSubidentifiers: + (OFArray OF_GENERIC(OFNumber *) *)subidentifiers; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated ASN.1 ObjectIdentifier with the + * specified subidentifiers. + * + * @param subidentifiers The subidentifiers of the ASN.1 ObjectIdentifier + * @return An initialized OFASN1ObjectIdentifier + */ +- (instancetype)initWithSubidentifiers: + (OFArray OF_GENERIC(OFNumber *) *)subidentifiers OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 ObjectIdentifier 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 OFASN1ObjectIdentifier + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1ObjectIdentifier.m Index: src/OFASN1ObjectIdentifier.m ================================================================== --- /dev/null +++ src/OFASN1ObjectIdentifier.m @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2008-2021 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 "OFASN1ObjectIdentifier.h" +#import "OFArray.h" +#import "OFData.h" +#import "OFNumber.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" + +@implementation OFASN1ObjectIdentifier +@synthesize subidentifiers = _subidentifiers; + ++ (instancetype)objectIdentifierWithSubidentifiers: + (OFArray OF_GENERIC(OFNumber *) *)subidentifiers +{ + return [[[self alloc] + initWithSubidentifiers: subidentifiers] autorelease]; +} + +- (instancetype)initWithSubidentifiers: + (OFArray OF_GENERIC(OFNumber *) *)subidentifiers +{ + self = [super init]; + + @try { + if (subidentifiers.count < 1) + @throw [OFInvalidFormatException exception]; + + switch ([[subidentifiers objectAtIndex: 0] longLongValue]) { + case 0: + case 1: + case 2: + break; + default: + @throw [OFInvalidFormatException exception]; + } + + _subidentifiers = [subidentifiers copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + void *pool = objc_autoreleasePoolPush(); + OFMutableArray OF_GENERIC(OFNumber *) *subidentifiers; + + @try { + const unsigned char *items = DEREncodedContents.items; + size_t count = DEREncodedContents.count; + unsigned long long value = 0; + uint_fast8_t bits = 0; + + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberObjectIdentifier || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1 || count == 0) + @throw [OFInvalidArgumentException exception]; + + subidentifiers = [OFMutableArray array]; + + for (size_t i = 0; i < count; i++) { + if (bits == 0 && items[i] == 0x80) + @throw [OFInvalidFormatException exception]; + + value = (value << 7) | (items[i] & 0x7F); + bits += 7; + + if (bits > sizeof(unsigned long long) * 8) + @throw [OFOutOfRangeException exception]; + + if (items[i] & 0x80) + continue; + + if (subidentifiers.count == 0) { + if (value < 40) + [subidentifiers addObject: + [OFNumber numberWithInt: 0]]; + else if (value < 80) { + [subidentifiers addObject: + [OFNumber numberWithInt: 1]]; + value -= 40; + } else { + [subidentifiers addObject: + [OFNumber numberWithInt: 2]]; + value -= 80; + } + } + + [subidentifiers addObject: + [OFNumber numberWithUnsignedLongLong: value]]; + + value = 0; + bits = 0; + } + + if (items[count - 1] & 0x80) + @throw [OFInvalidFormatException exception]; + + [subidentifiers makeImmutable]; + } @catch (id e) { + [self release]; + @throw e; + } + + self = [self initWithSubidentifiers: subidentifiers]; + + objc_autoreleasePoolPop(pool); + + return self; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_subidentifiers release]; + + [super dealloc]; +} + +- (bool)isEqual: (id)object +{ + OFASN1ObjectIdentifier *objectIdentifier; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1ObjectIdentifier class]]) + return false; + + objectIdentifier = object; + + if (![objectIdentifier->_subidentifiers isEqual: _subidentifiers]) + return false; + + return true; +} + +- (unsigned long)hash +{ + return _subidentifiers.hash; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: + @"", + [_subidentifiers componentsJoinedByString: @"."]]; +} +@end ADDED src/OFASN1OctetString.h Index: src/OFASN1OctetString.h ================================================================== --- /dev/null +++ src/OFASN1OctetString.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFData; + +/** + * @brief An ASN.1 OctetString. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1OctetString: OFObject +{ + OFData *_octetStringValue; +} + +/** + * @brief The OctetString value. + */ +@property (readonly, nonatomic) OFData *octetStringValue; + +/** + * @brief Creates an OctetString with the specified value. + * + * @param octetString The OctetString value + * @return A new, autoreleased OFASN1OctetString + */ ++ (instancetype)octetStringWithOctetString: (OFData *)octetString; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated OctetString with the specified + * value. + * + * @param octetString The OctetString value + * @return An initialized OFASN1OctetString + */ +- (instancetype)initWithOctetString: (OFData *)octetString + OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 OctetString 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 OctetString + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1OctetString.m Index: src/OFASN1OctetString.m ================================================================== --- /dev/null +++ src/OFASN1OctetString.m @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2008-2021 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 "OFASN1OctetString.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" + +@implementation OFASN1OctetString +@synthesize octetStringValue = _octetStringValue; + ++ (instancetype)octetStringWithOctetString: (OFData *)octetString +{ + return [[[self alloc] initWithOctetString: octetString] autorelease]; +} + +- (instancetype)initWithOctetString: (OFData *)octetString +{ + self = [super init]; + + @try { + _octetStringValue = [octetString copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + @try { + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberOctetString || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1) + @throw [OFInvalidArgumentException exception]; + } @catch (id e) { + [self release]; + @throw e; + } + + return [self initWithOctetString: DEREncodedContents]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_octetStringValue release]; + + [super dealloc]; +} + +- (bool)isEqual: (id)object +{ + OFASN1OctetString *octetString; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1OctetString class]]) + return false; + + octetString = object; + + if (![octetString->_octetStringValue isEqual: _octetStringValue]) + return false; + + return true; +} + +- (unsigned long)hash +{ + return _octetStringValue.hash; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _octetStringValue]; +} +@end ADDED src/OFASN1PrintableString.h Index: src/OFASN1PrintableString.h ================================================================== --- /dev/null +++ src/OFASN1PrintableString.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/** + * @brief An ASN.1 PrintableString. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1PrintableString: OFObject +{ + OFString *_printableStringValue; +} + +/** + * @brief The PrintableString value. + */ +@property (readonly, nonatomic) OFString *printableStringValue; + +/** + * @brief The string value. + */ +@property (readonly, nonatomic) OFString *stringValue; + +/** + * @brief Creates a PrintableString with the specified string value. + * + * @param string The string value of the PrintableString + * @return A new, autoreleased OFASN1PrintableString + */ ++ (instancetype)stringWithString: (OFString *)string; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated PrintableString with the specified + * string value. + * + * @param string The string value of the PrintableString + * @return An initialized OFASN1PrintableString + */ +- (instancetype)initWithString: (OFString *)string OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 PrintableString 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 OFASN1PrintableString + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1PrintableString.m Index: src/OFASN1PrintableString.m ================================================================== --- /dev/null +++ src/OFASN1PrintableString.m @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2008-2021 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 "OFASN1PrintableString.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidEncodingException.h" + +@implementation OFASN1PrintableString +@synthesize printableStringValue = _printableStringValue; + ++ (instancetype)stringWithString: (OFString *)string +{ + return [[[self alloc] initWithString: string] autorelease]; +} + +- (instancetype)initWithString: (OFString *)string +{ + self = [super init]; + + @try { + void *pool = objc_autoreleasePoolPush(); + const char *cString = string.UTF8String; + size_t length = string.UTF8StringLength; + + for (size_t i = 0; i < length; i++) { + if (OFASCIIIsAlnum(cString[i])) + continue; + + switch (cString[i]) { + case ' ': + case '\'': + case '(': + case ')': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case '=': + case '?': + continue; + default: + @throw [OFInvalidEncodingException exception]; + } + } + + _printableStringValue = [string copy]; + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + void *pool = objc_autoreleasePoolPush(); + OFString *printableString; + + @try { + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberPrintableString || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1) + @throw [OFInvalidArgumentException exception]; + + printableString = [OFString + stringWithCString: DEREncodedContents.items + encoding: OFStringEncodingASCII + length: DEREncodedContents.count]; + } @catch (id e) { + [self release]; + @throw e; + } + + self = [self initWithString: printableString]; + + objc_autoreleasePoolPop(pool); + + return self; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_printableStringValue release]; + + [super dealloc]; +} + +- (OFString *)stringValue +{ + return self.printableStringValue; +} + +- (bool)isEqual: (id)object +{ + OFASN1PrintableString *printableString; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1PrintableString class]]) + return false; + + printableString = object; + + if (![printableString->_printableStringValue isEqual: + _printableStringValue]) + return false; + + return true; +} + +- (unsigned long)hash +{ + return _printableStringValue.hash; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _printableStringValue]; +} +@end ADDED src/OFASN1UTF8String.h Index: src/OFASN1UTF8String.h ================================================================== --- /dev/null +++ src/OFASN1UTF8String.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008-2021 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" +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/** + * @brief An ASN.1 UTF8String. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1UTF8String: OFObject +{ + OFString *_UTF8StringValue; +} + +/** + * @brief The UTF8String value. + */ +@property (readonly, nonatomic) OFString *UTF8StringValue; + +/** + * @brief The string value. + */ +@property (readonly, nonatomic) OFString *stringValue; + +/** + * @brief Creates a UTF8String with the specified string value. + * + * @param string The string value of the UTF8String + * @return A new, autoreleased OFASN1UTF8String + */ ++ (instancetype)stringWithString: (OFString *)string; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated UTF8String with the specified + * string value. + * + * @param string The string value of the UTF8String + * @return An initialized OFASN1UTF8String + */ +- (instancetype)initWithString: (OFString *)string OF_DESIGNATED_INITIALIZER; + +/** + * @brief Initializes an already allocated ASN.1 UTF8String 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 OFASN1UTF8String + */ +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1UTF8String.m Index: src/OFASN1UTF8String.m ================================================================== --- /dev/null +++ src/OFASN1UTF8String.m @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2008-2021 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 UTF8StringValue = _UTF8StringValue; + ++ (instancetype)stringWithString: (OFString *)string +{ + return [[[self alloc] initWithString: string] autorelease]; +} + +- (instancetype)initWithString: (OFString *)string +{ + self = [super init]; + + @try { + _UTF8StringValue = [string copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + void *pool = objc_autoreleasePoolPush(); + OFString *UTF8String; + + @try { + if (tagClass != OFASN1TagClassUniversal || + tagNumber != OFASN1TagNumberUTF8String || constructed) + @throw [OFInvalidArgumentException exception]; + + if (DEREncodedContents.itemSize != 1) + @throw [OFInvalidArgumentException exception]; + + UTF8String = [OFString + stringWithUTF8String: DEREncodedContents.items + length: DEREncodedContents.count]; + } @catch (id e) { + [self release]; + @throw e; + } + + self = [self initWithString: UTF8String]; + + objc_autoreleasePoolPop(pool); + + return self; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_UTF8StringValue release]; + + [super dealloc]; +} + +- (OFString *)stringValue +{ + return self.UTF8StringValue; +} + +- (bool)isEqual: (id)object +{ + OFASN1UTF8String *UTF8String; + + if (object == self) + return true; + + if (![object isKindOfClass: [OFASN1UTF8String class]]) + return false; + + UTF8String = object; + + if (![UTF8String->_UTF8StringValue isEqual: _UTF8StringValue]) + return false; + + return true; +} + +- (unsigned long)hash +{ + return _UTF8StringValue.hash; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _UTF8StringValue]; +} +@end ADDED src/OFASN1Value.h Index: src/OFASN1Value.h ================================================================== --- /dev/null +++ src/OFASN1Value.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2008-2021 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 */ + OFASN1TagClassUniversal = 0x0, + /** Application */ + OFASN1TagClassApplication = 0x1, + /** Context specific */ + OFASN1TagClassContextSpecific = 0x2, + /** Private */ + OFASN1TagClassPrivate = 0x3 +} OFASN1TagClass; + +/** + * @brief ASN.1 tag number. + */ +typedef enum { + /** Boolean */ + OFASN1TagNumberBoolean = 0x01, + /** Integer */ + OFASN1TagNumberInteger = 0x02, + /** Bit string */ + OFASN1TagNumberBitString = 0x03, + /** Octet string */ + OFASN1TagNumberOctetString = 0x04, + /** Null */ + OFASN1TagNumberNull = 0x05, + /** Object Identifier */ + OFASN1TagNumberObjectIdentifier = 0x06, + /** Enumerated */ + OFASN1TagNumberEnumerated = 0x0A, + /** UTF-8 string */ + OFASN1TagNumberUTF8String = 0x0C, + /** Sequence */ + OFASN1TagNumberSequence = 0x10, + /** Set */ + OFASN1TagNumberSet = 0x11, + /** NumericString */ + OFASN1TagNumberNumericString = 0x12, + /** PrintableString */ + OFASN1TagNumberPrintableString = 0x13, + /** IA5String */ + OFASN1TagNumberIA5String = 0x16 +} OFASN1TagNumber; + +/** + * @brief A class representing an ASN.1 value. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFASN1Value: OFObject +{ + OFASN1TagClass _tagClass; + OFASN1TagNumber _tagNumber; + bool _constructed; + OFData *_DEREncodedContents; +} + +/** + * @brief The tag class of the value's type. + */ +@property (readonly, nonatomic) OFASN1TagClass tagClass; + +/** + * @brief The tag number of the value's type. + */ +@property (readonly, nonatomic) OFASN1TagNumber 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: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)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: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents + OF_DESIGNATED_INITIALIZER; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1Value.m Index: src/OFASN1Value.m ================================================================== --- /dev/null +++ src/OFASN1Value.m @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2008-2021 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 "OFString.h" + +#import "OFInvalidFormatException.h" + +@implementation OFASN1Value +@synthesize tagClass = _tagClass, tagNumber = _tagNumber; +@synthesize constructed = _constructed; +@synthesize DEREncodedContents = _DEREncodedContents; + ++ (instancetype)valueWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + return [[[self alloc] + initWithTagClass: tagClass + tagNumber: tagNumber + constructed: constructed + DEREncodedContents: DEREncodedContents] autorelease]; +} + +- (instancetype)initWithTagClass: (OFASN1TagClass)tagClass + tagNumber: (OFASN1TagNumber)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; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_DEREncodedContents release]; + + [super dealloc]; +} + +- (bool)isEqual: (id)object +{ + OFASN1Value *value; + + if (object == self) + return true; + + 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; +} + +- (unsigned long)hash +{ + unsigned long hash; + + OFHashInit(&hash); + + OFHashAddByte(&hash, _tagClass & 0xFF); + OFHashAddByte(&hash, _tagNumber & 0xFF); + OFHashAddByte(&hash, _constructed); + OFHashAddHash(&hash, _DEREncodedContents.hash); + + OFHashFinalize(&hash); + + return hash; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: + @"", + _tagClass, _tagNumber, _constructed, + _DEREncodedContents.description]; +} +@end ADDED src/OFData+ASN1DERParsing.h Index: src/OFData+ASN1DERParsing.h ================================================================== --- /dev/null +++ src/OFData+ASN1DERParsing.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2008-2021 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_ASN1DERParsing_reference; +#ifdef __cplusplus +} +#endif + +@interface OFData (ASN1DERParsing) +/** + * @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 objectByParsingASN1DER; + +/** + * @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)objectByParsingASN1DERWithDepthLimit: (size_t)depthLimit; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFData+ASN1DERParsing.m Index: src/OFData+ASN1DERParsing.m ================================================================== --- /dev/null +++ src/OFData+ASN1DERParsing.m @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2008-2021 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+ASN1DERParsing.h" +#import "OFASN1BitString.h" +#import "OFASN1Boolean.h" +#import "OFASN1Enumerated.h" +#import "OFASN1IA5String.h" +#import "OFASN1Integer.h" +#import "OFASN1NumericString.h" +#import "OFASN1ObjectIdentifier.h" +#import "OFASN1OctetString.h" +#import "OFASN1PrintableString.h" +#import "OFASN1UTF8String.h" +#import "OFASN1Value.h" +#import "OFArray.h" +#import "OFNull.h" +#import "OFSet.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); + +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: + valueClass = [OFASN1Boolean class]; + break; + case OFASN1TagNumberInteger: + valueClass = [OFASN1Integer class]; + break; + 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: + valueClass = [OFASN1UTF8String class]; + break; + 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 Index: src/OFData.h ================================================================== --- src/OFData.h +++ src/OFData.h @@ -354,7 +354,8 @@ @end OF_ASSUME_NONNULL_END #import "OFMutableData.h" +#import "OFData+ASN1DERParsing.h" #import "OFData+CryptographicHashing.h" #import "OFData+MessagePackParsing.h" Index: src/OFData.m ================================================================== --- src/OFData.m +++ src/OFData.m @@ -43,10 +43,11 @@ /* References for static linking */ void _references_to_categories_of_OFData(void) { + _OFData_ASN1DERParsing_reference = 1; _OFData_CryptographicHashing_reference = 1; _OFData_MessagePackParsing_reference = 1; } @implementation OFData Index: src/OFNull.h ================================================================== --- src/OFNull.h +++ src/OFNull.h @@ -12,10 +12,11 @@ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #import "OFObject.h" +#import "OFASN1DERRepresentation.h" #import "OFJSONRepresentation.h" #import "OFMessagePackRepresentation.h" #import "OFSerialization.h" OF_ASSUME_NONNULL_BEGIN @@ -25,15 +26,15 @@ * * @brief A class for representing null values in collections. */ OF_SUBCLASSING_RESTRICTED @interface OFNull: OFObject + OFMessagePackRepresentation, OFASN1DERRepresentation> /** * @brief Returns an OFNull singleton. * * @return An OFNull singleton */ + (OFNull *)null; @end OF_ASSUME_NONNULL_END Index: src/OFNull.m ================================================================== --- src/OFNull.m +++ src/OFNull.m @@ -104,10 +104,16 @@ - (OFData *)messagePackRepresentation { uint8_t type = 0xC0; return [OFData dataWithItems: &type count: 1]; } + +- (OFData *)ASN1DERRepresentation +{ + const unsigned char bytes[] = { OFASN1TagNumberNull, 0 }; + return [OFData dataWithItems: bytes count: sizeof(bytes)]; +} - (instancetype)autorelease { return self; } Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -139,10 +139,22 @@ #import "OFRunLoop.h" #ifdef OF_WINDOWS # import "OFWindowsRegistryKey.h" #endif + +#import "OFASN1BitString.h" +#import "OFASN1Boolean.h" +#import "OFASN1Enumerated.h" +#import "OFASN1IA5String.h" +#import "OFASN1Integer.h" +#import "OFASN1NumericString.h" +#import "OFASN1ObjectIdentifier.h" +#import "OFASN1OctetString.h" +#import "OFASN1PrintableString.h" +#import "OFASN1UTF8String.h" +#import "OFASN1Value.h" #import "OFAllocFailedException.h" #import "OFException.h" #import "OFChangeCurrentDirectoryFailedException.h" #import "OFChecksumMismatchException.h" Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -16,10 +16,12 @@ DISTCLEAN = Info.plist PROG_NOINST = tests${PROG_SUFFIX} STATIC_LIB_NOINST = ${TESTS_STATIC_LIB} SRCS = ForwardingTests.m \ + OFASN1DERParsingTests.m \ + OFASN1DERRepresentationTests.m \ OFArrayTests.m \ ${OF_BLOCK_TESTS_M} \ OFCharacterSetTests.m \ OFDataTests.m \ OFDateTests.m \ ADDED tests/OFASN1DERParsingTests.m Index: tests/OFASN1DERParsingTests.m ================================================================== --- /dev/null +++ tests/OFASN1DERParsingTests.m @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2008-2021 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 "TestsAppDelegate.h" + +static OFString *module = @"OFData+ASN1DERParsing"; + +@implementation TestsAppDelegate (OFASN1DERParsingTests) +- (void)ASN1DERParsingTests +{ + void *pool = objc_autoreleasePoolPush(); + OFASN1BitString *bitString; + OFArray *array; + OFSet *set; + OFEnumerator *enumerator; + + /* Boolean */ + TEST(@"Parsing of boolean", + ![[[OFData dataWithItems: "\x01\x01\x00" + count: 3] objectByParsingASN1DER] boolValue] && + [[[OFData dataWithItems: "\x01\x01\xFF" + count: 3] objectByParsingASN1DER] boolValue]) + + EXPECT_EXCEPTION(@"Detection of invalid boolean #1", + OFInvalidFormatException, + [[OFData dataWithItems: "\x01\x01\x01" + count: 3] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid boolean #2", + OFInvalidFormatException, + [[OFData dataWithItems: "\x01\x02\x00\x00" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid boolean #3", + OFInvalidFormatException, + [[OFData dataWithItems: "\x01\x00" + count: 2] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated boolean", + OFTruncatedDataException, + [[OFData dataWithItems: "\x01\x01" + count: 2] objectByParsingASN1DER]) + + /* Integer */ + TEST(@"Parsing of integer", + [[[OFData dataWithItems: "\x02\x00" + count: 2] objectByParsingASN1DER] + longLongValue] == 0 && + [[[OFData dataWithItems: "\x02\x01\x01" + count: 3] objectByParsingASN1DER] + longLongValue] == 1 && + [[[OFData dataWithItems: "\x02\x02\x01\x04" + count: 4] objectByParsingASN1DER] + longLongValue] == 260 && + [[[OFData dataWithItems: "\x02\x01\xFF" + count: 3] objectByParsingASN1DER] + longLongValue] == -1 && + [[[OFData dataWithItems: "\x02\x03\xFF\x00\x00" + count: 5] objectByParsingASN1DER] + longLongValue] == -65536 && + (unsigned long long)[[[OFData dataWithItems: "\x02\x09\x00\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF" + "\xFF" + count: 11] + objectByParsingASN1DER] longLongValue] == ULLONG_MAX) + + EXPECT_EXCEPTION(@"Detection of invalid integer #1", + OFInvalidFormatException, + [[OFData dataWithItems: "\x02\x02\x00\x00" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid integer #2", + OFInvalidFormatException, + [[OFData dataWithItems: "\x02\x02\x00\x7F" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid integer #3", + OFInvalidFormatException, + [[OFData dataWithItems: "\x02\x02\xFF\x80" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of out of range integer", + OFOutOfRangeException, + [[OFData dataWithItems: "\x02\x09\x01" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + count: 11] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated integer", + OFTruncatedDataException, + [[OFData dataWithItems: "\x02\x02\x00" + count: 3] objectByParsingASN1DER]) + + /* Bit string */ + TEST(@"Parsing of bit string", + (bitString = [[OFData dataWithItems: "\x03\x01\x00" + count: 3] objectByParsingASN1DER]) && + [bitString.bitStringValue isEqual: [OFData dataWithItems: "" + count: 0]] && + bitString.bitStringLength == 0 && + (bitString = [[OFData dataWithItems: "\x03\x0D\x01Hello World\x80" + count: 15] objectByParsingASN1DER]) && + [bitString.bitStringValue + isEqual: [OFData dataWithItems: "Hello World\x80" + count: 12]] && + bitString.bitStringLength == 95 && + (bitString = [[OFData dataWithItems: "\x03\x81\x80\x00xxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxx" + count: 131] objectByParsingASN1DER]) && + [bitString.bitStringValue + isEqual: [OFData dataWithItems: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxx" + count: 127]] && + bitString.bitStringLength == 127 * 8) + + EXPECT_EXCEPTION(@"Detection of invalid bit string #1", + OFInvalidFormatException, + [[OFData dataWithItems: "\x03\x00" + count: 2] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid bit string #2", + OFInvalidFormatException, + [[OFData dataWithItems: "\x03\x01\x01" + count: 3] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of out of range bit string", + OFOutOfRangeException, + [[OFData dataWithItems: "\x03\x89" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01" + count: 11] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated bit string", + OFTruncatedDataException, + [[OFData dataWithItems: "\x03\x01" + count: 2] objectByParsingASN1DER]) + + /* Octet string */ + TEST(@"Parsing of octet string", + [[[[OFData dataWithItems: "\x04\x0CHello World!" + count: 14] objectByParsingASN1DER] + octetStringValue] isEqual: [OFData dataWithItems: "Hello World!" + count: 12]] && + [[[[OFData dataWithItems: "\x04\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxx" + count: 131] objectByParsingASN1DER] + octetStringValue] isEqual: + [OFData dataWithItems: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + count: 128]]) + + EXPECT_EXCEPTION(@"Detection of out of range octet string", + OFOutOfRangeException, + [[OFData dataWithItems: "\x04\x89" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01" + count: 11] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated octet string", + OFTruncatedDataException, + [[OFData dataWithItems: "\x04\x01" + count: 2] objectByParsingASN1DER]) + + /* Null */ + TEST(@"Parsing of null", + [[[OFData dataWithItems: "\x05\x00" + count: 2] objectByParsingASN1DER] + isEqual: [OFNull null]]) + + EXPECT_EXCEPTION(@"Detection of invalid null", + OFInvalidFormatException, + [[OFData dataWithItems: "\x05\x01\x00" + count: 3] objectByParsingASN1DER]) + + /* Object Identifier */ + TEST(@"Parsing of Object Identifier", + (array = [[[OFData dataWithItems: "\x06\x01\x27" + count: 3] objectByParsingASN1DER] + subidentifiers]) && array.count == 2 && + [[array objectAtIndex: 0] unsignedLongLongValue] == 0 && + [[array objectAtIndex: 1] unsignedLongLongValue] == 39 && + (array = [[[OFData dataWithItems: "\x06\x01\x4F" + count: 3] objectByParsingASN1DER] + subidentifiers]) && array.count == 2 && + [[array objectAtIndex: 0] unsignedLongLongValue] == 1 && + [[array objectAtIndex: 1] unsignedLongLongValue] == 39 && + (array = [[[OFData dataWithItems: "\x06\x02\x88\x37" + count: 4] objectByParsingASN1DER] + subidentifiers]) && array.count == 2 && + [[array objectAtIndex: 0] unsignedLongLongValue] == 2 && + [[array objectAtIndex: 1] unsignedLongLongValue] == 999 && + (array = [[[OFData dataWithItems: "\x06\x09\x2A\x86\x48\x86\xF7\x0D" + "\x01\x01\x0B" + count: 11] objectByParsingASN1DER] + subidentifiers]) && array.count == 7 && + [[array objectAtIndex: 0] unsignedLongLongValue] == 1 && + [[array objectAtIndex: 1] unsignedLongLongValue] == 2 && + [[array objectAtIndex: 2] unsignedLongLongValue] == 840 && + [[array objectAtIndex: 3] unsignedLongLongValue] == 113549 && + [[array objectAtIndex: 4] unsignedLongLongValue] == 1 && + [[array objectAtIndex: 5] unsignedLongLongValue] == 1 && + [[array objectAtIndex: 6] unsignedLongLongValue] == 11) + + EXPECT_EXCEPTION(@"Detection of invalid Object Identifier #1", + OFInvalidFormatException, + [[OFData dataWithItems: "\x06\x01\x81" + count: 3] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid Object Identifier #2", + OFInvalidFormatException, + [[OFData dataWithItems: "\x06\x02\x80\x01" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of out of range Object Identifier", + OFOutOfRangeException, + [[OFData dataWithItems: "\x06\x0A\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\x7F" + count: 12] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated Object Identifier", + OFTruncatedDataException, + [[OFData dataWithItems: "\x06\x02\x00" + count: 3] objectByParsingASN1DER]) + + /* Enumerated */ + TEST(@"Parsing of enumerated", + [[[OFData dataWithItems: "\x0A\x00" + count: 2] objectByParsingASN1DER] longLongValue] == + 0 && + [[[OFData dataWithItems: "\x0A\x01\x01" + count: 3] objectByParsingASN1DER] longLongValue] == + 1 && + [[[OFData dataWithItems: "\x0A\x02\x01\x04" + count: 4] objectByParsingASN1DER] longLongValue] == + 260 && + [[[OFData dataWithItems: "\x0A\x01\xFF" + count: 3] objectByParsingASN1DER] longLongValue] == + -1 && + [[[OFData dataWithItems: "\x0A\x03\xFF\x00\x00" + count: 5] objectByParsingASN1DER] longLongValue] == + -65536 && + (unsigned long long)[[[OFData dataWithItems: "\x0A\x09\x00\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF" + "\xFF" + count: 11] + objectByParsingASN1DER] longLongValue] == ULLONG_MAX) + + EXPECT_EXCEPTION(@"Detection of invalid enumerated #1", + OFInvalidFormatException, + [[OFData dataWithItems: "\x0A\x02\x00\x00" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid enumerated #2", + OFInvalidFormatException, + [[OFData dataWithItems: "\x0A\x02\x00\x7F" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid enumerated #3", + OFInvalidFormatException, + [[OFData dataWithItems: "\x0A\x02\xFF\x80" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of out of range enumerated", + OFOutOfRangeException, + [[OFData dataWithItems: "\x0A\x09\x01" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + count: 11] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated enumerated", + OFTruncatedDataException, + [[OFData dataWithItems: "\x0A\x02\x00" + count: 3] objectByParsingASN1DER]) + + /* UTF-8 string */ + TEST(@"Parsing of UTF-8 string", + [[[[OFData dataWithItems: "\x0C\x0EHällo Wörld!" + count: 16] objectByParsingASN1DER] + UTF8StringValue] isEqual: @"Hällo Wörld!"] && + [[[[OFData dataWithItems: "\x0C\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxx" + count: 131] objectByParsingASN1DER] + UTF8StringValue] isEqual: @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + @"xxxxxxxxxxx"]) + + 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] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated UTF-8 string", + OFTruncatedDataException, + [[OFData dataWithItems: "\x0C\x01" + count: 2] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated length", + OFTruncatedDataException, + [[OFData dataWithItems: "\x0C\x83\x01\x01" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid / inefficient length #1", + OFInvalidFormatException, + [[OFData dataWithItems: "\x0C\x81\x7F" + count: 3] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of invalid / inefficient length #2", + OFInvalidFormatException, + [[OFData dataWithItems: "\x0C\x82\x00\x80xxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxx" + count: 132] objectByParsingASN1DER]) + + /* Sequence */ + TEST(@"Parsing of sequence", + (array = [[OFData dataWithItems: "\x30\x00" + count: 2] objectByParsingASN1DER]) && + [array isKindOfClass: [OFArray class]] && array.count == 0 && + (array = [[OFData dataWithItems: "\x30\x09\x02\x01\x7B\x0C\x04Test" + count: 11] objectByParsingASN1DER]) && + [array isKindOfClass: [OFArray class]] && array.count == 2 && + [[array objectAtIndex: 0] longLongValue] == 123 && + [[[array objectAtIndex: 1] stringValue] isEqual: @"Test"]) + + EXPECT_EXCEPTION(@"Detection of truncated sequence #1", + OFTruncatedDataException, + [[OFData dataWithItems: "\x30\x01" + count: 2] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated sequence #2", + OFTruncatedDataException, + [[OFData dataWithItems: "\x30\x04\x02\x01\x01\x00\x00" + count: 7] objectByParsingASN1DER]) + + /* Set */ + TEST(@"Parsing of set", + (set = [[OFData dataWithItems: "\x31\x00" + count: 2] objectByParsingASN1DER]) && + [set isKindOfClass: [OFSet class]] && set.count == 0 && + (set = [[OFData dataWithItems: "\x31\x09\x02\x01\x7B\x0C\x04Test" + count: 11] objectByParsingASN1DER]) && + [set isKindOfClass: [OFSet class]] && set.count == 2 && + (enumerator = [set objectEnumerator]) && + [[enumerator nextObject] longLongValue] == 123 && + [[[enumerator nextObject] stringValue] isEqual: @"Test"]) + + EXPECT_EXCEPTION(@"Detection of invalid set", + OFInvalidFormatException, + [[OFData dataWithItems: "\x31\x06\x02\x01\x02\x02\x01\x01" + count: 8] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated set #1", + OFTruncatedDataException, + [[OFData dataWithItems: "\x31\x01" + count: 2] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated set #2", + OFTruncatedDataException, + [[OFData dataWithItems: "\x31\x04\x02\x01\x01\x00\x00" + count: 7] objectByParsingASN1DER]) + + /* NumericString */ + TEST(@"Parsing of NumericString", + [[[[OFData dataWithItems: "\x12\x0B" "12345 67890" + count: 13] objectByParsingASN1DER] + numericStringValue] isEqual: @"12345 67890"] && + [[[[OFData dataWithItems: "\x12\x81\x80" "0000000000000000000000000" + "0000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000" + "00000000000000000000000" + count: 131] objectByParsingASN1DER] + numericStringValue] isEqual: @"000000000000000000000000000000000000" + @"000000000000000000000000000000000000" + @"000000000000000000000000000000000000" + @"00000000000000000000"]) + + EXPECT_EXCEPTION(@"Detection of invalid NumericString", + OFInvalidEncodingException, + [[OFData dataWithItems: "\x12\x02." + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of out of range NumericString", + OFOutOfRangeException, + [[OFData dataWithItems: "\x12\x89" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01" + count: 11] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated NumericString", + OFTruncatedDataException, + [[OFData dataWithItems: "\x12\x01" + count: 2] objectByParsingASN1DER]) + + /* PrintableString */ + TEST(@"Parsing of PrintableString", + [[[[OFData dataWithItems: "\x13\x0CHello World." + count: 14] objectByParsingASN1DER] + printableStringValue] isEqual: @"Hello World."] && + [[[[OFData dataWithItems: "\x13\x81\x80 '()+,-./:=?abcdefghijklmnop" + "qrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ '()" + "+,-./:=?abcdefghijklmnopqrstuvwxyzABCDEF" + "GHIJKLMNOPQRSTUVWXYZ" + count: 131] objectByParsingASN1DER] + printableStringValue] isEqual: @" '()+,-./:=?abcdefghijklmnopqrstuv" + @"wxyzABCDEFGHIJKLMNOPQRSTUVWXYZ '()" + @"+,-./:=?abcdefghijklmnopqrstuvwxyz" + @"ABCDEFGHIJKLMNOPQRSTUVWXYZ"]) + + EXPECT_EXCEPTION(@"Detection of invalid PrintableString", + OFInvalidEncodingException, + [[OFData dataWithItems: "\x13\x02;" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of out of range PrintableString", + OFOutOfRangeException, + [[OFData dataWithItems: "\x13\x89" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01" + count: 11] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated PrintableString", + OFTruncatedDataException, + [[OFData dataWithItems: "\x13\x01" + count: 2] objectByParsingASN1DER]) + + /* IA5String */ + TEST(@"Parsing of IA5String", + [[[[OFData dataWithItems: "\x16\x0CHello World!" + count: 14] objectByParsingASN1DER] + IA5StringValue] isEqual: @"Hello World!"] && + [[[[OFData dataWithItems: "\x16\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxx" + count: 131] objectByParsingASN1DER] + IA5StringValue] isEqual: @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + @"xxxxxxxx"]) + + EXPECT_EXCEPTION(@"Detection of invalid IA5String", + OFInvalidEncodingException, + [[OFData dataWithItems: "\x16\x02ä" + count: 4] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of out of range IA5String", + OFOutOfRangeException, + [[OFData dataWithItems: "\x16\x89" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01" + count: 11] objectByParsingASN1DER]) + + EXPECT_EXCEPTION(@"Detection of truncated IA5String", + OFTruncatedDataException, + [[OFData dataWithItems: "\x16\x01" + count: 2] objectByParsingASN1DER]) + + objc_autoreleasePoolPop(pool); +} +@end ADDED tests/OFASN1DERRepresentationTests.m Index: tests/OFASN1DERRepresentationTests.m ================================================================== --- /dev/null +++ tests/OFASN1DERRepresentationTests.m @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008-2021 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 "TestsAppDelegate.h" + +static OFString *module; + +@implementation TestsAppDelegate (OFASN1DERRepresentationTests) +- (void)ASN1DERRepresentationTests +{ + void *pool = objc_autoreleasePoolPush(); + OFData *data; + + module = @"OFASN1BitString"; + TEST(@"-[ASN1DERRepresentation]", + (data = [OFData dataWithItems: "\xFF\x00\xF8" count: 3]) && + [[[OFASN1BitString bitStringWithBitString: data length: 21] + ASN1DERRepresentation] isEqual: + [OFData dataWithItems: "\x03\x04\x03\xFF\x00\xF8" count: 6]] && + (data = [OFData dataWithItems: "abcdefäöü" count: 12]) && + [[[OFASN1BitString bitStringWithBitString: data length: 12 * 8] + ASN1DERRepresentation] isEqual: + [OFData dataWithItems: "\x03\x0D\x00" "abcdefäöü" count: 15]] && + (data = [OFData dataWithItems: "" count: 0]) && + [[[OFASN1BitString bitStringWithBitString: data length: 0] + ASN1DERRepresentation] isEqual: + [OFData dataWithItems: "\x03\x01\x00" count: 3]]) + + module = @"OFASN1Boolean"; + TEST(@"-[ASN1DERRepresentation]", + [[[OFASN1Boolean booleanWithBool: false] ASN1DERRepresentation] + isEqual: [OFData dataWithItems: "\x01\x01\x00" count: 3]] && + [[[OFASN1Boolean booleanWithBool: true] ASN1DERRepresentation] + isEqual: [OFData dataWithItems: "\x01\x01\xFF" count: 3]]) + + module = @"OFNull"; + TEST(@"-[OFASN1DERRepresentation]", + [[[OFNull null] ASN1DERRepresentation] isEqual: + [OFData dataWithItems: "\x05\x00" count: 2]]) + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -56,10 +56,18 @@ - (void)outputTesting: (OFString *)test inModule: (OFString *)module; - (void)outputSuccess: (OFString *)test inModule: (OFString *)module; - (void)outputFailure: (OFString *)test inModule: (OFString *)module; @end + +@interface TestsAppDelegate (OFASN1DERParsingTests) +- (void)ASN1DERParsingTests; +@end + +@interface TestsAppDelegate (OFASN1DERRepresentationTests) +- (void)ASN1DERRepresentationTests; +@end @interface TestsAppDelegate (OFArrayTests) - (void)arrayTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -440,10 +440,12 @@ [self XMLNodeTests]; [self XMLElementBuilderTests]; [self serializationTests]; [self JSONTests]; [self propertyListTests]; + [self ASN1DERParsingTests]; + [self ASN1DERRepresentationTests]; #if defined(OF_HAVE_PLUGINS) [self pluginTests]; #endif #ifdef OF_WINDOWS [self windowsRegistryKeyTests];