Skip to content

Commit 9c5adf2

Browse files
authored
Merge pull request #52 from outfoxx/fix/random-data
Random data resilience for ASN1 and CBOR
2 parents 87b95c9 + 680bbc1 commit 9c5adf2

File tree

5 files changed

+103
-34
lines changed

5 files changed

+103
-34
lines changed

Sources/PotentASN1/ASN1DERReader.swift

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,20 @@ internal enum ASN1DERReader {
8282

8383
private static func parseItem(_ buffer: inout UnsafeBufferPointer<UInt8>) throws -> ASN1 {
8484
var (tagValue, itemBuffer) = try parseTagged(&buffer)
85-
defer {
86-
assert(itemBuffer.isEmpty)
85+
86+
let item = try parseItem(&itemBuffer, as: tagValue)
87+
88+
if !itemBuffer.isEmpty {
89+
throw ASN1Serialization.Error.invalidTaggedItem
8790
}
8891

89-
return try parseItem(&itemBuffer, as: tagValue)
92+
return item
9093
}
9194

9295
private static func parseItem(
9396
_ itemBuffer: inout UnsafeBufferPointer<UInt8>,
9497
as tagValue: ASN1.AnyTag
9598
) throws -> ASN1 {
96-
defer {
97-
assert(itemBuffer.isEmpty)
98-
}
9999

100100
guard let tag = ASN1.Tag(rawValue: tagValue) else {
101101
// Check required constructed types
@@ -110,80 +110,87 @@ internal enum ASN1DERReader {
110110
}
111111
}
112112

113+
let item: ASN1
114+
113115
switch tag {
114116
case .boolean:
115-
return .boolean(try itemBuffer.pop() != 0)
117+
item = .boolean(try itemBuffer.pop() != 0)
116118

117119
case .integer:
118-
return .integer(try parseInt(&itemBuffer))
120+
item = .integer(try parseInt(&itemBuffer))
119121

120122
case .bitString:
121123
let unusedBits = try itemBuffer.pop()
122124
let data = Data(try itemBuffer.popAll())
123-
return .bitString((data.count * 8) - Int(unusedBits), data)
125+
item = .bitString((data.count * 8) - Int(unusedBits), data)
124126

125127
case .octetString:
126-
return .octetString(Data(try itemBuffer.popAll()))
128+
item = .octetString(Data(try itemBuffer.popAll()))
127129

128130
case .null:
129-
return .null
131+
item = .null
130132

131133
case .objectIdentifier:
132-
return .objectIdentifier(try parseOID(&itemBuffer))
134+
item = .objectIdentifier(try parseOID(&itemBuffer))
133135

134136
case .real:
135-
return .real(try parseReal(&itemBuffer))
137+
item = .real(try parseReal(&itemBuffer))
136138

137139
case .utf8String:
138-
return .utf8String(try parseString(&itemBuffer, encoding: .utf8))
140+
item = .utf8String(try parseString(&itemBuffer, encoding: .utf8))
139141

140142
case .numericString:
141-
return .numericString(try parseString(&itemBuffer, encoding: .ascii))
143+
item = .numericString(try parseString(&itemBuffer, encoding: .ascii))
142144

143145
case .printableString:
144-
return .printableString(try parseString(&itemBuffer, encoding: .ascii))
146+
item = .printableString(try parseString(&itemBuffer, encoding: .ascii))
145147

146148
case .teletexString:
147-
return .teletexString(try parseString(&itemBuffer, encoding: .ascii))
149+
item = .teletexString(try parseString(&itemBuffer, encoding: .ascii))
148150

149151
case .videotexString:
150-
return .videotexString(try parseString(&itemBuffer, encoding: .ascii))
152+
item = .videotexString(try parseString(&itemBuffer, encoding: .ascii))
151153

152154
case .ia5String:
153-
return .ia5String(try parseString(&itemBuffer, encoding: .ascii))
155+
item = .ia5String(try parseString(&itemBuffer, encoding: .ascii))
154156

155157
case .utcTime:
156-
return .utcTime(try parseTime(&itemBuffer, formatter: utcFormatter))
158+
item = .utcTime(try parseTime(&itemBuffer, formatter: utcFormatter))
157159

158160
case .generalizedTime:
159-
return .generalizedTime(try parseTime(&itemBuffer, formatter: generalizedFormatter))
161+
item = .generalizedTime(try parseTime(&itemBuffer, formatter: generalizedFormatter))
160162

161163
case .graphicString:
162-
return .graphicString(try parseString(&itemBuffer, encoding: .ascii))
164+
item = .graphicString(try parseString(&itemBuffer, encoding: .ascii))
163165

164166
case .visibleString:
165-
return .visibleString(try parseString(&itemBuffer, encoding: .ascii))
167+
item = .visibleString(try parseString(&itemBuffer, encoding: .ascii))
166168

167169
case .generalString:
168-
return .generalString(try parseString(&itemBuffer, encoding: .ascii))
170+
item = .generalString(try parseString(&itemBuffer, encoding: .ascii))
169171

170172
case .universalString:
171-
return .universalString(try parseString(&itemBuffer, encoding: .ascii))
173+
item = .universalString(try parseString(&itemBuffer, encoding: .ascii))
172174

173175
case .characterString:
174-
return .characterString(try parseString(&itemBuffer, encoding: .ascii))
176+
item = .characterString(try parseString(&itemBuffer, encoding: .ascii))
175177

176178
case .bmpString:
177-
return .bmpString(try parseString(&itemBuffer, encoding: .ascii))
179+
item = .bmpString(try parseString(&itemBuffer, encoding: .ascii))
178180

179181
case .sequence, .set:
180182
throw ASN1Serialization.Error.nonConstructedCollection
181183

182184
case .objectDescriptor, .external, .enumerated, .embedded, .relativeOID:
183185
// Default to saving tagged version
184-
return .tagged(tag.rawValue, Data(try itemBuffer.popAll()))
186+
item = .tagged(tag.rawValue, Data(try itemBuffer.popAll()))
187+
}
188+
189+
if !itemBuffer.isEmpty {
190+
throw ASN1Serialization.Error.invalidTaggedItem
185191
}
186192

193+
return item
187194
}
188195

189196
private static func parseTime(
@@ -213,7 +220,7 @@ internal enum ASN1DERReader {
213220
private static func parseReal(_ buffer: inout UnsafeBufferPointer<UInt8>) throws -> Decimal {
214221
let lead = try buffer.pop()
215222
if lead & 0x40 == 0x40 {
216-
return lead & 0x1 == 0 ? Decimal(Double.infinity) : Decimal(-Double.infinity)
223+
throw ASN1Serialization.Error.unsupportedReal
217224
}
218225
else if lead & 0xC0 == 0 {
219226
let bytes = try buffer.popAll()
@@ -303,7 +310,20 @@ internal enum ASN1DERReader {
303310
}
304311

305312
for _ in 0 ..< numBytes {
306-
length = (length * 0x100) + Int(try buffer.pop())
313+
314+
let newLength = (length &* 0x100) &+ Int(try buffer.pop())
315+
316+
// Check for overflow
317+
if newLength < length {
318+
throw ASN1Serialization.Error.lengthOverflow
319+
}
320+
321+
// Check avaiable data
322+
if newLength > buffer.count {
323+
throw ASN1Serialization.Error.unexpectedEOF
324+
}
325+
326+
length = newLength
307327
}
308328

309329
return length

Sources/PotentASN1/ASN1Serialization.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ public enum ASN1Serialization {
3131
case invalidGeneralizedTime
3232
/// Unsupported REAL type.
3333
case unsupportedReal
34-
/// Encoded value length could not be stored.
34+
/// Encoded value length could not be stored or exceeds available data.
3535
case lengthOverflow
3636
/// Number of fields in OID is invalid
3737
case invalidObjectIdentifierLength
38+
/// Tagged item was encoded incorrectly
39+
case invalidTaggedItem
3840
}
3941

4042
/// Read ASN.1/DER encoded data as a collection of ``ASN1`` values.

Tests/ASN1Tests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,31 @@ class ASN1Tests: XCTestCase {
249249
XCTAssertEqual(offset(5), -40920)
250250
}
251251

252+
func testRandomData() throws {
253+
254+
struct TestStruct: Codable, SchemaSpecified {
255+
var id: OID
256+
var data: Data
257+
258+
static var asn1Schema: Schema {
259+
.sequence([
260+
"id": .objectIdentifier(),
261+
"data": .octetString(size: .is(16))
262+
])
263+
}
264+
}
265+
266+
let encoded = try ASN1.Encoder.encode(TestStruct(id: [1, 2, 3, 4, 5], data: Data(count: 16)))
267+
268+
for _ in 0 ..< 10000 {
269+
270+
var random = Data(capacity: encoded.count)
271+
for _ in 0 ..< encoded.count {
272+
random.append(UInt8.random(in: 0 ..< .max))
273+
}
274+
275+
XCTAssertThrowsError(try ASN1.Decoder.decode(TestStruct.self, from: random))
276+
}
277+
}
278+
252279
}

Tests/AnyValueTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ class AnyValueTests: XCTestCase {
270270
XCTAssertEqual(try AnyValue.wrapped(uuid), .uuid(uuid))
271271
let date = Date()
272272
XCTAssertEqual(try AnyValue.wrapped(date), .date(date))
273-
XCTAssertEqual(try AnyValue.wrapped([1, "test", true]), .array([1, "test", true]))
273+
XCTAssertEqual(try AnyValue.wrapped([1, "test", true] as [Any]), .array([1, "test", true]))
274274

275275
// Unorderd dictionaries
276276
XCTAssertEqual(
@@ -283,9 +283,9 @@ class AnyValueTests: XCTestCase {
283283
.dictionaryValue.map { val in Dictionary(uniqueKeysWithValues: val.map { ($0, $1) }) },
284284
[.int(1): .string("a"), .int(2): .string("b"), .int(3): .string("c")]
285285
)
286-
XCTAssertEqual(try AnyValue.wrapped(["a": 1, "b": "test", "c": true] as OrderedDictionary),
286+
XCTAssertEqual(try AnyValue.wrapped(["a": 1, "b": "test", "c": true] as OrderedDictionary<AnyValue, AnyValue>),
287287
.dictionary(["a": 1, "b": "test", "c": true]))
288-
XCTAssertEqual(try AnyValue.wrapped([1: 1, 2: "test", 3: true] as OrderedDictionary),
288+
XCTAssertEqual(try AnyValue.wrapped([1: 1, 2: "test", 3: true] as OrderedDictionary<AnyValue, AnyValue>),
289289
.dictionary([1: 1, 2: "test", 3: true]))
290290

291291
// Passthrough

Tests/CBORTests.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,24 @@ class CBORTests: XCTestCase {
9292
XCTAssertEqual(map, try CBORSerialization.cbor(from: Data([0xA3, 0x61, 0x63, 0x01, 0x61, 0x61, 0x02, 0x61, 0x62, 0x03])))
9393
}
9494

95+
func testRandomData() throws {
96+
97+
struct TestStruct: Codable {
98+
var id: [Int]
99+
var data: Data
100+
}
101+
102+
let encoded = try CBOR.Encoder.default.encode(TestStruct(id: [1, 2, 3, 4, 5], data: Data(count: 16)))
103+
104+
for _ in 0 ..< 10000 {
105+
106+
var random = Data(capacity: encoded.count)
107+
for _ in 0 ..< encoded.count {
108+
random.append(UInt8.random(in: 0 ..< .max))
109+
}
110+
111+
XCTAssertThrowsError(try CBOR.Decoder.default.decode(TestStruct.self, from: random))
112+
}
113+
}
114+
95115
}

0 commit comments

Comments
 (0)