krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

v1.7.2: 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}