1 // swiftlint:disable all
2 //
3 // ASN1DERDecoder.swift
4 //
5 // Copyright © 2017 Filippo Maguolo.
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy
8 // of this software and associated documentation files (the "Software"), to deal
9 // in the Software without restriction, including without limitation the rights
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 // copies of the Software, and to permit persons to whom the Software is
12 // furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included in all
15 // copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 // SOFTWARE.
24
25
26 import Foundation
27
28 public class ASN1DERDecoder {
29
decodenull30 public static func decode(data: Data) throws -> [ASN1Object] {
31 var iterator = data.makeIterator()
32 return try parse(iterator: &iterator)
33 }
34
parsenull35 private static func parse(iterator: inout Data.Iterator) throws -> [ASN1Object] {
36
37 var result: [ASN1Object] = []
38
39 while let nextValue = iterator.next() {
40
41 let asn1obj = ASN1Object()
42 asn1obj.identifier = ASN1Identifier(rawValue: nextValue)
43
44 if asn1obj.identifier!.isConstructed() {
45
46 let contentData = try loadSubContent(iterator: &iterator)
47
48 if contentData.isEmpty {
49 asn1obj.sub = try parse(iterator: &iterator)
50 } else {
51 var subIterator = contentData.makeIterator()
52 asn1obj.sub = try parse(iterator: &subIterator)
53 }
54
55 asn1obj.value = nil
56
57 asn1obj.rawValue = Data(contentData)
58
59 for item in asn1obj.sub! {
60 item.parent = asn1obj
61 }
62 } else {
63
64 if asn1obj.identifier!.typeClass() == .universal {
65
66 var contentData = try loadSubContent(iterator: &iterator)
67
68 asn1obj.rawValue = Data(contentData)
69
70 // decode the content data with come more convenient format
71
72 switch asn1obj.identifier!.tagNumber() {
73
74 case .endOfContent:
75 return result
76
77 case .boolean:
78 if let value = contentData.first {
79 asn1obj.value = value > 0 ? true : false
80
81 }
82
83 case .integer:
84 while contentData.first == 0 {
85 contentData.remove(at: 0) // remove not significant digit
86 }
87 asn1obj.value = contentData
88
89 case .null:
90 asn1obj.value = nil
91
92 case .objectIdentifier:
93 asn1obj.value = decodeOid(contentData: &contentData)
94
95 case .utf8String,
96 .printableString,
97 .numericString,
98 .generalString,
99 .universalString,
100 .characterString,
101 .t61String:
102
103 asn1obj.value = String(data: contentData, encoding: .utf8)
104
105 case .bmpString:
106 asn1obj.value = String(data: contentData, encoding: .unicode)
107
108 case .visibleString,
109 .ia5String:
110
111 asn1obj.value = String(data: contentData, encoding: .ascii)
112
113 case .utcTime:
114 asn1obj.value = dateFormatter(contentData: &contentData,
115 formats: ["yyMMddHHmmssZ", "yyMMddHHmmZ"])
116
117 case .generalizedTime:
118 asn1obj.value = dateFormatter(contentData: &contentData,
119 formats: ["yyyyMMddHHmmssZ"])
120
121 case .bitString:
122 if contentData.count > 0 {
123 _ = contentData.remove(at: 0) // unused bits
124 }
125 asn1obj.value = contentData
126
127 case .octetString:
128 do {
129 var subIterator = contentData.makeIterator()
130 asn1obj.sub = try parse(iterator: &subIterator)
131 } catch {
132 if let str = String(data: contentData, encoding: .utf8) {
133 asn1obj.value = str
134 } else {
135 asn1obj.value = contentData
136 }
137 }
138
139 default:
140 print("unsupported tag: \(asn1obj.identifier!.tagNumber())")
141 asn1obj.value = contentData
142 }
143 } else {
144 // custom/private tag
145
146 let contentData = try loadSubContent(iterator: &iterator)
147 asn1obj.rawValue = Data(contentData)
148
149 if let str = String(data: contentData, encoding: .utf8) {
150 asn1obj.value = str
151 } else {
152 asn1obj.value = contentData
153 }
154 }
155 }
156 result.append(asn1obj)
157 }
158 return result
159 }
160
161 // Decode DER OID bytes to String with dot notation
decodeOidnull162 static func decodeOid(contentData: inout Data) -> String? {
163 if contentData.isEmpty {
164 return nil
165 }
166
167 var oid: String = ""
168
169 let first = Int(contentData.remove(at: 0))
170 oid.append("\(first / 40).\(first % 40)")
171
172 var t = 0
173 while contentData.count > 0 {
174 let n = Int(contentData.remove(at: 0))
175 t = (t << 7) | (n & 0x7F)
176 if (n & 0x80) == 0 {
177 oid.append(".\(t)")
178 t = 0
179 }
180 }
181 return oid
182 }
183
dateFormatternull184 private static func dateFormatter(contentData: inout Data, formats: [String]) -> Date? {
185 guard let str = String(data: contentData, encoding: .utf8) else { return nil }
186 for format in formats {
187 let fmt = DateFormatter()
188 fmt.locale = Locale(identifier: "en_US_POSIX")
189 fmt.dateFormat = format
190 if let dt = fmt.date(from: str) {
191 return dt
192 }
193 }
194 return nil
195 }
196 }
197
198 enum ASN1Error: Error {
199 case parseError
200 case outOfBuffer
201 }
202
203 extension Data {
204 public var uint64Value: UInt64? {
205 guard count <= 8, !isEmpty else { // check if suitable for UInt64
206 return nil
207 }
208
209 var value: UInt64 = 0
210 for (index, byte) in self.enumerated() {
211 value += UInt64(byte) << UInt64(8*(count-index-1))
212 }
213 return value
214 }
215 }
216
217 extension Data {
218 public var sequenceContent: Data {
219 var iterator = self.makeIterator()
220 _ = iterator.next()
221 do {
222 return try loadSubContent(iterator: &iterator)
223 } catch {
224 return self
225 }
226 }
227 }
228
229 // Decode the number of bytes of the content
getContentLengthnull230 private func getContentLength(iterator: inout Data.Iterator) -> UInt64 {
231 let first = iterator.next()
232
233 guard first != nil else {
234 return 0
235 }
236
237 if (first! & 0x80) != 0 { // long
238 let octetsToRead = first! - 0x80
239 var data = Data()
240 for _ in 0..<octetsToRead {
241 if let n = iterator.next() {
242 data.append(n)
243 }
244 }
245
246 return data.uint64Value ?? 0
247
248 } else { // short
249 return UInt64(first!)
250 }
251 }
252
loadSubContentnull253 private func loadSubContent(iterator: inout Data.Iterator) throws -> Data {
254
255 let len = getContentLength(iterator: &iterator)
256
257 guard len < Int.max else {
258 return Data()
259 }
260
261 var byteArray: [UInt8] = []
262
263 for _ in 0..<Int(len) {
264 if let n = iterator.next() {
265 byteArray.append(n)
266 } else {
267 throw ASN1Error.outOfBuffer
268 }
269 }
270 return Data(byteArray)
271 }
272 // swiftlint:enable all
273