krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v1.7.1: DomainDig/Models.swift · raw
1import Foundation
2
3// MARK: - DNS Models
4
5enum DNSRecordType: String, CaseIterable, Codable {
6 case A
7 case AAAA
8 case MX
9 case NS
10 case TXT
11 case CNAME
12 case SOA
13 case SRV
14 case CAA
15 case DS
16
17 var queryType: Int {
18 switch self {
19 case .A: return 1
20 case .AAAA: return 28
21 case .MX: return 15
22 case .NS: return 2
23 case .TXT: return 16
24 case .CNAME: return 5
25 case .SOA: return 6
26 case .SRV: return 33
27 case .CAA: return 257
28 case .DS: return 43
29 }
30 }
31
32 var usesRawDataValue: Bool {
33 switch self {
34 case .TXT, .SOA, .DS:
35 return true
36 default:
37 return false
38 }
39 }
40}
41
42struct DNSRecord: Identifiable, Codable {
43 var id = UUID()
44 let value: String
45 let ttl: Int
46}
47
48struct DNSSection: Identifiable, Codable {
49 var id = UUID()
50 let recordType: DNSRecordType
51 var records: [DNSRecord]
52 var wildcardRecords: [DNSRecord] = []
53 var dnssecSigned: Bool?
54 var error: String?
55}
56
57// MARK: - SSL Models
58
59struct SSLCertificateInfo: Codable {
60 struct CertChainEntry: Codable {
61 let subject: String
62 let issuer: String
63 }
64
65 let commonName: String
66 let subjectAltNames: [String]
67 let issuer: String
68 let validFrom: Date
69 let validUntil: Date
70 let daysUntilExpiry: Int
71 let chainDepth: Int
72 let tlsVersion: String?
73 let cipherSuite: String?
74 let chain: [CertChainEntry]
75
76 init(
77 commonName: String,
78 subjectAltNames: [String],
79 issuer: String,
80 validFrom: Date,
81 validUntil: Date,
82 daysUntilExpiry: Int,
83 chainDepth: Int,
84 tlsVersion: String? = nil,
85 cipherSuite: String? = nil,
86 chain: [CertChainEntry] = []
87 ) {
88 self.commonName = commonName
89 self.subjectAltNames = subjectAltNames
90 self.issuer = issuer
91 self.validFrom = validFrom
92 self.validUntil = validUntil
93 self.daysUntilExpiry = daysUntilExpiry
94 self.chainDepth = chainDepth
95 self.tlsVersion = tlsVersion
96 self.cipherSuite = cipherSuite
97 self.chain = chain
98 }
99
100 init(from decoder: Decoder) throws {
101 let container = try decoder.container(keyedBy: CodingKeys.self)
102 commonName = try container.decode(String.self, forKey: .commonName)
103 subjectAltNames = try container.decode([String].self, forKey: .subjectAltNames)
104 issuer = try container.decode(String.self, forKey: .issuer)
105 validFrom = try container.decode(Date.self, forKey: .validFrom)
106 validUntil = try container.decode(Date.self, forKey: .validUntil)
107 daysUntilExpiry = try container.decode(Int.self, forKey: .daysUntilExpiry)
108 chainDepth = try container.decode(Int.self, forKey: .chainDepth)
109 tlsVersion = try container.decodeIfPresent(String.self, forKey: .tlsVersion)
110 cipherSuite = try container.decodeIfPresent(String.self, forKey: .cipherSuite)
111 chain = try container.decodeIfPresent([CertChainEntry].self, forKey: .chain) ?? []
112 }
113}
114
115// MARK: - HTTP Headers Models
116
117struct HTTPHeader: Identifiable, Codable {
118 var id = UUID()
119 let name: String
120 let value: String
121
122 static let securityHeaders: Set<String> = [
123 "strict-transport-security",
124 "x-frame-options",
125 "x-content-type-options",
126 "content-security-policy",
127 "referrer-policy"
128 ]
129
130 var isSecurityHeader: Bool {
131 Self.securityHeaders.contains(name.lowercased())
132 }
133}
134
135// MARK: - Reachability Models
136
137struct PortReachability: Identifiable, Codable {
138 var id = UUID()
139 let port: UInt16
140 let reachable: Bool
141 let latencyMs: Int?
142}
143
144// MARK: - IP Geolocation Models
145
146struct IPGeolocation: Codable {
147 let ip: String
148 let city: String?
149 let region: String?
150 let country_name: String?
151 let org: String?
152 let latitude: Double?
153 let longitude: Double?
154}
155
156// MARK: - Email Security Models
157
158struct EmailSecurityResult: Codable {
159 let spf: EmailSecurityRecord
160 let dmarc: EmailSecurityRecord
161 let dkim: EmailSecurityRecord
162 let bimi: EmailSecurityRecord
163 let mtaSts: MTASTSResult?
164
165 init(
166 spf: EmailSecurityRecord,
167 dmarc: EmailSecurityRecord,
168 dkim: EmailSecurityRecord,
169 bimi: EmailSecurityRecord = EmailSecurityRecord(found: false, value: nil),
170 mtaSts: MTASTSResult? = nil
171 ) {
172 self.spf = spf
173 self.dmarc = dmarc
174 self.dkim = dkim
175 self.bimi = bimi
176 self.mtaSts = mtaSts
177 }
178
179 init(from decoder: Decoder) throws {
180 let container = try decoder.container(keyedBy: CodingKeys.self)
181 spf = try container.decode(EmailSecurityRecord.self, forKey: .spf)
182 dmarc = try container.decode(EmailSecurityRecord.self, forKey: .dmarc)
183 dkim = try container.decode(EmailSecurityRecord.self, forKey: .dkim)
184 bimi = try container.decodeIfPresent(EmailSecurityRecord.self, forKey: .bimi)
185 ?? EmailSecurityRecord(found: false, value: nil)
186 mtaSts = try container.decodeIfPresent(MTASTSResult.self, forKey: .mtaSts)
187 }
188}
189
190struct EmailSecurityRecord: Codable {
191 let found: Bool
192 let value: String?
193 let matchedSelector: String?
194
195 init(found: Bool, value: String?, matchedSelector: String? = nil) {
196 self.found = found
197 self.value = value
198 self.matchedSelector = matchedSelector
199 }
200
201 init(from decoder: Decoder) throws {
202 let container = try decoder.container(keyedBy: CodingKeys.self)
203 found = try container.decode(Bool.self, forKey: .found)
204 value = try container.decodeIfPresent(String.self, forKey: .value)
205 matchedSelector = try container.decodeIfPresent(String.self, forKey: .matchedSelector)
206 }
207}
208
209struct MTASTSResult: Codable {
210 let txtFound: Bool
211 let policyMode: String?
212}
213
214// MARK: - Redirect Chain Models
215
216struct RedirectHop: Identifiable, Codable {
217 var id = UUID()
218 let stepNumber: Int
219 let statusCode: Int
220 let url: String
221 let isFinal: Bool
222}
223
224// MARK: - Port Scan Models
225
226struct PortScanResult: Identifiable, Codable {
227 var id = UUID()
228 let port: UInt16
229 let service: String
230 let open: Bool
231 var banner: String?
232
233 nonisolated init(port: UInt16, service: String, open: Bool, banner: String? = nil) {
234 self.port = port
235 self.service = service
236 self.open = open
237 self.banner = banner
238 }
239
240 init(from decoder: Decoder) throws {
241 let container = try decoder.container(keyedBy: CodingKeys.self)
242 id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
243 port = try container.decode(UInt16.self, forKey: .port)
244 service = try container.decode(String.self, forKey: .service)
245 open = try container.decode(Bool.self, forKey: .open)
246 banner = try container.decodeIfPresent(String.self, forKey: .banner)
247 }
248}
249
250// MARK: - History Models
251
252struct HistoryEntry: Identifiable, Codable {
253 var id = UUID()
254 let domain: String
255 let timestamp: Date
256 let dnsSections: [DNSSection]
257 let sslInfo: SSLCertificateInfo?
258 let httpHeaders: [HTTPHeader]
259 let reachabilityResults: [PortReachability]
260 let ipGeolocation: IPGeolocation?
261 var emailSecurity: EmailSecurityResult?
262 var mtaSts: MTASTSResult?
263 var ptrRecord: String?
264 var redirectChain: [RedirectHop]
265 var portScanResults: [PortScanResult]
266 var hstsPreloaded: Bool?
267
268 init(domain: String, timestamp: Date, dnsSections: [DNSSection],
269 sslInfo: SSLCertificateInfo?, httpHeaders: [HTTPHeader],
270 reachabilityResults: [PortReachability], ipGeolocation: IPGeolocation?,
271 emailSecurity: EmailSecurityResult? = nil, mtaSts: MTASTSResult? = nil, ptrRecord: String? = nil,
272 redirectChain: [RedirectHop] = [], portScanResults: [PortScanResult] = [],
273 hstsPreloaded: Bool? = nil) {
274 self.domain = domain
275 self.timestamp = timestamp
276 self.dnsSections = dnsSections
277 self.sslInfo = sslInfo
278 self.httpHeaders = httpHeaders
279 self.reachabilityResults = reachabilityResults
280 self.ipGeolocation = ipGeolocation
281 self.emailSecurity = emailSecurity
282 self.mtaSts = mtaSts ?? emailSecurity?.mtaSts
283 self.ptrRecord = ptrRecord
284 self.redirectChain = redirectChain
285 self.portScanResults = portScanResults
286 self.hstsPreloaded = hstsPreloaded
287 }
288
289 init(from decoder: Decoder) throws {
290 let container = try decoder.container(keyedBy: CodingKeys.self)
291 id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
292 domain = try container.decode(String.self, forKey: .domain)
293 timestamp = try container.decode(Date.self, forKey: .timestamp)
294 dnsSections = try container.decode([DNSSection].self, forKey: .dnsSections)
295 sslInfo = try container.decodeIfPresent(SSLCertificateInfo.self, forKey: .sslInfo)
296 httpHeaders = try container.decode([HTTPHeader].self, forKey: .httpHeaders)
297 reachabilityResults = try container.decode([PortReachability].self, forKey: .reachabilityResults)
298 ipGeolocation = try container.decodeIfPresent(IPGeolocation.self, forKey: .ipGeolocation)
299 emailSecurity = try container.decodeIfPresent(EmailSecurityResult.self, forKey: .emailSecurity)
300 mtaSts = try container.decodeIfPresent(MTASTSResult.self, forKey: .mtaSts) ?? emailSecurity?.mtaSts
301 ptrRecord = try container.decodeIfPresent(String.self, forKey: .ptrRecord)
302 redirectChain = try container.decodeIfPresent([RedirectHop].self, forKey: .redirectChain) ?? []
303 portScanResults = try container.decodeIfPresent([PortScanResult].self, forKey: .portScanResults) ?? []
304 hstsPreloaded = try container.decodeIfPresent(Bool.self, forKey: .hstsPreloaded)
305 }
306}
307
308// MARK: - Cloudflare DNS-over-HTTPS Response
309
310struct CloudflareDNSResponse: Decodable {
311 let Status: Int
312 let AD: Bool?
313 let Answer: [CloudflareDNSAnswer]?
314
315 struct CloudflareDNSAnswer: Decodable {
316 let name: String
317 let type: Int
318 let TTL: Int
319 let data: String
320 }
321}