safeKey($domain); // $selKey = $this->safeKey($selector, 32); // // $disk = Storage::disk('local'); // $baseRel = "dkim/{$dirKey}"; // $privRel = "{$baseRel}/{$selKey}.pem"; // $pubRel = "{$baseRel}/{$selKey}.pub"; // // // 2) Idempotent: existiert das Paar schon? -> nur lesen & zurückgeben // if ($disk->exists($privRel) && $disk->exists($pubRel)) { // $privateKey = $disk->get($privRel); // $publicKeyPem = $disk->get($pubRel); // $publicKeyBase = self::extractPublicKeyBase64($publicKeyPem); // if (strlen($publicKeyBase) < 300) { // throw new \RuntimeException('DKIM: Public Key zu kurz – vermutlich Parsing-Fehler.'); // } // return [ // 'selector' => $selKey, // 'priv_path' => storage_path("app/{$privRel}"), // 'pub_path' => storage_path("app/{$pubRel}"), // 'public_pem' => $publicKeyPem, // 'private_pem' => $privateKey, // 'dns_name' => "{$selKey}._domainkey", // 'dns_txt' => "v=DKIM1; k=rsa; p={$publicKeyBase}", // 'bits' => $bits, // ]; // } // // // 3) Sonst neu generieren // $disk->makeDirectory($baseRel); // // $res = openssl_pkey_new([ // 'private_key_type' => OPENSSL_KEYTYPE_RSA, // 'private_key_bits' => $bits, // ]); // if ($res === false) { // throw new \RuntimeException('DKIM: openssl_pkey_new() fehlgeschlagen: ' . (openssl_error_string() ?: 'unbekannt')); // } // // $privateKey = ''; // if (!openssl_pkey_export($res, $privateKey)) { // throw new \RuntimeException('DKIM: openssl_pkey_export() fehlgeschlagen: ' . (openssl_error_string() ?: 'unbekannt')); // } // // $details = openssl_pkey_get_details($res); // if ($details === false || empty($details['key'])) { // throw new \RuntimeException('DKIM: Public Key konnte nicht gelesen werden.'); // } // $publicKeyPem = $details['key']; // $publicKeyBase = self::extractPublicKeyBase64($publicKeyPem); // if (strlen($publicKeyBase) < 300) { // throw new \RuntimeException('DKIM: Public Key zu kurz – vermutlich Parsing-Fehler.'); // } // // if (!$disk->put($privRel, $privateKey)) { // throw new \RuntimeException("DKIM: Private-Key schreiben fehlgeschlagen: {$privRel}"); // } // if (!$disk->put($pubRel, $publicKeyPem)) { // throw new \RuntimeException("DKIM: Public-Key schreiben fehlgeschlagen: {$pubRel}"); // } // // // 4) Rückgabe // return [ // 'selector' => $selKey, // 'priv_path' => storage_path("app/{$privRel}"), // 'pub_path' => storage_path("app/{$pubRel}"), // 'public_pem' => $publicKeyPem, // 'private_pem' => $privateKey, // 'dns_name' => "{$selKey}._domainkey", // 'dns_txt' => "v=DKIM1; k=rsa; p={$publicKeyBase}", // 'bits' => $bits, // ]; // } public function generateForDomain(Domain $domain, int $bits = 2048, string $selector = null): array { // 1) Selector zentral aus der Config (Fallback 'mwl1') $selector = $selector ?: (string) config('mailpool.defaults.dkim_selector', 'mwl1'); $dirKey = $this->safeKey($domain); $selKey = $this->safeKey($selector, 32); $disk = Storage::disk('local'); $baseRel = "dkim/{$dirKey}"; $privRel = "{$baseRel}/{$selKey}.pem"; $pubRel = "{$baseRel}/{$selKey}.pub"; // 2) Idempotent: existiert das Paar schon? if ($disk->exists($privRel) && $disk->exists($pubRel)) { $privateKey = $disk->get($privRel); $publicKeyPem = $disk->get($pubRel); $publicKeyBase = self::extractPublicKeyBase64($publicKeyPem); if (strlen($publicKeyBase) < 300) { throw new \RuntimeException('DKIM: Public Key zu kurz – vermutlich Parsing-Fehler.'); } return [ 'selector' => $selKey, 'priv_path' => $disk->path($privRel), 'pub_path' => $disk->path($pubRel), 'public_pem' => $publicKeyPem, 'private_pem' => $privateKey, 'dns_name' => "{$selKey}._domainkey", 'dns_txt' => "v=DKIM1; k=rsa; p={$publicKeyBase}", 'bits' => $bits, ]; } // 3) Neu generieren $disk->makeDirectory($baseRel); $res = openssl_pkey_new([ 'private_key_type' => OPENSSL_KEYTYPE_RSA, 'private_key_bits' => $bits, ]); if ($res === false) { throw new \RuntimeException('DKIM: openssl_pkey_new() fehlgeschlagen: ' . (openssl_error_string() ?: 'unbekannt')); } $privateKey = ''; if (!openssl_pkey_export($res, $privateKey)) { throw new \RuntimeException('DKIM: openssl_pkey_export() fehlgeschlagen: ' . (openssl_error_string() ?: 'unbekannt')); } $details = openssl_pkey_get_details($res); if ($details === false || empty($details['key'])) { throw new \RuntimeException('DKIM: Public Key konnte nicht gelesen werden.'); } $publicKeyPem = $details['key']; $publicKeyBase = self::extractPublicKeyBase64($publicKeyPem); if (strlen($publicKeyBase) < 300) { throw new \RuntimeException('DKIM: Public Key zu kurz – vermutlich Parsing-Fehler.'); } if (!$disk->put($privRel, $privateKey)) { throw new \RuntimeException("DKIM: Private-Key schreiben fehlgeschlagen: {$privRel}"); } if (!$disk->put($pubRel, $publicKeyPem)) { throw new \RuntimeException("DKIM: Public-Key schreiben fehlgeschlagen: {$pubRel}"); } return [ 'selector' => $selKey, 'priv_path' => $disk->path($privRel), 'pub_path' => $disk->path($pubRel), 'public_pem' => $publicKeyPem, 'private_pem' => $privateKey, 'dns_name' => "{$selKey}._domainkey", 'dns_txt' => "v=DKIM1; k=rsa; p={$publicKeyBase}", 'bits' => $bits, ]; } protected function safeKey($value, int $max = 64): string { if (is_object($value)) { if (isset($value->id)) $value = $value->id; elseif (method_exists($value, 'getKey')) $value = $value->getKey(); else $value = json_encode($value); } $raw = (string) $value; $san = preg_replace('/[^A-Za-z0-9._-]/', '_', $raw); if ($san === '' ) $san = 'unknown'; if (strlen($san) > $max) { $san = substr($san, 0, $max - 13) . '_' . substr(sha1($raw), 0, 12); } return $san; } protected static function extractPublicKeyBase64(string $pem): string { // Hole den Body zwischen den Headern (multiline, dotall) if (!preg_match('/^-+BEGIN PUBLIC KEY-+\r?\n(.+?)\r?\n-+END PUBLIC KEY-+\s*$/ms', $pem, $m)) { throw new \RuntimeException('DKIM: Ungültiges Public-Key-PEM (Header/Footers nicht gefunden).'); } // Whitespace entfernen → reines Base64 $b64 = preg_replace('/\s+/', '', $m[1]); if ($b64 === '' || base64_decode($b64, true) === false) { throw new \RuntimeException('DKIM: Public Key Base64 ist leer/ungültig.'); } return $b64; } }