summaryrefslogtreecommitdiff
path: root/DomainDig/SSLCheckService.swift
diff options
context:
space:
mode:
Diffstat (limited to 'DomainDig/SSLCheckService.swift')
-rw-r--r--DomainDig/SSLCheckService.swift73
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