krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v1.5.0: 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}
232
233// MARK: - History Models
234
235struct HistoryEntry: Identifiable, Codable {
236 var id = UUID()
237 let domain: String
238 let timestamp: Date
239 let dnsSections: [DNSSection]
240 let sslInfo: SSLCertificateInfo?
241 let httpHeaders: [HTTPHeader]
242 let reachabilityResults: [PortReachability]
243 let ipGeolocation: IPGeolocation?
244 var emailSecurity: EmailSecurityResult?
245 var mtaSts: MTASTSResult?
246 var ptrRecord: String?
247 var redirectChain: [RedirectHop]
248 var portScanResults: [PortScanResult]
249 var hstsPreloaded: Bool?
250
251 init(domain: String, timestamp: Date, dnsSections: [DNSSection],
252 sslInfo: SSLCertificateInfo?, httpHeaders: [HTTPHeader],
253 reachabilityResults: [PortReachability], ipGeolocation: IPGeolocation?,
254 emailSecurity: EmailSecurityResult? = nil, mtaSts: MTASTSResult? = nil, ptrRecord: String? = nil,
255 redirectChain: [RedirectHop] = [], portScanResults: [PortScanResult] = [],
256 hstsPreloaded: Bool? = nil) {
257 self.domain = domain
258 self.timestamp = timestamp
259 self.dnsSections = dnsSections
260 self.sslInfo = sslInfo
261 self.httpHeaders = httpHeaders
262 self.reachabilityResults = reachabilityResults
263 self.ipGeolocation = ipGeolocation
264 self.emailSecurity = emailSecurity
265 self.mtaSts = mtaSts ?? emailSecurity?.mtaSts
266 self.ptrRecord = ptrRecord
267 self.redirectChain = redirectChain
268 self.portScanResults = portScanResults
269 self.hstsPreloaded = hstsPreloaded
270 }
271
272 init(from decoder: Decoder) throws {
273 let container = try decoder.container(keyedBy: CodingKeys.self)
274 id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
275 domain = try container.decode(String.self, forKey: .domain)
276 timestamp = try container.decode(Date.self, forKey: .timestamp)
277 dnsSections = try container.decode([DNSSection].self, forKey: .dnsSections)
278 sslInfo = try container.decodeIfPresent(SSLCertificateInfo.self, forKey: .sslInfo)
279 httpHeaders = try container.decode([HTTPHeader].self, forKey: .httpHeaders)
280 reachabilityResults = try container.decode([PortReachability].self, forKey: .reachabilityResults)
281 ipGeolocation = try container.decodeIfPresent(IPGeolocation.self, forKey: .ipGeolocation)
282 emailSecurity = try container.decodeIfPresent(EmailSecurityResult.self, forKey: .emailSecurity)
283 mtaSts = try container.decodeIfPresent(MTASTSResult.self, forKey: .mtaSts) ?? emailSecurity?.mtaSts
284 ptrRecord = try container.decodeIfPresent(String.self, forKey: .ptrRecord)
285 redirectChain = try container.decodeIfPresent([RedirectHop].self, forKey: .redirectChain) ?? []
286 portScanResults = try container.decodeIfPresent([PortScanResult].self, forKey: .portScanResults) ?? []
287 hstsPreloaded = try container.decodeIfPresent(Bool.self, forKey: .hstsPreloaded)
288 }
289}
290
291// MARK: - Cloudflare DNS-over-HTTPS Response
292
293struct CloudflareDNSResponse: Decodable {
294 let Status: Int
295 let Answer: [CloudflareDNSAnswer]?
296
297 struct CloudflareDNSAnswer: Decodable {
298 let name: String
299 let type: Int
300 let TTL: Int
301 let data: String
302 }
303}