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