diff options
Diffstat (limited to 'DomainDig/SSLCheckService.swift')
| -rw-r--r-- | DomainDig/SSLCheckService.swift | 73 |
1 files changed, 60 insertions, 13 deletions
diff --git a/DomainDig/SSLCheckService.swift b/DomainDig/SSLCheckService.swift index e180d4e..73c00bd 100644 --- a/DomainDig/SSLCheckService.swift +++ b/DomainDig/SSLCheckService.swift @@ -65,24 +65,28 @@ struct SSLCheckService { let validFrom: Date let validUntil: Date - if let notBefore = SecCertificateCopyNotValidBeforeDate(leaf) as Date? { - validFrom = notBefore - } else { - validFrom = Date.distantPast - } + let derData = SecCertificateCopyData(leaf) as Data + let parsed = DERCertificateParser.parse(derData) + + if #available(iOS 18.0, *) { + if let notBefore = SecCertificateCopyNotValidBeforeDate(leaf) as Date? { + validFrom = notBefore + } else { + validFrom = parsed.notBefore ?? Date.distantPast + } - if let notAfter = SecCertificateCopyNotValidAfterDate(leaf) as Date? { - validUntil = notAfter + if let notAfter = SecCertificateCopyNotValidAfterDate(leaf) as Date? { + validUntil = notAfter + } else { + validUntil = parsed.notAfter ?? Date.distantFuture + } } else { - validUntil = Date.distantFuture + validFrom = parsed.notBefore ?? Date.distantPast + validUntil = parsed.notAfter ?? Date.distantFuture } let daysUntilExpiry = Calendar.current.dateComponents([.day], from: Date(), to: validUntil).day ?? 0 - // Parse the DER-encoded certificate to extract SANs and Issuer - let derData = SecCertificateCopyData(leaf) as Data - let parsed = DERCertificateParser.parse(derData) - let sans = parsed.subjectAltNames.isEmpty ? [commonName] : parsed.subjectAltNames // Issuer: prefer parsed issuer, fall back to chain's next cert summary @@ -116,6 +120,7 @@ struct SSLCheckService { chain: chain ) } + } fileprivate struct TLSMetadata { @@ -133,6 +138,8 @@ private enum DERCertificateParser { struct Result { var issuerCommonName: String? var subjectAltNames: [String] = [] + var notBefore: Date? + var notAfter: Date? } static func parse(_ data: Data) -> Result { @@ -171,8 +178,11 @@ private enum DERCertificateParser { offset = issuerSeq.contentStart + issuerSeq.length } - // Skip validity + // Validity if let validity = readTagAndLength(bytes, offset: offset) { + let (notBefore, notAfter) = extractValidity(bytes, sequenceStart: validity.contentStart, length: validity.length) + result.notBefore = notBefore + result.notAfter = notAfter offset = validity.contentStart + validity.length } @@ -287,6 +297,43 @@ private enum DERCertificateParser { return sans } + private static func extractValidity(_ bytes: [UInt8], sequenceStart: Int, length: Int) -> (Date?, Date?) { + let end = sequenceStart + length + var position = sequenceStart + var dates: [Date] = [] + + while position < end, dates.count < 2 { + guard let timeTL = readTagAndLength(bytes, offset: position) else { break } + let raw = String(bytes: bytes[timeTL.contentStart..<timeTL.contentStart + timeTL.length], encoding: .ascii) + if let raw { + dates.append(parseASN1Time(raw)) + } + position = timeTL.contentStart + timeTL.length + } + + let notBefore = dates.indices.contains(0) ? dates[0] : nil + let notAfter = dates.indices.contains(1) ? dates[1] : nil + return (notBefore, notAfter) + } + + private static func parseASN1Time(_ string: String) -> Date { + let utcFormatter = DateFormatter() + utcFormatter.locale = Locale(identifier: "en_US_POSIX") + utcFormatter.timeZone = TimeZone(secondsFromGMT: 0) + utcFormatter.dateFormat = "yyMMddHHmmss'Z'" + + if let date = utcFormatter.date(from: string) { + return date + } + + let generalizedFormatter = DateFormatter() + generalizedFormatter.locale = Locale(identifier: "en_US_POSIX") + generalizedFormatter.timeZone = TimeZone(secondsFromGMT: 0) + generalizedFormatter.dateFormat = "yyyyMMddHHmmss'Z'" + + return generalizedFormatter.date(from: string) ?? Date.distantFuture + } + private struct TLV { let contentStart: Int let length: Int |
