summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Schleifer <js@heap.zone>2018-10-13 23:40:15 +0200
committerJonathan Schleifer <js@heap.zone>2018-10-13 23:40:15 +0200
commit0f1899cf2059de34dfc0370920e2b40e4a112ac8 (patch)
tree1959d215416e1e7cb7fc6ded8c245bb2aa2b14f7
parent7aeb90692680bb00802d9cb8b8f34d0f4b5a0b38 (diff)
Initial ASN.1 DER parsing support
-rw-r--r--src/Makefile8
-rw-r--r--src/OFASN1Boolean.h36
-rw-r--r--src/OFASN1Boolean.m62
-rw-r--r--src/OFASN1Integer.h36
-rw-r--r--src/OFASN1Integer.m76
-rw-r--r--src/OFASN1Null.h28
-rw-r--r--src/OFASN1Null.m51
-rw-r--r--src/OFASN1UTF8String.h38
-rw-r--r--src/OFASN1UTF8String.m62
-rw-r--r--src/OFASN1Value.h120
-rw-r--r--src/OFASN1Value.m118
-rw-r--r--src/OFData+ASN1DERValue.h54
-rw-r--r--src/OFData+ASN1DERValue.m170
-rw-r--r--src/OFData+MessagePackValue.m2
-rw-r--r--src/OFData.h1
-rw-r--r--src/OFData.m1
-rw-r--r--tests/Makefile1
-rw-r--r--tests/OFDataASN1DERValueTests.m172
-rw-r--r--tests/TestsAppDelegate.h4
-rw-r--r--tests/TestsAppDelegate.m1
20 files changed, 1039 insertions, 2 deletions
diff --git a/src/Makefile b/src/Makefile
index 831aba90..2872d095 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -10,7 +10,12 @@ FRAMEWORK = ${OBJFW_FRAMEWORK}
LIB_MAJOR = ${OBJFW_LIB_MAJOR}
LIB_MINOR = ${OBJFW_LIB_MINOR}
-SRCS = OFApplication.m \
+SRCS = OFASN1Boolean.m \
+ OFASN1Integer.m \
+ OFASN1Null.m \
+ OFASN1UTF8String.m \
+ OFASN1Value.m \
+ OFApplication.m \
OFArray.m \
OFAutoreleasePool.m \
OFBlock.m \
@@ -19,6 +24,7 @@ SRCS = OFApplication.m \
OFConstantString.m \
OFCountedSet.m \
OFData.m \
+ OFData+ASN1DERValue.m \
OFData+CryptoHashing.m \
OFData+MessagePackValue.m \
OFDate.m \
diff --git a/src/OFASN1Boolean.h b/src/OFASN1Boolean.h
new file mode 100644
index 00000000..e3d7caec
--- /dev/null
+++ b/src/OFASN1Boolean.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#import "OFASN1Value.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+/*!
+ * @brief An ASN.1 boolean.
+ */
+@interface OFASN1Boolean: OFASN1Value
+{
+ bool _booleanValue;
+}
+
+/*!
+ * @brief The boolean value.
+ */
+@property (readonly, nonatomic) bool booleanValue;
+@end
+
+OF_ASSUME_NONNULL_END
diff --git a/src/OFASN1Boolean.m b/src/OFASN1Boolean.m
new file mode 100644
index 00000000..6ce00956
--- /dev/null
+++ b/src/OFASN1Boolean.m
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#include "config.h"
+
+#import "OFASN1Boolean.h"
+#import "OFData.h"
+
+#import "OFInvalidArgumentException.h"
+#import "OFInvalidFormatException.h"
+
+@implementation OFASN1Boolean
+@synthesize booleanValue = _booleanValue;
+
+- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass
+ tagNumber: (of_asn1_tag_number_t)tagNumber
+ constructed: (bool)constructed
+ DEREncodedContents: (OFData *)DEREncodedContents
+{
+ self = [super initWithTagClass: tagClass
+ tagNumber: tagNumber
+ constructed: constructed
+ DEREncodedContents: DEREncodedContents];
+
+ @try {
+ unsigned char value;
+
+ if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL ||
+ _tagNumber != OF_ASN1_TAG_NUMBER_BOOLEAN || _constructed)
+ @throw [OFInvalidArgumentException exception];
+
+ if ([_DEREncodedContents count] != 1)
+ @throw [OFInvalidFormatException exception];
+
+ value = *(unsigned char *)[_DEREncodedContents itemAtIndex: 0];
+
+ if (value != 0 && value != 0xFF)
+ @throw [OFInvalidFormatException exception];
+
+ _booleanValue = value;
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+@end
diff --git a/src/OFASN1Integer.h b/src/OFASN1Integer.h
new file mode 100644
index 00000000..8db5f449
--- /dev/null
+++ b/src/OFASN1Integer.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#import "OFASN1Value.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+/*!
+ * @brief An ASN.1 integer.
+ */
+@interface OFASN1Integer: OFASN1Value
+{
+ intmax_t _integerValue;
+}
+
+/*!
+ * @brief The integer value.
+ */
+@property (readonly, nonatomic) intmax_t integerValue;
+@end
+
+OF_ASSUME_NONNULL_END
diff --git a/src/OFASN1Integer.m b/src/OFASN1Integer.m
new file mode 100644
index 00000000..b87df202
--- /dev/null
+++ b/src/OFASN1Integer.m
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#include "config.h"
+
+#import "OFASN1Integer.h"
+#import "OFData.h"
+
+#import "OFInvalidArgumentException.h"
+#import "OFInvalidFormatException.h"
+#import "OFOutOfRangeException.h"
+
+@implementation OFASN1Integer
+@synthesize integerValue = _integerValue;
+
+- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass
+ tagNumber: (of_asn1_tag_number_t)tagNumber
+ constructed: (bool)constructed
+ DEREncodedContents: (OFData *)DEREncodedContents
+{
+ self = [super initWithTagClass: tagClass
+ tagNumber: tagNumber
+ constructed: constructed
+ DEREncodedContents: DEREncodedContents];
+
+ @try {
+ const unsigned char *items;
+ size_t count;
+ uintmax_t value;
+
+ if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL ||
+ _tagNumber != OF_ASN1_TAG_NUMBER_INTEGER || _constructed)
+ @throw [OFInvalidArgumentException exception];
+
+ /* TODO: Support for big numbers */
+ items = [_DEREncodedContents items];
+ count = [_DEREncodedContents count];
+ value = 0;
+
+ if (count > sizeof(uintmax_t) &&
+ (count != sizeof(uintmax_t) + 1 || items[0] != 0))
+ @throw [OFOutOfRangeException exception];
+
+ if (count >= 2 && ((items[0] == 0 && !(items[1] & 0x80)) ||
+ (items[0] == 0xFF && items[1] & 0x80)))
+ @throw [OFInvalidFormatException exception];
+
+ if (count >= 1 && items[0] & 0x80)
+ value = ~(uintmax_t)0;
+
+ while (count--)
+ value = (value << 8) | *items++;
+
+ _integerValue = value;
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+@end
diff --git a/src/OFASN1Null.h b/src/OFASN1Null.h
new file mode 100644
index 00000000..2cf740a0
--- /dev/null
+++ b/src/OFASN1Null.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#import "OFASN1Value.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+/*!
+ * @brief An ASN.1 null value.
+ */
+@interface OFASN1Null: OFASN1Value
+@end
+
+OF_ASSUME_NONNULL_END
diff --git a/src/OFASN1Null.m b/src/OFASN1Null.m
new file mode 100644
index 00000000..fb68b8d3
--- /dev/null
+++ b/src/OFASN1Null.m
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#include "config.h"
+
+#import "OFASN1Null.h"
+#import "OFData.h"
+
+#import "OFInvalidArgumentException.h"
+#import "OFInvalidFormatException.h"
+
+@implementation OFASN1Null
+- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass
+ tagNumber: (of_asn1_tag_number_t)tagNumber
+ constructed: (bool)constructed
+ DEREncodedContents: (OFData *)DEREncodedContents
+{
+ self = [super initWithTagClass: tagClass
+ tagNumber: tagNumber
+ constructed: constructed
+ DEREncodedContents: DEREncodedContents];
+
+ @try {
+ if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL ||
+ _tagNumber != OF_ASN1_TAG_NUMBER_NULL || _constructed)
+ @throw [OFInvalidArgumentException exception];
+
+ if ([_DEREncodedContents count] != 0)
+ @throw [OFInvalidFormatException exception];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+@end
diff --git a/src/OFASN1UTF8String.h b/src/OFASN1UTF8String.h
new file mode 100644
index 00000000..01c5d5fe
--- /dev/null
+++ b/src/OFASN1UTF8String.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#import "OFASN1Value.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+@class OFString;
+
+/*!
+ * @brief An ASN.1 UTF-8 string.
+ */
+@interface OFASN1UTF8String: OFASN1Value
+{
+ OFString *_stringValue;
+}
+
+/*!
+ * @brief The string value.
+ */
+@property (readonly, nonatomic) OFString *stringValue;
+@end
+
+OF_ASSUME_NONNULL_END
diff --git a/src/OFASN1UTF8String.m b/src/OFASN1UTF8String.m
new file mode 100644
index 00000000..ce9299eb
--- /dev/null
+++ b/src/OFASN1UTF8String.m
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#include "config.h"
+
+#import "OFASN1UTF8String.h"
+#import "OFData.h"
+#import "OFString.h"
+
+#import "OFInvalidArgumentException.h"
+
+@implementation OFASN1UTF8String
+@synthesize stringValue = _stringValue;
+
+- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass
+ tagNumber: (of_asn1_tag_number_t)tagNumber
+ constructed: (bool)constructed
+ DEREncodedContents: (OFData *)DEREncodedContents
+{
+ self = [super initWithTagClass: tagClass
+ tagNumber: tagNumber
+ constructed: constructed
+ DEREncodedContents: DEREncodedContents];
+
+ @try {
+ if (_tagClass != OF_ASN1_TAG_CLASS_UNIVERSAL ||
+ _tagNumber != OF_ASN1_TAG_NUMBER_UTF8_STRING ||
+ _constructed)
+ @throw [OFInvalidArgumentException exception];
+
+ _stringValue = [[OFString alloc]
+ initWithUTF8String: [_DEREncodedContents items]
+ length: [_DEREncodedContents count]];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_stringValue release];
+
+ [super dealloc];
+}
+@end
diff --git a/src/OFASN1Value.h b/src/OFASN1Value.h
new file mode 100644
index 00000000..3ae39746
--- /dev/null
+++ b/src/OFASN1Value.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#import "OFObject.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+/*! @file */
+
+@class OFData;
+
+/*!
+ * @brief ASN.1 tag class.
+ */
+typedef enum {
+ /*! Universal */
+ OF_ASN1_TAG_CLASS_UNIVERSAL = 0x0,
+ /*! Application */
+ OF_ASN1_TAG_CLASS_APPLICATION = 0x1,
+ /*! Context specific */
+ OF_ASN1_TAG_CLASS_CONTEXT_SPECIFIC = 0x2,
+ /*! Private */
+ OF_ASN1_TAG_CLASS_PRIVATE = 0x3
+} of_asn1_tag_class_t;
+
+/*!
+ * @brief ASN.1 tag number.
+ */
+typedef enum {
+ /*! Boolean */
+ OF_ASN1_TAG_NUMBER_BOOLEAN = 0x01,
+ /*! Integer */
+ OF_ASN1_TAG_NUMBER_INTEGER = 0x02,
+ /*! Null */
+ OF_ASN1_TAG_NUMBER_NULL = 0x05,
+ /*! UTF-8 string */
+ OF_ASN1_TAG_NUMBER_UTF8_STRING = 0x0C,
+ /*! Sequence */
+ OF_ASN1_TAG_NUMBER_SEQUENCE = 0x10
+} of_asn1_tag_number_t;
+
+/*!
+ * @brief A class representing an ASN.1 value.
+ */
+@interface OFASN1Value: OFObject <OFCopying>
+{
+ of_asn1_tag_class_t _tagClass;
+ of_asn1_tag_number_t _tagNumber;
+ bool _constructed;
+ OFData *_DEREncodedContents;
+}
+
+/*!
+ * @brief The tag class of the value's type.
+ */
+@property (readonly, nonatomic) of_asn1_tag_class_t tagClass;
+
+/*!
+ * @brief The tag number of the value's type.
+ */
+@property (readonly, nonatomic) of_asn1_tag_number_t tagNumber;
+
+/*!
+ * @brief Whether the value if of a constructed type.
+ */
+@property (readonly, nonatomic, getter=isConstructed) bool constructed;
+
+/*!
+ * @brief The DER-encoded contents octets of the value.
+ */
+@property (readonly, nonatomic) OFData *DEREncodedContents;
+
+/*!
+ * @brief Creates a new ASN.1 value with the specified arguments.
+ *
+ * @param tagClass The tag class of the value's type
+ * @param tagNumber The tag number of the value's type
+ * @param constructed Whether the value if of a constructed type
+ * @param DEREncodedContents The DER-encoded contents octets of the value.
+ * @return A new ASN.1 value
+ */
++ (instancetype)valueWithTagClass: (of_asn1_tag_class_t)tagClass
+ tagNumber: (of_asn1_tag_number_t)tagNumber
+ constructed: (bool)constructed
+ DEREncodedContents: (OFData *)DEREncodedContents;
+
+- (instancetype)init OF_UNAVAILABLE;
+
+/*!
+ * @brief Initializes an already allocated ASN.1 value with the specified
+ * arguments.
+ *
+ * @param tagClass The tag class of the value's type
+ * @param tagNumber The tag number of the value's type
+ * @param constructed Whether the value if of a constructed type
+ * @param DEREncodedContents The DER-encoded contents octets of the value.
+ * @return An initialized ASN.1 value
+ */
+- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass
+ tagNumber: (of_asn1_tag_number_t)tagNumber
+ constructed: (bool)constructed
+ DEREncodedContents: (OFData *)DEREncodedContents
+ OF_DESIGNATED_INITIALIZER;
+@end
+
+OF_ASSUME_NONNULL_END
diff --git a/src/OFASN1Value.m b/src/OFASN1Value.m
new file mode 100644
index 00000000..f56a46db
--- /dev/null
+++ b/src/OFASN1Value.m
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#include "config.h"
+
+#import "OFASN1Value.h"
+#import "OFData.h"
+
+#import "OFInvalidFormatException.h"
+
+@implementation OFASN1Value
+@synthesize tagClass = _tagClass, tagNumber = _tagNumber;
+@synthesize constructed = _constructed;
+@synthesize DEREncodedContents = _DEREncodedContents;
+
++ (instancetype)valueWithTagClass: (of_asn1_tag_class_t)tagClass
+ tagNumber: (of_asn1_tag_number_t)tagNumber
+ constructed: (bool)constructed
+ DEREncodedContents: (OFData *)DEREncodedContents
+{
+ return [[[self alloc]
+ initWithTagClass: tagClass
+ tagNumber: tagNumber
+ constructed: constructed
+ DEREncodedContents: DEREncodedContents] autorelease];
+}
+
+- (instancetype)init
+{
+ OF_INVALID_INIT_METHOD
+}
+
+- (instancetype)initWithTagClass: (of_asn1_tag_class_t)tagClass
+ tagNumber: (of_asn1_tag_number_t)tagNumber
+ constructed: (bool)constructed
+ DEREncodedContents: (OFData *)DEREncodedContents
+{
+ self = [super init];
+
+ @try {
+ if ([DEREncodedContents itemSize] != 1)
+ @throw [OFInvalidFormatException exception];
+
+ _tagClass = tagClass;
+ _tagNumber = tagNumber;
+ _constructed = constructed;
+ _DEREncodedContents = [DEREncodedContents copy];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_DEREncodedContents release];
+
+ [super dealloc];
+}
+
+- (bool)isEqual: (id)object
+{
+ OFASN1Value *value;
+
+ if (![object isKindOfClass: [OFASN1Value class]])
+ return false;
+
+ value = object;
+
+ if (value->_tagClass != _tagClass)
+ return false;
+ if (value->_tagNumber != _tagNumber)
+ return false;
+ if (value->_constructed != _constructed)
+ return false;
+ if (![value->_DEREncodedContents isEqual: _DEREncodedContents])
+ return false;
+
+ return true;
+}
+
+- (uint32_t)hash
+{
+ uint32_t hash;
+
+ OF_HASH_INIT(hash);
+
+ OF_HASH_ADD(hash, _tagClass & 0xFF);
+ OF_HASH_ADD(hash, _tagNumber & 0xFF);
+ OF_HASH_ADD(hash, _constructed);
+ OF_HASH_ADD_HASH(hash, [_DEREncodedContents hash]);
+
+ OF_HASH_FINALIZE(hash);
+
+ return hash;
+}
+
+- (id)copy
+{
+ return [self retain];
+}
+@end
diff --git a/src/OFData+ASN1DERValue.h b/src/OFData+ASN1DERValue.h
new file mode 100644
index 00000000..667d897b
--- /dev/null
+++ b/src/OFData+ASN1DERValue.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#import "OFData.h"
+#import "OFASN1Value.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern int _OFData_ASN1DERValue_reference;
+#ifdef __cplusplus
+}
+#endif
+
+@interface OFData (ASN1DERValue)
+/*!
+ * @brief The data interpreted as ASN.1 in DER representation and parsed as an
+ * object.
+ *
+ * This is either an OFArray (for a sequence), an OFSet (for a set) or an
+ * OFASN1Value.
+ */
+@property (readonly, nonatomic) id ASN1DERValue;
+
+/*!
+ * @brief Parses the ASN.1 DER representation and returns it as an object.
+ *
+ * This is either an OFArray (for a sequence), an OFSet (for a set) or an
+ * OFASN1Value.
+ *
+ * @param depthLimit The maximum depth the parser should accept (defaults to 32
+ * if not specified, 0 means no limit (insecure!))
+ * @return The ASN.1 DER representation as an object
+ */
+- (id)ASN1DERValueWithDepthLimit: (size_t)depthLimit;
+@end
+
+OF_ASSUME_NONNULL_END
diff --git a/src/OFData+ASN1DERValue.m b/src/OFData+ASN1DERValue.m
new file mode 100644
index 00000000..cf86818a
--- /dev/null
+++ b/src/OFData+ASN1DERValue.m
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#include "config.h"
+
+#import "OFData+ASN1DERValue.h"
+#import "OFASN1Boolean.h"
+#import "OFASN1Integer.h"
+#import "OFASN1Null.h"
+#import "OFASN1UTF8String.h"
+#import "OFASN1Value.h"
+#import "OFArray.h"
+
+#import "OFInvalidArgumentException.h"
+#import "OFInvalidFormatException.h"
+#import "OFOutOfRangeException.h"
+#import "OFTruncatedDataException.h"
+
+enum {
+ ASN1_TAG_CONSTRUCTED_MASK = 0x20
+};
+
+int _OFData_ASN1DERValue_reference;
+
+static size_t parseObject(OFData *self, id *object, size_t depthLimit);
+
+static OFArray *
+parseSequence(OFData *contents, size_t depthLimit)
+{
+ size_t count = [contents count];
+ OFMutableArray *ret;
+
+ if (depthLimit == 0)
+ @throw [OFOutOfRangeException exception];
+
+ ret = [OFMutableArray array];
+
+ while (count > 0) {
+ id object;
+ size_t objectLength;
+
+ objectLength = parseObject(contents, &object, depthLimit);
+
+ count -= objectLength;
+ contents = [contents subdataWithRange:
+ of_range(objectLength, count)];
+
+ [ret addObject: object];
+ }
+
+ [ret makeImmutable];
+
+ return ret;
+}
+
+static size_t
+parseObject(OFData *self, id *object, size_t depthLimit)
+{
+ const unsigned char *items = [self items];
+ size_t count = [self count];
+ unsigned char tag;
+ size_t contentsLength, bytesConsumed = 0;
+ Class valueClass;
+ OFData *contents;
+
+ if (count < 2)
+ @throw [OFTruncatedDataException exception];
+
+ tag = *items++;
+ contentsLength = *items++;
+ bytesConsumed += 2;
+
+ if (contentsLength > 127) {
+ uint_fast8_t lengthLength = contentsLength & 0x7F;
+
+ if (lengthLength > sizeof(size_t))
+ @throw [OFOutOfRangeException exception];
+
+ if (count - bytesConsumed < lengthLength)
+ @throw [OFTruncatedDataException exception];
+
+ if (lengthLength == 0 ||
+ (lengthLength == 1 && items[0] < 0x80) ||
+ (lengthLength >= 2 && items[0] == 0))
+ @throw [OFInvalidFormatException exception];
+
+ contentsLength = 0;
+
+ for (uint_fast8_t i = 0; i < lengthLength; i++)
+ contentsLength = (contentsLength << 8) | *items++;
+
+ bytesConsumed += lengthLength;
+
+ if (contentsLength <= 127)
+ @throw [OFInvalidFormatException exception];
+ }
+
+ if (count - bytesConsumed < contentsLength)
+ @throw [OFTruncatedDataException exception];
+
+ contents = [self subdataWithRange:
+ of_range(bytesConsumed, contentsLength)];
+ bytesConsumed += contentsLength;
+
+ switch (tag) {
+ case OF_ASN1_TAG_NUMBER_BOOLEAN:
+ valueClass = [OFASN1Boolean class];
+ break;
+ case OF_ASN1_TAG_NUMBER_INTEGER:
+ valueClass = [OFASN1Integer class];
+ break;
+ case OF_ASN1_TAG_NUMBER_NULL:
+ valueClass = [OFASN1Null class];
+ break;
+ case OF_ASN1_TAG_NUMBER_UTF8_STRING:
+ valueClass = [OFASN1UTF8String class];
+ break;
+ case OF_ASN1_TAG_NUMBER_SEQUENCE | ASN1_TAG_CONSTRUCTED_MASK:
+ *object = parseSequence(contents, depthLimit - 1);
+ return bytesConsumed;
+ default:
+ valueClass = [OFASN1Value class];
+ break;
+ }
+
+ *object = [valueClass valueWithTagClass: tag >> 6
+ tagNumber: tag & 0x1F
+ constructed: tag & ASN1_TAG_CONSTRUCTED_MASK
+ DEREncodedContents: contents];
+ return bytesConsumed;
+}
+
+@implementation OFData (ASN1DERValue)
+- (id)ASN1DERValue
+{
+ return [self ASN1DERValueWithDepthLimit: 32];
+}
+
+- (id)ASN1DERValueWithDepthLimit: (size_t)depthLimit
+{
+ void *pool = objc_autoreleasePoolPush();
+ id object;
+
+ if ([self itemSize] != 1)
+ @throw [OFInvalidArgumentException exception];
+
+ if (parseObject(self, &object, depthLimit) != [self count])
+ @throw [OFInvalidFormatException exception];
+
+ [object retain];
+
+ objc_autoreleasePoolPop(pool);
+
+ return [object autorelease];
+}
+@end
diff --git a/src/OFData+MessagePackValue.m b/src/OFData+MessagePackValue.m
index 02832d0b..d6895ed7 100644
--- a/src/OFData+MessagePackValue.m
+++ b/src/OFData+MessagePackValue.m
@@ -571,6 +571,6 @@ parseObject(const unsigned char *buffer, size_t length, id *object,
objc_autoreleasePoolPop(pool);
- return object;
+ return [object autorelease];
}
@end
diff --git a/src/OFData.h b/src/OFData.h
index f2f60b2b..4151f1ca 100644
--- a/src/OFData.h
+++ b/src/OFData.h
@@ -325,5 +325,6 @@ enum {
OF_ASSUME_NONNULL_END
#import "OFMutableData.h"
+#import "OFData+ASN1DERValue.h"
#import "OFData+CryptoHashing.h"
#import "OFData+MessagePackValue.h"
diff --git a/src/OFData.m b/src/OFData.m
index 56c53b15..4b18d9c1 100644
--- a/src/OFData.m
+++ b/src/OFData.m
@@ -48,6 +48,7 @@
void
_references_to_categories_of_OFData(void)
{
+ _OFData_ASN1DERValue_reference = 1;
_OFData_CryptoHashing_reference = 1;
_OFData_MessagePackValue_reference = 1;
}
diff --git a/tests/Makefile b/tests/Makefile
index 50684697..c24ece22 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -14,6 +14,7 @@ SRCS = ForwardingTests.m \
${OFBLOCKTESTS_M} \
OFCharacterSetTests.m \
OFDataTests.m \
+ OFDataASN1DERValueTests.m \
OFDateTests.m \
OFDictionaryTests.m \
OFInvocationTests.m \
diff --git a/tests/OFDataASN1DERValueTests.m b/tests/OFDataASN1DERValueTests.m
new file mode 100644
index 00000000..d3e8a5d0
--- /dev/null
+++ b/tests/OFDataASN1DERValueTests.m
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
+ * 2018
+ * Jonathan Schleifer <js@heap.zone>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#include "config.h"
+
+#import "OFData.h"
+#import "OFASN1Boolean.h"
+#import "OFASN1Integer.h"
+#import "OFASN1Null.h"
+#import "OFASN1UTF8String.h"
+#import "OFArray.h"
+#import "OFString.h"
+#import "OFAutoreleasePool.h"
+
+#import "TestsAppDelegate.h"
+
+#import "OFInvalidFormatException.h"
+#import "OFOutOfRangeException.h"
+#import "OFTruncatedDataException.h"
+
+static OFString *module = @"OFData+ASN1DERValue";
+
+@implementation TestsAppDelegate (OFDataASN1DERValueTests)
+- (void)dataASN1DERValueTests
+{
+ OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
+ OFArray *array;
+
+ TEST(@"Parsing of boolean",
+ ![[[OFData dataWithItems: "\x01\x01\x00"
+ count: 3] ASN1DERValue] booleanValue] &&
+ [[[OFData dataWithItems: "\x01\x01\xFF"
+ count: 3] ASN1DERValue] booleanValue])
+
+ EXPECT_EXCEPTION(@"Detection of invalid boolean #1",
+ OFInvalidFormatException, [[OFData dataWithItems: "\x01\x01\x01"
+ count: 3] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of invalid boolean #2",
+ OFInvalidFormatException, [[OFData dataWithItems: "\x01\x02\x00\x00"
+ count: 4] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of invalid boolean #3",
+ OFInvalidFormatException, [[OFData dataWithItems: "\x01\x00"
+ count: 2] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of truncated boolean",
+ OFTruncatedDataException, [[OFData dataWithItems: "\x01\x01"
+ count: 2] ASN1DERValue])
+
+ TEST(@"Parsing of integer",
+ [[[OFData dataWithItems: "\x02\x00"
+ count: 2] ASN1DERValue] integerValue] == 0 &&
+ [[[OFData dataWithItems: "\x02\x01\x01"
+ count: 3] ASN1DERValue] integerValue] == 1 &&
+ [[[OFData dataWithItems: "\x02\x02\x01\x04"
+ count: 4] ASN1DERValue] integerValue] == 260 &&
+ [[[OFData dataWithItems: "\x02\x01\xFF"
+ count: 3] ASN1DERValue] integerValue] == -1 &&
+ [[[OFData dataWithItems: "\x02\x03\xFF\x00\x00"
+ count: 5] ASN1DERValue] integerValue] == -65536 &&
+ (uintmax_t)[[[OFData dataWithItems: "\x02\x09\x00\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF"
+ count: 11] ASN1DERValue]
+ integerValue] == UINTMAX_MAX)
+
+ EXPECT_EXCEPTION(@"Detecting of invalid integer #1",
+ OFInvalidFormatException, [[OFData dataWithItems: "\x02\x02\x00\x00"
+ count: 4] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detecting of invalid integer #2",
+ OFInvalidFormatException, [[OFData dataWithItems: "\x02\x02\x00\x7F"
+ count: 4] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detecting of invalid integer #3",
+ OFInvalidFormatException, [[OFData dataWithItems: "\x02\x02\xFF\x80"
+ count: 4] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of out of range integer",
+ OFOutOfRangeException,
+ [[OFData dataWithItems: "\x02\x09\x01"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ count: 11] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of truncated integer",
+ OFTruncatedDataException, [[OFData dataWithItems: "\x02\x02\x00"
+ count: 3] ASN1DERValue])
+
+ TEST(@"Parsing of NULL",
+ [[[OFData dataWithItems: "\x05\x00"
+ count: 2] ASN1DERValue]
+ isKindOfClass: [OFASN1Null class]])
+
+ EXPECT_EXCEPTION(@"Detection of invalid NULL",
+ OFInvalidFormatException, [[OFData dataWithItems: "\x05\x01\x00"
+ count: 3] ASN1DERValue])
+
+ TEST(@"Parsing of UTF-8 string",
+ [[[[OFData dataWithItems: "\x0C\x0EHällo Wörld!"
+ count: 16] ASN1DERValue] stringValue]
+ isEqual: @"Hällo Wörld!"] &&
+ [[[[OFData dataWithItems: "\x0C\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxx"
+ count: 131] ASN1DERValue] stringValue]
+ isEqual: @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ @"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ @"xxxxxxxxxxxxxxxx"])
+
+ EXPECT_EXCEPTION(@"Detection of out of range UTF-8 string",
+ OFOutOfRangeException,
+ [[OFData dataWithItems: "\x0C\x89"
+ "\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+ count: 11] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of truncated UTF-8 string",
+ OFTruncatedDataException, [[OFData dataWithItems: "\x0C\x01"
+ count: 2] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of truncated length",
+ OFTruncatedDataException, [[OFData dataWithItems: "\x0C\x83\x01\x01"
+ count: 4] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of invalid / inefficient length #1",
+ OFInvalidFormatException, [[OFData dataWithItems: "\x0C\x81\x7F"
+ count: 3] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Detection of invalid / inefficient length #2",
+ OFInvalidFormatException,
+ [[OFData dataWithItems: "\x0C\x82\x00\x80xxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxx"
+ count: 132] ASN1DERValue])
+
+ TEST(@"Parsing of sequence",
+ (array = [[OFData dataWithItems: "\x30\x00"
+ count: 2] ASN1DERValue]) &&
+ [array isKindOfClass: [OFArray class]] && [array count] == 0 &&
+ (array = [[OFData dataWithItems: "\x30\x09\x02\x01\x7B\x0C\x04Test"
+ count: 11] ASN1DERValue]) &&
+ [array isKindOfClass: [OFArray class]] && [array count] == 2 &&
+ [[array objectAtIndex: 0] integerValue] == 123 &&
+ [[[array objectAtIndex: 1] stringValue] isEqual: @"Test"])
+
+ EXPECT_EXCEPTION(@"Parsing of truncated sequence #1",
+ OFTruncatedDataException, [[OFData dataWithItems: "\x30\x01"
+ count: 2] ASN1DERValue])
+
+ EXPECT_EXCEPTION(@"Parsing of truncated sequence #2",
+ OFTruncatedDataException,
+ [[OFData dataWithItems: "\x30\x04\x02\x01\x01\x00\x00"
+ count: 7] ASN1DERValue])
+
+ [pool drain];
+}
+@end
diff --git a/tests/TestsAppDelegate.h b/tests/TestsAppDelegate.h
index b20f5518..5bd57288 100644
--- a/tests/TestsAppDelegate.h
+++ b/tests/TestsAppDelegate.h
@@ -107,6 +107,10 @@ enum {
- (void)dataTests;
@end
+@interface TestsAppDelegate (OFDataASN1DERValueTests)
+- (void)dataASN1DERValueTests;
+@end
+
@interface TestsAppDelegate (OFDateTests)
- (void)dateTests;
@end
diff --git a/tests/TestsAppDelegate.m b/tests/TestsAppDelegate.m
index c50ec504..09fe23e5 100644
--- a/tests/TestsAppDelegate.m
+++ b/tests/TestsAppDelegate.m
@@ -390,6 +390,7 @@ main(int argc, char *argv[])
[self stringTests];
[self characterSetTests];
[self dataTests];
+ [self dataASN1DERValueTests];
[self arrayTests];
[self dictionaryTests];
[self listTests];