diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 6bc224e7d4..4eec706bb5 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -148,9 +148,7 @@ private function getRootCertOptions(string $configFile): array { ]; } - private function getLeafCertOptions(): array { - $configFile = $this->generateLeafConfig(); - + private function getLeafCertOptions(string $configFile): array { return [ 'digest_alg' => 'sha256', 'config' => $configFile, @@ -179,7 +177,12 @@ public function generateCertificate(): string { throw $this->buildOpenSslException('Failed to generate OpenSSL private key'); } - $csr = $this->createCsr($this->getCsrNames(), $privateKey, ['digest_alg' => 'sha256']); + $configFile = $this->generateLeafConfig(); + $csr = $this->createCsr($this->getCsrNames(), $privateKey, [ + 'digest_alg' => 'sha256', + 'config' => $configFile, + 'config_section_name' => 'req', + ]); if ($csr === false) { throw $this->buildOpenSslException('Failed to generate OpenSSL CSR'); } @@ -202,7 +205,7 @@ public function generateCertificate(): string { CertificateType::LEAF->value, ); $serialNumber = (int)$serialNumberString; - $options = $this->getLeafCertOptions(); + $options = $this->getLeafCertOptions($configFile); $x509 = $this->signCsr($csr, $rootCertificate, $rootPrivateKey, $this->getLeafExpiryInDays(), $options, $serialNumber); if ($x509 === false) { @@ -357,7 +360,6 @@ private function buildLeafCertificateConfig(): array { $config = [ 'req' => [ 'distinguished_name' => 'req_distinguished_name', - 'req_extensions' => 'v3_req', 'prompt' => 'no', ], 'req_distinguished_name' => [], diff --git a/tests/php/Integration/Handler/CertificateEngine/OpenSslHandlerTest.php b/tests/php/Integration/Handler/CertificateEngine/OpenSslHandlerTest.php index 4d48d38a04..2c155640f0 100644 --- a/tests/php/Integration/Handler/CertificateEngine/OpenSslHandlerTest.php +++ b/tests/php/Integration/Handler/CertificateEngine/OpenSslHandlerTest.php @@ -455,6 +455,111 @@ public function testSerialNumberGeneration(): void { $this->assertMatchesRegularExpression('/^[0-9A-Fa-f]+$/', $parsed['serialNumberHex'], 'Serial number hex should contain only hex characters'); } + #[DataProvider('leafSubjectCases')] + public function testLeafCertificateSubjectMatchesConfiguredFields(array $configuredFields, array $expectedSubject): void { + $rootInstance = $this->getInstance(); + $rootInstance->generateRootCert('Test Root CA', []); + + $signerInstance = $this->getInstance(); + $signerInstance->setCommonName('Test Signer'); + $signerInstance->setPassword('123456'); + foreach ($configuredFields as $field => $value) { + match ($field) { + 'C' => $signerInstance->setCountry($value), + 'ST' => $signerInstance->setState($value), + 'L' => $signerInstance->setLocality($value), + 'O' => $signerInstance->setOrganization($value), + }; + } + + $certificate = $signerInstance->generateCertificate(); + $parsed = $signerInstance->readCertificate($certificate, '123456'); + $subject = array_intersect_key($parsed['subject'], array_flip(['CN', 'C', 'ST', 'L', 'O'])); + ksort($subject); + ksort($expectedSubject); + $this->assertSame($expectedSubject, $subject); + } + + public static function leafSubjectCases(): array { + return [ + 'common name only' => [ + [], + ['CN' => 'Test Signer'], + ], + 'country and locality without defaults' => [ + ['C' => 'DE', 'L' => 'Berlin'], + ['CN' => 'Test Signer', 'C' => 'DE', 'L' => 'Berlin'], + ], + 'state only' => [ + ['ST' => 'Brandenburg'], + ['CN' => 'Test Signer', 'ST' => 'Brandenburg'], + ], + 'organization only' => [ + ['O' => 'LibreCode'], + ['CN' => 'Test Signer', 'O' => 'LibreCode'], + ], + 'country locality and state' => [ + ['C' => 'DE', 'ST' => 'Brandenburg', 'L' => 'Berlin'], + ['CN' => 'Test Signer', 'C' => 'DE', 'ST' => 'Brandenburg', 'L' => 'Berlin'], + ], + 'country locality and organization' => [ + ['C' => 'DE', 'L' => 'Berlin', 'O' => 'LibreCode'], + ['CN' => 'Test Signer', 'C' => 'DE', 'L' => 'Berlin', 'O' => 'LibreCode'], + ], + 'all configured fields' => [ + ['C' => 'BR', 'ST' => 'Rio de Janeiro', 'L' => 'Niteroi', 'O' => 'LibreCode'], + ['CN' => 'Test Signer', 'C' => 'BR', 'ST' => 'Rio de Janeiro', 'L' => 'Niteroi', 'O' => 'LibreCode'], + ], + 'placeholder strings explicitly supplied' => [ + ['ST' => 'Some-State', 'O' => 'Internet Widgits Pty Ltd'], + ['CN' => 'Test Signer', 'ST' => 'Some-State', 'O' => 'Internet Widgits Pty Ltd'], + ], + ]; + } + + public function testLeafCsrUsesGeneratedConfig(): void { + $rootInstance = $this->getInstance(); + $rootInstance->generateRootCert('Test Root CA', []); + + $signerInstance = $this->getMockBuilder(OpenSslHandler::class) + ->setConstructorArgs([ + $this->config, + $this->appConfig, + $this->appDataFactory, + $this->dateTimeFormatter, + $this->tempManager, + $this->certificatePolicyService, + $this->urlGenerator, + $this->serialNumberService, + $this->caIdentifierService, + $this->policyService, + $this->logger, + $this->crlMapper, + $this->subjectAlternativeNameService, + $this->crlRevocationChecker, + ]) + ->onlyMethods(['createCsr']) + ->getMock(); + $signerInstance->expects($this->once())->method('createCsr') + ->willReturnCallback(function (array $distinguishedNames, mixed $privateKey, array $options) { + $this->assertArrayHasKey('config', $options); + $this->assertSame('req', $options['config_section_name'] ?? null); + $this->assertFileExists($options['config']); + $config = file_get_contents($options['config']); + $this->assertNotFalse($config); + $this->assertStringContainsString('[req_distinguished_name]', $config); + $this->assertStringContainsString('[v3_req]', $config); + $this->assertStringNotContainsString('req_extensions =', $config); + return openssl_csr_new($distinguishedNames, $privateKey, $options); + }); + + $signerInstance->setCommonName('Test Signer'); + $signerInstance->setPassword('123456'); + $certificate = $signerInstance->generateCertificate(); + $parsed = $signerInstance->readCertificate($certificate, '123456'); + $this->assertSame('Test Signer', $parsed['subject']['CN']); + } + public function testLeafCertificateUsesRsa2048Key(): void { ['certificate' => $certificate] = $this->generateLeafCertificate();