mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-12-21 11:10:08 +01:00
✨ Check certificate expiry
This commit is contained in:
@@ -60,6 +60,10 @@
|
|||||||
036C39122E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
|
036C39122E5C8D42008DAEDF /* PackagistError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C390E2E5C8D3B008DAEDF /* PackagistError.swift */; };
|
||||||
036C39142E5CB822008DAEDF /* TestBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39132E5CB820008DAEDF /* TestBundle.swift */; };
|
036C39142E5CB822008DAEDF /* TestBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036C39132E5CB820008DAEDF /* TestBundle.swift */; };
|
||||||
036C3A212E5CBBAA008DAEDF /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; };
|
036C3A212E5CBBAA008DAEDF /* ValetConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */; };
|
||||||
|
0392CDE62EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
||||||
|
0392CDE72EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
||||||
|
0392CDE82EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
||||||
|
0392CDE92EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
||||||
0396160D2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
0396160D2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
||||||
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
||||||
0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
||||||
@@ -998,6 +1002,7 @@
|
|||||||
036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistP2Response.swift; sourceTree = "<group>"; };
|
036C39092E5C8CBD008DAEDF /* PackagistP2Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistP2Response.swift; sourceTree = "<group>"; };
|
||||||
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistError.swift; sourceTree = "<group>"; };
|
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistError.swift; sourceTree = "<group>"; };
|
||||||
036C39132E5CB820008DAEDF /* TestBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBundle.swift; sourceTree = "<group>"; };
|
036C39132E5CB820008DAEDF /* TestBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBundle.swift; sourceTree = "<group>"; };
|
||||||
|
0392CDE52EB23B8F009176DA /* CertificateValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateValidator.swift; sourceTree = "<group>"; };
|
||||||
0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggableEvent.swift; sourceTree = "<group>"; };
|
0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggableEvent.swift; sourceTree = "<group>"; };
|
||||||
039C29122E8AA15F007F5FAB /* ActiveApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveApi.swift; sourceTree = "<group>"; };
|
039C29122E8AA15F007F5FAB /* ActiveApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveApi.swift; sourceTree = "<group>"; };
|
||||||
039C29172E8AA311007F5FAB /* TestableApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApi.swift; sourceTree = "<group>"; };
|
039C29172E8AA311007F5FAB /* TestableApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApi.swift; sourceTree = "<group>"; };
|
||||||
@@ -2193,6 +2198,7 @@
|
|||||||
C4E4404527C56F4700D225E1 /* ValetSite.swift */,
|
C4E4404527C56F4700D225E1 /* ValetSite.swift */,
|
||||||
C41C02A827E61A65009F26CB /* FakeValetSite.swift */,
|
C41C02A827E61A65009F26CB /* FakeValetSite.swift */,
|
||||||
C4BB39382981AFC700F8E797 /* PhpVersionSource.swift */,
|
C4BB39382981AFC700F8E797 /* PhpVersionSource.swift */,
|
||||||
|
0392CDE52EB23B8F009176DA /* CertificateValidator.swift */,
|
||||||
);
|
);
|
||||||
path = Sites;
|
path = Sites;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2758,6 +2764,7 @@
|
|||||||
C456A0C62AA614BD0080144F /* PhpPreference.swift in Sources */,
|
C456A0C62AA614BD0080144F /* PhpPreference.swift in Sources */,
|
||||||
C4B585442770FE3900DA4FBE /* RealCommand.swift in Sources */,
|
C4B585442770FE3900DA4FBE /* RealCommand.swift in Sources */,
|
||||||
C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */,
|
C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */,
|
||||||
|
0392CDE62EB23B8F009176DA /* CertificateValidator.swift in Sources */,
|
||||||
C40C5C9C2846A40600E28255 /* Preset.swift in Sources */,
|
C40C5C9C2846A40600E28255 /* Preset.swift in Sources */,
|
||||||
C4B79EBC29CA38DB00A483EE /* BrewCommand.swift in Sources */,
|
C4B79EBC29CA38DB00A483EE /* BrewCommand.swift in Sources */,
|
||||||
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
|
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
|
||||||
@@ -2926,6 +2933,7 @@
|
|||||||
C471E83928F9BB650021E251 /* ValetSite.swift in Sources */,
|
C471E83928F9BB650021E251 /* ValetSite.swift in Sources */,
|
||||||
C471E83A28F9BB650021E251 /* FakeValetSite.swift in Sources */,
|
C471E83A28F9BB650021E251 /* FakeValetSite.swift in Sources */,
|
||||||
C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */,
|
C471E83C28F9BB650021E251 /* ValetDomainScanner.swift in Sources */,
|
||||||
|
0392CDE82EB23B8F009176DA /* CertificateValidator.swift in Sources */,
|
||||||
033D459A2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
|
033D459A2B0D4EC600070080 /* InstallPhpExtensionCommand.swift in Sources */,
|
||||||
C4E2E86928FC3002003B070C /* Utility.swift in Sources */,
|
C4E2E86928FC3002003B070C /* Utility.swift in Sources */,
|
||||||
C471E83D28F9BB650021E251 /* FakeDomainScanner.swift in Sources */,
|
C471E83D28F9BB650021E251 /* FakeDomainScanner.swift in Sources */,
|
||||||
@@ -3160,6 +3168,7 @@
|
|||||||
C489E0BE2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
|
C489E0BE2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
|
||||||
C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */,
|
C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */,
|
||||||
C471E8A628F9BB8F0021E251 /* App.swift in Sources */,
|
C471E8A628F9BB8F0021E251 /* App.swift in Sources */,
|
||||||
|
0392CDE72EB23B8F009176DA /* CertificateValidator.swift in Sources */,
|
||||||
C4513F912B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
|
C4513F912B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */,
|
||||||
C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */,
|
C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */,
|
||||||
C45B914C295607F400F4EC78 /* Service.swift in Sources */,
|
C45B914C295607F400F4EC78 /* Service.swift in Sources */,
|
||||||
@@ -3413,6 +3422,7 @@
|
|||||||
C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */,
|
C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */,
|
||||||
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */,
|
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */,
|
||||||
C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */,
|
C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */,
|
||||||
|
0392CDE92EB23B8F009176DA /* CertificateValidator.swift in Sources */,
|
||||||
C4821C5B2C2DEDE200357A68 /* AppMenu.swift in Sources */,
|
C4821C5B2C2DEDE200357A68 /* AppMenu.swift in Sources */,
|
||||||
C463E381284930EE00422731 /* PresetHelper.swift in Sources */,
|
C463E381284930EE00422731 /* PresetHelper.swift in Sources */,
|
||||||
C441CC572AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
|
C441CC572AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
|
||||||
|
|||||||
@@ -116,6 +116,11 @@
|
|||||||
value = ""
|
value = ""
|
||||||
isEnabled = "NO">
|
isEnabled = "NO">
|
||||||
</EnvironmentVariable>
|
</EnvironmentVariable>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "SKIP_UPDATE_CHECK"
|
||||||
|
value = ""
|
||||||
|
isEnabled = "YES">
|
||||||
|
</EnvironmentVariable>
|
||||||
</EnvironmentVariables>
|
</EnvironmentVariables>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
//
|
||||||
|
// CertificateValidator.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Assistant on 29/10/2025.
|
||||||
|
// Copyright © 2023 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Security
|
||||||
|
|
||||||
|
/**
|
||||||
|
A utility class for validating SSL certificates, including checking expiration dates.
|
||||||
|
*/
|
||||||
|
class CertificateValidator {
|
||||||
|
|
||||||
|
/// The dependency container for file system access
|
||||||
|
private let container: Container
|
||||||
|
|
||||||
|
init(_ container: Container) {
|
||||||
|
self.container = container
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if a certificate file exists and returns its expiration date.
|
||||||
|
- Parameter certificatePath: Path to the certificate file (supports ~ for home directory)
|
||||||
|
- Returns: A tuple containing (exists: Bool, expirationDate: Date?)
|
||||||
|
*/
|
||||||
|
func validateCertificate(at certificatePath: String) -> (exists: Bool, expirationDate: Date?) {
|
||||||
|
let exists = container.filesystem.fileExists(certificatePath)
|
||||||
|
|
||||||
|
guard exists else {
|
||||||
|
return (exists: false, expirationDate: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
let expirationDate = getCertificateExpirationDate(at: certificatePath)
|
||||||
|
return (exists: true, expirationDate: expirationDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Loads certificate data from a file path using the filesystem abstraction.
|
||||||
|
- Parameter path: The file path to the certificate
|
||||||
|
- Returns: Certificate data as CFData, or nil if loading fails
|
||||||
|
*/
|
||||||
|
private func loadCertificateData(from path: String) -> CFData? {
|
||||||
|
do {
|
||||||
|
let certificateString = try container.filesystem.getStringFromFile(path)
|
||||||
|
|
||||||
|
// Remove PEM headers and footers, and whitespace
|
||||||
|
let cleanedCertificate = certificateString
|
||||||
|
.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
|
||||||
|
.replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
|
||||||
|
.replacingOccurrences(of: "\n", with: "")
|
||||||
|
.replacingOccurrences(of: "\r", with: "")
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
guard let certificateData = Data(base64Encoded: cleanedCertificate) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificateData as CFData
|
||||||
|
} catch {
|
||||||
|
Log.err("Failed to read certificate file at \(path): \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Gets detailed information about a certificate.
|
||||||
|
- Parameter certificatePath: Path to the certificate file
|
||||||
|
- Returns: A dictionary containing certificate details, or nil if the certificate couldn't be read
|
||||||
|
*/
|
||||||
|
func getCertificateInfo(at certificatePath: String) -> [String: Any]? {
|
||||||
|
guard let certificateData = loadCertificateData(from: certificatePath),
|
||||||
|
let certificate = SecCertificateCreateWithData(nil, certificateData) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let certDict = SecCertificateCopyValues(certificate, nil, nil) as? [String: Any] else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var info: [String: Any] = [:]
|
||||||
|
|
||||||
|
// Extract common name
|
||||||
|
if let subjectDict = certDict[kSecOIDX509V1SubjectName as String] as? [String: Any],
|
||||||
|
let subjectArray = subjectDict[kSecPropertyKeyValue as String] as? [[String: Any]] {
|
||||||
|
for item in subjectArray {
|
||||||
|
if let label = item[kSecPropertyKeyLabel as String] as? String,
|
||||||
|
label == "Common Name",
|
||||||
|
let value = item[kSecPropertyKeyValue as String] as? String {
|
||||||
|
info["commonName"] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract expiration date
|
||||||
|
if let validityDict = certDict[kSecOIDX509V1ValidityNotAfter as String] as? [String: Any],
|
||||||
|
let validityValue = validityDict[kSecPropertyKeyValue as String] as? NSNumber {
|
||||||
|
let expirationDate = Date(timeIntervalSinceReferenceDate: validityValue.doubleValue)
|
||||||
|
info["expirationDate"] = expirationDate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract issue date
|
||||||
|
if let validityDict = certDict[kSecOIDX509V1ValidityNotBefore as String] as? [String: Any],
|
||||||
|
let validityValue = validityDict[kSecPropertyKeyValue as String] as? NSNumber {
|
||||||
|
let issueDate = Date(timeIntervalSinceReferenceDate: validityValue.doubleValue)
|
||||||
|
info["issueDate"] = issueDate
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Gets the expiration date of a certificate.
|
||||||
|
- Parameter certificatePath: Path to the certificate file
|
||||||
|
- Returns: The expiration date, or nil if the certificate couldn't be read
|
||||||
|
*/
|
||||||
|
func getCertificateExpirationDate(at certificatePath: String) -> Date? {
|
||||||
|
guard let info = getCertificateInfo(at: certificatePath) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return info["expirationDate"] as? Date
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,9 @@ class ValetSite: ValetListable {
|
|||||||
/// Whether the site has been secured.
|
/// Whether the site has been secured.
|
||||||
var secured: Bool!
|
var secured: Bool!
|
||||||
|
|
||||||
|
/// When the certificate expires.
|
||||||
|
var expiryDate: Date?
|
||||||
|
|
||||||
/// What driver is currently in use. If not detected, defaults to nil.
|
/// What driver is currently in use. If not detected, defaults to nil.
|
||||||
var driver: String?
|
var driver: String?
|
||||||
|
|
||||||
@@ -122,13 +125,23 @@ class ValetSite: ValetListable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Checks if a certificate file can be found in the `valet/Certificates` directory.
|
Checks if a certificate file can be found in the `valet/Certificates` directory.
|
||||||
- Note: The file is not validated, only its presence is checked.
|
Also tracks the expiry date of the certificate if it exists.
|
||||||
*/
|
*/
|
||||||
public func determineSecured() {
|
public func determineSecured() {
|
||||||
secured = container.filesystem
|
let certificatePath = "~/.config/valet/Certificates/\(self.name).\(self.tld).crt"
|
||||||
.fileExists("~/.config/valet/Certificates/\(self.name).\(self.tld).key")
|
|
||||||
|
|
||||||
// TODO: Also verify the certificate hasn't expired
|
let (exists, expiryDate) = CertificateValidator(container)
|
||||||
|
.validateCertificate(at: certificatePath)
|
||||||
|
|
||||||
|
if exists, let expiryDate {
|
||||||
|
Log.info("Certificate for \(self.name).\(self.tld) expires at: \(expiryDate).")
|
||||||
|
} else {
|
||||||
|
Log.info("No certificate for \(self.name).\(self.tld).")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the information for the list
|
||||||
|
self.secured = exists
|
||||||
|
self.expiryDate = expiryDate
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user