Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -13,11 +13,13 @@ SRCS = OFASN1BitString.m \ OFASN1Boolean.m \ OFASN1IA5String.m \ OFASN1Integer.m \ OFASN1Null.m \ + OFASN1NumericString.m \ OFASN1OctetString.m \ + OFASN1PrintableString.m \ OFASN1UTF8String.m \ OFASN1Value.m \ OFApplication.m \ OFArray.m \ OFAutoreleasePool.m \ ADDED src/OFASN1NumericString.h Index: src/OFASN1NumericString.h ================================================================== --- src/OFASN1NumericString.h +++ src/OFASN1NumericString.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/*! + * @brief An ASN.1 NumericString. + */ +@interface OFASN1NumericString: OFASN1Value +{ + OFString *_numericStringValue; +} + +/*! + * @brief The NumericString value. + */ +@property (readonly, nonatomic) OFString *numericStringValue; + +/*! + * @brief The string value. + */ +@property (readonly, nonatomic) OFString *stringValue; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1NumericString.m Index: src/OFASN1NumericString.m ================================================================== --- src/OFASN1NumericString.m +++ src/OFASN1NumericString.m @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFASN1NumericString.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidEncodingException.h" + +@implementation OFASN1NumericString +@synthesize numericStringValue = _numericStringValue; + +- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + self = [super initWithTagClass: tagClass + tagNumber: tagNumber + constructed: constructed + DEREncodedContents: DEREncodedContents]; + + @try { + const unsigned char *items = [_DEREncodedContents items]; + size_t count = [_DEREncodedContents count]; + + if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL || + _tagNumber != OF_ASN1_TAG_NUMBER_NUMERIC_STRING || + _constructed) + @throw [OFInvalidArgumentException exception]; + + for (size_t i = 0; i < count; i++) + if (!of_ascii_isdigit(items[i]) && items[i] != ' ') + @throw [OFInvalidEncodingException exception]; + + _numericStringValue = [[OFString alloc] + initWithCString: [_DEREncodedContents items] + encoding: OF_STRING_ENCODING_ASCII + length: [_DEREncodedContents count]]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_numericStringValue release]; + + [super dealloc]; +} + +- (OFString *)stringValue +{ + return [self numericStringValue]; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _numericStringValue]; +} +@end ADDED src/OFASN1PrintableString.h Index: src/OFASN1PrintableString.h ================================================================== --- src/OFASN1PrintableString.h +++ src/OFASN1PrintableString.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFASN1Value.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/*! + * @brief An ASN.1 PrintableString. + */ +@interface OFASN1PrintableString: OFASN1Value +{ + OFString *_printableStringValue; +} + +/*! + * @brief The PrintableString value. + */ +@property (readonly, nonatomic) OFString *printableStringValue; + +/*! + * @brief The string value. + */ +@property (readonly, nonatomic) OFString *stringValue; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFASN1PrintableString.m Index: src/OFASN1PrintableString.m ================================================================== --- src/OFASN1PrintableString.m +++ src/OFASN1PrintableString.m @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFASN1PrintableString.h" +#import "OFData.h" +#import "OFString.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidEncodingException.h" + +@implementation OFASN1PrintableString +@synthesize printableStringValue = _printableStringValue; + +- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass + tagNumber: (of_asn1_tag_number_t)tagNumber + constructed: (bool)constructed + DEREncodedContents: (OFData *)DEREncodedContents +{ + self = [super initWithTagClass: tagClass + tagNumber: tagNumber + constructed: constructed + DEREncodedContents: DEREncodedContents]; + + @try { + const unsigned char *items = [_DEREncodedContents items]; + size_t count = [_DEREncodedContents count]; + + if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL || + _tagNumber != OF_ASN1_TAG_NUMBER_PRINTABLE_STRING || + _constructed) + @throw [OFInvalidArgumentException exception]; + + for (size_t i = 0; i < count; i++) { + if (of_ascii_isalnum(items[i])) + continue; + + switch (items[i]) { + case ' ': + case '\'': + case '(': + case ')': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case '=': + case '?': + continue; + default: + @throw [OFInvalidEncodingException exception]; + } + } + + _printableStringValue = [[OFString alloc] + initWithCString: [_DEREncodedContents items] + encoding: OF_STRING_ENCODING_ASCII + length: [_DEREncodedContents count]]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_printableStringValue release]; + + [super dealloc]; +} + +- (OFString *)stringValue +{ + return [self printableStringValue]; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"", + _printableStringValue]; +} +@end Index: src/OFASN1Value.h ================================================================== --- src/OFASN1Value.h +++ src/OFASN1Value.h @@ -40,25 +40,29 @@ /*! * @brief ASN.1 tag number. */ typedef enum { /*! Boolean */ - OF_ASN1_TAG_NUMBER_BOOLEAN = 0x01, + OF_ASN1_TAG_NUMBER_BOOLEAN = 0x01, /*! Integer */ - OF_ASN1_TAG_NUMBER_INTEGER = 0x02, + OF_ASN1_TAG_NUMBER_INTEGER = 0x02, /*! Bit string */ - OF_ASN1_TAG_NUMBER_BIT_STRING = 0x03, + OF_ASN1_TAG_NUMBER_BIT_STRING = 0x03, /*! Octet string */ - OF_ASN1_TAG_NUMBER_OCTET_STRING = 0x04, + OF_ASN1_TAG_NUMBER_OCTET_STRING = 0x04, /*! Null */ - OF_ASN1_TAG_NUMBER_NULL = 0x05, + OF_ASN1_TAG_NUMBER_NULL = 0x05, /*! UTF-8 string */ - OF_ASN1_TAG_NUMBER_UTF8_STRING = 0x0C, + OF_ASN1_TAG_NUMBER_UTF8_STRING = 0x0C, /*! Sequence */ - OF_ASN1_TAG_NUMBER_SEQUENCE = 0x10, + OF_ASN1_TAG_NUMBER_SEQUENCE = 0x10, + /*! NumericString */ + OF_ASN1_TAG_NUMBER_NUMERIC_STRING = 0x12, + /*! PrintableString */ + OF_ASN1_TAG_NUMBER_PRINTABLE_STRING = 0x13, /*! IA5String */ - OF_ASN1_TAG_NUMBER_IA5_STRING = 0x16 + OF_ASN1_TAG_NUMBER_IA5_STRING = 0x16 } of_asn1_tag_number_t; /*! * @brief A class representing an ASN.1 value. */ Index: src/OFData+ASN1DERValue.m ================================================================== --- src/OFData+ASN1DERValue.m +++ src/OFData+ASN1DERValue.m @@ -21,11 +21,13 @@ #import "OFASN1BitString.h" #import "OFASN1Boolean.h" #import "OFASN1IA5String.h" #import "OFASN1Integer.h" #import "OFASN1Null.h" +#import "OFASN1NumericString.h" #import "OFASN1OctetString.h" +#import "OFASN1PrintableString.h" #import "OFASN1UTF8String.h" #import "OFASN1Value.h" #import "OFArray.h" #import "OFInvalidArgumentException.h" @@ -115,11 +117,11 @@ contents = [self subdataWithRange: of_range(bytesConsumed, contentsLength)]; bytesConsumed += contentsLength; - switch (tag) { + switch (tag & ~ASN1_TAG_CONSTRUCTED_MASK) { case OF_ASN1_TAG_NUMBER_BOOLEAN: valueClass = [OFASN1Boolean class]; break; case OF_ASN1_TAG_NUMBER_INTEGER: valueClass = [OFASN1Integer class]; @@ -135,14 +137,21 @@ break; case OF_ASN1_TAG_NUMBER_UTF8_STRING: valueClass = [OFASN1UTF8String class]; break; case OF_ASN1_TAG_NUMBER_SEQUENCE: - @throw [OFInvalidFormatException exception]; - case OF_ASN1_TAG_NUMBER_SEQUENCE | ASN1_TAG_CONSTRUCTED_MASK: + if (!(tag & ASN1_TAG_CONSTRUCTED_MASK)) + @throw [OFInvalidFormatException exception]; + *object = parseSequence(contents, depthLimit - 1); return bytesConsumed; + case OF_ASN1_TAG_NUMBER_NUMERIC_STRING: + valueClass = [OFASN1NumericString class]; + break; + case OF_ASN1_TAG_NUMBER_PRINTABLE_STRING: + valueClass = [OFASN1PrintableString class]; + break; case OF_ASN1_TAG_NUMBER_IA5_STRING: valueClass = [OFASN1IA5String class]; break; default: valueClass = [OFASN1Value class]; Index: tests/OFASN1DERValueTests.m ================================================================== --- tests/OFASN1DERValueTests.m +++ tests/OFASN1DERValueTests.m @@ -21,11 +21,13 @@ #import "OFASN1BitString.h" #import "OFASN1Boolean.h" #import "OFASN1IA5String.h" #import "OFASN1Integer.h" #import "OFASN1Null.h" +#import "OFASN1NumericString.h" #import "OFASN1OctetString.h" +#import "OFASN1PrintableString.h" #import "OFASN1UTF8String.h" #import "OFArray.h" #import "OFString.h" #import "OFAutoreleasePool.h" @@ -43,10 +45,11 @@ { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; OFASN1BitString *bitString; OFArray *array; + /* Boolean */ TEST(@"Parsing of boolean", ![[[OFData dataWithItems: "\x01\x01\x00" count: 3] ASN1DERValue] booleanValue] && [[[OFData dataWithItems: "\x01\x01\xFF" count: 3] ASN1DERValue] booleanValue]) @@ -65,10 +68,11 @@ EXPECT_EXCEPTION(@"Detection of truncated boolean", OFTruncatedDataException, [[OFData dataWithItems: "\x01\x01" count: 2] ASN1DERValue]) + /* Integer */ TEST(@"Parsing of integer", [[[OFData dataWithItems: "\x02\x00" count: 2] ASN1DERValue] integerValue] == 0 && [[[OFData dataWithItems: "\x02\x01\x01" count: 3] ASN1DERValue] integerValue] == 1 && @@ -103,10 +107,11 @@ EXPECT_EXCEPTION(@"Detection of truncated integer", OFTruncatedDataException, [[OFData dataWithItems: "\x02\x02\x00" count: 3] ASN1DERValue]) + /* Bit string */ TEST(@"Parsing of bit string", (bitString = [[OFData dataWithItems: "\x03\x01\x00" count: 3] ASN1DERValue]) && [[bitString bitStringValue] isEqual: [OFData dataWithItems: "" count: 0]] && @@ -120,11 +125,11 @@ (bitString = [[OFData dataWithItems: "\x03\x81\x80\x00xxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxx" - count: 131] ASN1DERValue]) && + count: 131] ASN1DERValue]) && [[bitString bitStringValue] isEqual: [OFData dataWithItems: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxx" @@ -147,20 +152,21 @@ EXPECT_EXCEPTION(@"Detection of truncated bit string", OFTruncatedDataException, [[OFData dataWithItems: "\x03\x01" count: 2] ASN1DERValue]) + /* Octet string */ TEST(@"Parsing of octet string", [[[[OFData dataWithItems: "\x04\x0CHello World!" count: 14] ASN1DERValue] octetStringValue] isEqual: [OFData dataWithItems: "Hello World!" count: 12]] && [[[[OFData dataWithItems: "\x04\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxx" - count: 131] ASN1DERValue] octetStringValue] + count: 131] ASN1DERValue] octetStringValue] isEqual: [OFData dataWithItems: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxx" count: 128]]) @@ -173,28 +179,30 @@ EXPECT_EXCEPTION(@"Detection of truncated octet string", OFTruncatedDataException, [[OFData dataWithItems: "\x04\x01" count: 2] ASN1DERValue]) - TEST(@"Parsing of NULL", + /* Null */ + TEST(@"Parsing of null", [[[OFData dataWithItems: "\x05\x00" count: 2] ASN1DERValue] isKindOfClass: [OFASN1Null class]]) EXPECT_EXCEPTION(@"Detection of invalid NULL", OFInvalidFormatException, [[OFData dataWithItems: "\x05\x01\x00" count: 3] ASN1DERValue]) + /* UTF-8 string */ TEST(@"Parsing of UTF-8 string", [[[[OFData dataWithItems: "\x0C\x0EHällo Wörld!" - count: 16] ASN1DERValue] UTF8StringValue] + count: 16] ASN1DERValue] UTF8StringValue] isEqual: @"Hällo Wörld!"] && [[[[OFData dataWithItems: "\x0C\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxx" - count: 131] ASN1DERValue] UTF8StringValue] + count: 131] ASN1DERValue] UTF8StringValue] isEqual: @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @"xxxxxxxxxxxxxxxx"]) EXPECT_EXCEPTION(@"Detection of out of range UTF-8 string", @@ -221,10 +229,11 @@ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxx" count: 132] ASN1DERValue]) + /* Sequence */ TEST(@"Parsing of sequence", (array = [[OFData dataWithItems: "\x30\x00" count: 2] ASN1DERValue]) && [array isKindOfClass: [OFArray class]] && [array count] == 0 && (array = [[OFData dataWithItems: "\x30\x09\x02\x01\x7B\x0C\x04Test" @@ -240,19 +249,78 @@ EXPECT_EXCEPTION(@"Parsing of truncated sequence #2", OFTruncatedDataException, [[OFData dataWithItems: "\x30\x04\x02\x01\x01\x00\x00" count: 7] ASN1DERValue]) + /* NumericString */ + TEST(@"Parsing of NumericString", + [[[[OFData dataWithItems: "\x12\x0B" "12345 67890" + count: 13] ASN1DERValue] numericStringValue] + isEqual: @"12345 67890"] && + [[[[OFData dataWithItems: "\x12\x81\x80" "0000000000000000000000000" + "0000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000" + "00000000000000000000000" + count: 131] ASN1DERValue] numericStringValue] + isEqual: @"00000000000000000000000000000000000000000000000000000000" + @"00000000000000000000000000000000000000000000000000000000" + @"0000000000000000"]) + + EXPECT_EXCEPTION(@"Detection of invalid NumericString", + OFInvalidEncodingException, + [[OFData dataWithItems: "\x12\x02." + count: 4] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of out of range NumericString", + OFOutOfRangeException, + [[OFData dataWithItems: "\x12\x89" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01" + count: 11] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of truncated NumericString", + OFTruncatedDataException, [[OFData dataWithItems: "\x12\x01" + count: 2] ASN1DERValue]) + + /* PrintableString */ + TEST(@"Parsing of PrintableString", + [[[[OFData dataWithItems: "\x13\x0CHello World." + count: 14] ASN1DERValue] printableStringValue] + isEqual: @"Hello World."] && + [[[[OFData dataWithItems: "\x13\x81\x80 '()+,-./:=?abcdefghijklmnop" + "qrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ '()" + "+,-./:=?abcdefghijklmnopqrstuvwxyzABCDEF" + "GHIJKLMNOPQRSTUVWXYZ" + count: 131] ASN1DERValue] printableStringValue] + isEqual: @" '()+,-./:=?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR" + @"STUVWXYZ '()+,-./:=?abcdefghijklmnopqrstuvwxyzABCDEFGHIJ" + @"KLMNOPQRSTUVWXYZ"]) + + EXPECT_EXCEPTION(@"Detection of invalid PrintableString", + OFInvalidEncodingException, + [[OFData dataWithItems: "\x13\x02;" + count: 4] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of out of range PrintableString", + OFOutOfRangeException, + [[OFData dataWithItems: "\x13\x89" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01" + count: 11] ASN1DERValue]) + + EXPECT_EXCEPTION(@"Detection of truncated PrintableString", + OFTruncatedDataException, [[OFData dataWithItems: "\x13\x01" + count: 2] ASN1DERValue]) + + /* IA5String */ TEST(@"Parsing of IA5String", [[[[OFData dataWithItems: "\x16\x0CHello World!" - count: 14] ASN1DERValue] IA5StringValue] + count: 14] ASN1DERValue] IA5StringValue] isEqual: @"Hello World!"] && [[[[OFData dataWithItems: "\x16\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "xxxxxxxxxxxxxxxxxxxx" - count: 131] ASN1DERValue] IA5StringValue] + count: 131] ASN1DERValue] IA5StringValue] isEqual: @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @"xxxxxxxxxxxxxxxx"]) EXPECT_EXCEPTION(@"Detection of invalid IA5String",