@@ -44,18 +44,44 @@ public function saveAccountKey(string $pem, KeyType $type): void
4444
4545 // ── Certificates ─────────────────────────────────────────────────────────
4646
47- public function hasCertificate (string $ domain ): bool
47+ public function hasCertificate (string $ domain, KeyType $ keyType ): bool
4848 {
49- return file_exists ($ this ->certPath ($ domain ));
49+ if (file_exists ($ this ->certPath ($ domain , $ keyType ))) {
50+ return true ;
51+ }
52+
53+ // Legacy path without key-type suffix — treated as present for migration.
54+ return file_exists ($ this ->legacyCertPath ($ domain ));
5055 }
5156
52- public function getCertificate (string $ domain ): ?StoredCertificate
57+ public function getCertificate (string $ domain, KeyType $ keyType ): ?StoredCertificate
5358 {
54- if (!$ this ->hasCertificate ($ domain )) {
55- return null ;
59+ $ path = $ this ->certPath ($ domain , $ keyType );
60+
61+ if (!file_exists ($ path )) {
62+ // Attempt transparent migration of a legacy single-cert file.
63+ $ legacy = $ this ->legacyCertPath ($ domain );
64+
65+ if (!file_exists ($ legacy )) {
66+ return null ;
67+ }
68+
69+ $ data = json_decode ($ this ->readFile ($ legacy ), true , 512 , JSON_THROW_ON_ERROR );
70+ $ cert = StoredCertificate::fromArray ($ data );
71+
72+ // Only migrate when the legacy cert's key type matches what's requested.
73+ if ($ cert ->keyType !== $ keyType ) {
74+ return null ;
75+ }
76+
77+ $ this ->ensureDirectory ();
78+ $ this ->writeFile ($ path , json_encode ($ cert ->toArray (), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT ));
79+ unlink ($ legacy );
80+
81+ return $ cert ;
5682 }
5783
58- $ data = json_decode ($ this ->readFile ($ this -> certPath ( $ domain ) ), true , 512 , JSON_THROW_ON_ERROR );
84+ $ data = json_decode ($ this ->readFile ($ path ), true , 512 , JSON_THROW_ON_ERROR );
5985
6086 return StoredCertificate::fromArray ($ data );
6187 }
@@ -64,17 +90,26 @@ public function saveCertificate(string $domain, StoredCertificate $cert): void
6490 {
6591 $ this ->ensureDirectory ();
6692 $ this ->writeFile (
67- $ this ->certPath ($ domain ),
93+ $ this ->certPath ($ domain, $ cert -> keyType ),
6894 json_encode ($ cert ->toArray (), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT ),
6995 );
7096 }
7197
72- public function deleteCertificate (string $ domain ): void
98+ public function deleteCertificate (string $ domain, KeyType $ keyType ): void
7399 {
74- $ path = $ this ->certPath ($ domain );
100+ $ path = $ this ->certPath ($ domain, $ keyType );
75101
76102 if (file_exists ($ path )) {
77103 unlink ($ path );
104+
105+ return ;
106+ }
107+
108+ // Also remove a legacy file if it exists for this domain.
109+ $ legacy = $ this ->legacyCertPath ($ domain );
110+
111+ if (file_exists ($ legacy )) {
112+ unlink ($ legacy );
78113 }
79114 }
80115
@@ -90,9 +125,16 @@ private function accountMetaPath(): string
90125 return $ this ->dir () . 'account.json ' ;
91126 }
92127
93- private function certPath (string $ domain ): string
128+ private function certPath (string $ domain , KeyType $ keyType ): string
129+ {
130+ $ safe = preg_replace ('/[^a-zA-Z0-9._\-]/ ' , '_ ' , $ domain );
131+
132+ return $ this ->dir () . $ safe . '. ' . $ keyType ->value . '.cert.json ' ;
133+ }
134+
135+ /** Pre-v2 path — one cert per domain, no key-type suffix. */
136+ private function legacyCertPath (string $ domain ): string
94137 {
95- // Sanitise the domain so it is safe to use as a filename.
96138 $ safe = preg_replace ('/[^a-zA-Z0-9._\-]/ ' , '_ ' , $ domain );
97139
98140 return $ this ->dir () . $ safe . '.cert.json ' ;
0 commit comments