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 30 public static func decode(data: Data) throws -> [ASN1Object] { 31 var iterator = data.makeIterator() 32 return try parse(iterator: &iterator) 33 } 34 35 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 162 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 184 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 230 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 253 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