From 6e51525c5446f91aaf8b16789d493122dbed42c7 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 13:38:29 -0300 Subject: [PATCH 01/50] Fix configure commands after changes at array structure Signed-off-by: Vitor Mattos --- lib/Command/Configure/Cfssl.php | 12 ++++++------ lib/Command/Configure/OpenSsl.php | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Command/Configure/Cfssl.php b/lib/Command/Configure/Cfssl.php index fbeeb88fb4..6beed18fab 100644 --- a/lib/Command/Configure/Cfssl.php +++ b/lib/Command/Configure/Cfssl.php @@ -94,19 +94,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException('Invalid Comon Name'); } if ($input->getOption('ou')) { - $names[] = ['id' => 'OU', 'value' => $input->getOption('ou')]; + $names['OU'] = ['value' => $input->getOption('ou')]; } if ($input->getOption('o')) { - $names[] = ['id' => 'O', 'value' => $input->getOption('o')]; + $names['O'] = ['value' => $input->getOption('o')]; } if ($input->getOption('c')) { - $names[] = ['id' => 'C', 'value' => $input->getOption('c')]; + $names['C'] = ['value' => $input->getOption('c')]; } if ($input->getOption('l')) { - $names[] = ['id' => 'L', 'value' => $input->getOption('l')]; + $names['L'] = ['value' => $input->getOption('l')]; } if ($input->getOption('st')) { - $names[] = ['id' => 'ST', 'value' => $input->getOption('st')]; + $names['ST'] = ['value' => $input->getOption('st')]; } if (PHP_OS_FAMILY === 'Windows') { @@ -123,7 +123,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $configPath = $input->getOption('config-path'); $this->installService->generate( - $commonName, + (string) $commonName, $names, [ 'engine' => 'cfssl', diff --git a/lib/Command/Configure/OpenSsl.php b/lib/Command/Configure/OpenSsl.php index 76b921044d..b4207ab091 100644 --- a/lib/Command/Configure/OpenSsl.php +++ b/lib/Command/Configure/OpenSsl.php @@ -85,22 +85,22 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException('Invalid Comon Name'); } if ($input->getOption('ou')) { - $names[] = ['id' => 'OU', 'value' => $input->getOption('ou')]; + $names['OU'] = ['value' => $input->getOption('ou')]; } if ($input->getOption('o')) { - $names[] = ['id' => 'O', 'value' => $input->getOption('o')]; + $names['O'] = ['value' => $input->getOption('o')]; } if ($input->getOption('c')) { - $names[] = ['id' => 'C', 'value' => $input->getOption('c')]; + $names['C'] = ['value' => $input->getOption('c')]; } if ($input->getOption('l')) { - $names[] = ['id' => 'L', 'value' => $input->getOption('l')]; + $names['L'] = ['value' => $input->getOption('l')]; } if ($input->getOption('st')) { - $names[] = ['id' => 'ST', 'value' => $input->getOption('st')]; + $names['ST'] = ['value' => $input->getOption('st')]; } $this->installService->generate( - $commonName, + (string) $commonName, $names, [ 'engine' => 'openssl' From bdb5d768e808b2d62de9c74bba02a063513fcb9e Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 13:42:00 -0300 Subject: [PATCH 02/50] Make methods protected to be possible used by chield classes Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/AEngineHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Handler/CertificateEngine/AEngineHandler.php b/lib/Handler/CertificateEngine/AEngineHandler.php index 9aa69d8f32..b5aded892b 100644 --- a/lib/Handler/CertificateEngine/AEngineHandler.php +++ b/lib/Handler/CertificateEngine/AEngineHandler.php @@ -249,7 +249,7 @@ public function getName(): string { return $name; } - private function getNames(): array { + protected function getNames(): array { $names = [ 'C' => $this->getCountry(), 'ST' => $this->getState(), From 46f4cc0c89e4907ace8455f6866f61bd2c2c03b4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 13:42:49 -0300 Subject: [PATCH 03/50] Use directory separator Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/CfsslHandler.php | 14 +++++++------- lib/Handler/CertificateEngine/OpenSslHandler.php | 14 +++++++------- lib/Handler/CfsslServerHandler.php | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index 5e9cf44881..2fdc60bcd1 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -168,9 +168,9 @@ private function wakeUp(): void { throw new LibresignException('CFSSL not configured.'); } $cmd = 'nohup ' . $binary . ' serve -address=127.0.0.1 ' . - '-ca-key ' . $configPath . 'ca-key.pem ' . - '-ca ' . $configPath . 'ca.pem '. - '-config ' . $configPath . 'config_server.json > /dev/null 2>&1 & echo $!'; + '-ca-key ' . $configPath . DIRECTORY_SEPARATOR . 'ca-key.pem ' . + '-ca ' . $configPath . DIRECTORY_SEPARATOR . 'ca.pem '. + '-config ' . $configPath . DIRECTORY_SEPARATOR . 'config_server.json > /dev/null 2>&1 & echo $!'; shell_exec($cmd); $loops = 0; while (!$this->portOpen() && $loops <= 4) { @@ -249,8 +249,8 @@ private function genkey(): void { $binary = $this->getBinary(); $configPath = $this->getConfigPath(); $cmd = $binary . ' genkey ' . - '-initca=true ' . $configPath . 'csr_server.json | ' . - $binary . 'json -bare ' . $configPath . 'ca;'; + '-initca=true ' . $configPath . DIRECTORY_SEPARATOR . 'csr_server.json | ' . + $binary . 'json -bare ' . $configPath . DIRECTORY_SEPARATOR . 'ca;'; shell_exec($cmd); } @@ -285,8 +285,8 @@ public function isSetupOk(): bool { return false; }; $configPath = $this->getConfigPath(); - $certificate = file_exists($configPath . '/ca.pem'); - $privateKey = file_exists($configPath . '/ca-key.pem'); + $certificate = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); + $privateKey = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); if (!$certificate || !$privateKey) { return false; } diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index fff2e31d0d..b59d8112b9 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -37,8 +37,8 @@ class OpenSslHandler extends AEngineHandler implements IEngineHandler { public function generateCertificate(string $certificate = '', string $privateKey = ''): string { $configPath = $this->getConfigPath(); - $certificate = file_get_contents($configPath . '/ca.pem'); - $privateKey = file_get_contents($configPath . '/ca-key.pem'); + $certificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); + $privateKey = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); if (empty($certificate) || empty($privateKey)) { throw new LibresignException('Invalid root certificate'); } @@ -68,9 +68,9 @@ public function generateRootCert( openssl_x509_export($x509, $certout); openssl_pkey_export($privkey, $pkeyout); - file_put_contents($configPath . '/ca.csr', $csrout); - file_put_contents($configPath . '/ca.pem', $certout); - file_put_contents($configPath . '/ca-key.pem', $pkeyout); + file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca.csr', $csrout); + file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem', $certout); + file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem', $pkeyout); return $pkeyout; } @@ -81,8 +81,8 @@ public function isSetupOk(): bool { return false; } $configPath = $this->getConfigPath(); - $certificate = file_exists($configPath . '/ca.pem'); - $privateKey = file_exists($configPath . '/ca-key.pem'); + $certificate = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); + $privateKey = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); return $certificate && $privateKey; } diff --git a/lib/Handler/CfsslServerHandler.php b/lib/Handler/CfsslServerHandler.php index 66482727a7..9f73f10c69 100644 --- a/lib/Handler/CfsslServerHandler.php +++ b/lib/Handler/CfsslServerHandler.php @@ -49,7 +49,7 @@ private function putCsrServer( array $names, string $configPath ): void { - $filename = $configPath . self::CSR_FILE; + $filename = $configPath . DIRECTORY_SEPARATOR . self::CSR_FILE; $content = [ 'CN' => $commonName, 'key' => [ @@ -72,7 +72,7 @@ private function putCsrServer( } private function putConfigServer(string $key, string $configPath): void { - $filename = $configPath . self::CONFIG_FILE; + $filename = $configPath . DIRECTORY_SEPARATOR . self::CONFIG_FILE; $content = [ 'signing' => [ 'profiles' => [ From cb0a5b53fa3603ec0e183e29b26272f78bc2b5b0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 13:43:41 -0300 Subject: [PATCH 04/50] stop server if running after generate root cert with cfssl engine The root cert is loaded when the server start. If we don't restart the server, don't will load the root cert Signed-off-by: Vitor Mattos --- .../CertificateEngine/CfsslHandler.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index 2fdc60bcd1..fe0c452e1b 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -193,6 +193,27 @@ private function portOpen(): bool { return false; } + private function getServerPid(): int { + $cmd = 'ps -eo pid,command|'; + $cmd .= 'grep "cfssl.*serve.*-address"|' . + 'grep -v grep|' . + 'grep -v defunct|' . + 'sed -e "s/^[[:space:]]*//"|cut -d" " -f1'; + $output = shell_exec($cmd); + if (!is_string($output)) { + return 0; + } + $pid = trim($output); + return (int) $pid; + } + + private function stopIfRunning(): void { + $pid = $this->getServerPid(); + if ($pid > 0) { + exec('kill ' . $pid); + } + } + private function getBinary(): string { if ($this->binary) { return $this->binary; @@ -270,6 +291,8 @@ public function generateRootCert( $this->genkey(); + $this->stopIfRunning(); + for ($i = 1; $i <= 4; $i++) { if ($this->isUp($this->getCfsslUri())) { break; From e383ba01ba90598be9afd9b883f0b647decb294e Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 13:45:18 -0300 Subject: [PATCH 05/50] Fix array structure Signed-off-by: Vitor Mattos --- lib/Handler/CfsslServerHandler.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/Handler/CfsslServerHandler.php b/lib/Handler/CfsslServerHandler.php index 9f73f10c69..180068b0b5 100644 --- a/lib/Handler/CfsslServerHandler.php +++ b/lib/Handler/CfsslServerHandler.php @@ -57,10 +57,9 @@ private function putCsrServer( 'size' => 2048, ], ]; - foreach ($names as $name) { - $content['names'][0][$name['id']] = $name['value']; + foreach ($names as $id => $name) { + $content['names'][0][$id] = $name['value']; } - $response = file_put_contents($filename, json_encode($content)); if ($response === false) { throw new LibresignException( From 9f3d9daf63f8639640a797671f224de5acba443a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 13:45:40 -0300 Subject: [PATCH 06/50] Prevent to show logs of commands Signed-off-by: Vitor Mattos --- tests/integration/features/bootstrap/FeatureContext.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 1ca6d10458..42cd7ccb29 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -42,6 +42,7 @@ public static function runCommand($command): void { if (posix_getuid() !== $owner['uid']) { $fullCommand = 'runuser -u ' . $owner['name'] . ' -- ' . $fullCommand; } + $fullCommand.= ' 2>&1'; exec($fullCommand, $output); } From c537254ca94fa52f6e4f4db1f76079bda7ed3f98 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 13:47:12 -0300 Subject: [PATCH 07/50] Convert background scenario to separated scenario Signed-off-by: Vitor Mattos --- .../features/account/signature.feature | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index fd50673ccc..add493e7f4 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -1,8 +1,6 @@ Feature: account/signature - Background: Create users and root certificate - Given user "signer1" exists - And set the email of user "signer1" to "signer@domain.test" - And as user "admin" + Scenario: Create root certificate using API + Given as user "admin" And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"Common Name"} | And the response should have a status code 200 @@ -20,14 +18,17 @@ Feature: account/signature Then the response should have a status code 200 Scenario: Upload PFX file with error - Given sending "post" to ocs "/apps/libresign/api/v1/account/pfx" + Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + And sending "post" to ocs "/apps/libresign/api/v1/account/pfx" Then the response should have a status code 400 And the response should be a JSON array with the following mandatory values | key | value | | message | No certificate file provided | Scenario: Change pfx password with success - Given as user "signer1" + Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + And user "signer1" exists + And as user "signer1" And sending "post" to ocs "/apps/libresign/api/v1/account/signature" | signPassword | password | Then the response should have a status code 200 @@ -47,7 +48,9 @@ Feature: account/signature | message | New password to sign documents has been created | Scenario: Delete pfx password with success - Given as user "signer1" + Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + And user "signer1" exists + And as user "signer1" And sending "post" to ocs "/apps/libresign/api/v1/account/signature" | signPassword | password | Then the response should have a status code 200 From 0fb759f690ac55c66396f2c9bb9ad1f0dc43bc75 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 14:25:37 -0300 Subject: [PATCH 08/50] Throw exception when running at CLI to install CFSSL when engine is not CFSSL Signed-off-by: Vitor Mattos --- lib/Service/InstallService.php | 4 ++ .../features/account/signature.feature | 40 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/Service/InstallService.php b/lib/Service/InstallService.php index d13921944a..e193a8775a 100644 --- a/lib/Service/InstallService.php +++ b/lib/Service/InstallService.php @@ -24,6 +24,7 @@ namespace OCA\Libresign\Service; +use InvalidArgumentException; use OC; use OC\Archive\TAR; use OC\Archive\ZIP; @@ -485,6 +486,9 @@ public function uninstallPdftk(): void { public function installCfssl(?bool $async = false): void { if ($this->certificateEngineHandler->getEngine()->getName() !== 'cfssl') { + if (!$async) { + throw new InvalidArgumentException('Set the engine to cfssl with: config:app:set libresign certificate_engine --value cfssl'); + } return; } $this->setResource('cfssl'); diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index add493e7f4..31b1ed5049 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -11,11 +11,45 @@ Feature: account/signature | rootCert | {"commonName":"Common Name","names":[]} | | generated | true | - Scenario: Create pfx with success - Given as user "signer1" + Scenario: Create pfx with success with CFSSL + Given user "signer1" exists + And set the email of user "signer1" to "signer@domain.test" + And as user "signer1" + And run the command "config:app:set libresign certificate_engine --value cfssl" + And run the command "libresign:install --cfssl" + And run the command "libresign:configure:cfssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" And sending "post" to ocs "/apps/libresign/api/v1/account/signature" | signPassword | password | - Then the response should have a status code 200 + And the response should have a status code 200 + When sending "Post" to ocs "/apps/libresign/api/v1/account/pfx/read" + | key | value | + | password | password | + Then the response should be a JSON array with the following mandatory values + | key | value | + | name | /C=BR/ST=State of Company/O=Organization/CN=signer1-displayname | + | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | + | subjectAltName | DNS:signer1 | + | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | + | issuerInfoAccess | | + + Scenario: Create pfx with success with OpenSSL + Given user "signer1" exists + And set the email of user "signer1" to "signer@domain.test" + And as user "signer1" + And run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + And sending "post" to ocs "/apps/libresign/api/v1/account/signature" + | signPassword | password | + And the response should have a status code 200 + When sending "Post" to ocs "/apps/libresign/api/v1/account/pfx/read" + | key | value | + | password | password | + Then the response should be a JSON array with the following mandatory values + | key | value | + | name | /CN=Common Name/O=Organization/C=BR/ST=State of Company | + | subject | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | + | subjectAltName | | + | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | + | issuerInfoAccess | | Scenario: Upload PFX file with error Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" From ec2b89285aea12835bd312e8ff7d25ca94714891 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 14:26:21 -0300 Subject: [PATCH 09/50] Add pending steps Signed-off-by: Vitor Mattos --- tests/integration/features/account/signature.feature | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 31b1ed5049..2882d3dc06 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -53,7 +53,9 @@ Feature: account/signature Scenario: Upload PFX file with error Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" - And sending "post" to ocs "/apps/libresign/api/v1/account/pfx" + And user "signer1" exists + And as user "signer1" + When sending "post" to ocs "/apps/libresign/api/v1/account/pfx" Then the response should have a status code 400 And the response should be a JSON array with the following mandatory values | key | value | From 1fc40526142c37037044ee03785f8e69a38b26d9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 14:26:31 -0300 Subject: [PATCH 10/50] Implement integration test to setup root cert Signed-off-by: Vitor Mattos --- .../features/account/signature.feature | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 2882d3dc06..2fd41a2be5 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -1,5 +1,5 @@ Feature: account/signature - Scenario: Create root certificate using API + Scenario: Create root certificate with OpenSSL engine using API Given as user "admin" And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" | rootCert | {"commonName":"Common Name"} | @@ -11,6 +11,20 @@ Feature: account/signature | rootCert | {"commonName":"Common Name","names":[]} | | generated | true | + Scenario: Create root certificate with CFSSL engine using API + Given as user "admin" + And run the command "config:app:set libresign certificate_engine --value cfssl" + And run the command "libresign:install --cfssl" + And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/cfssl" + | rootCert | {"commonName":"Common Name"} | + And the response should have a status code 200 + And sending "get" to ocs "/apps/libresign/api/v1/admin/certificate" + And the response should have a status code 200 + And the response should be a JSON array with the following mandatory values + | key | value | + | rootCert | {"commonName":"Common Name","names":[]} | + | generated | true | + Scenario: Create pfx with success with CFSSL Given user "signer1" exists And set the email of user "signer1" to "signer@domain.test" From faf0cdb6cd6a6ac98ed2660bd1891b85d49a7e4c Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 14:57:46 -0300 Subject: [PATCH 11/50] cs:fix Signed-off-by: Vitor Mattos --- tests/integration/features/bootstrap/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 42cd7ccb29..341ba5ce19 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -42,7 +42,7 @@ public static function runCommand($command): void { if (posix_getuid() !== $owner['uid']) { $fullCommand = 'runuser -u ' . $owner['name'] . ' -- ' . $fullCommand; } - $fullCommand.= ' 2>&1'; + $fullCommand .= ' 2>&1'; exec($fullCommand, $output); } From 2c320d95e91d11328073523880ae03ab19b3f600 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 15:01:55 -0300 Subject: [PATCH 12/50] Fix to run at CI Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/CfsslHandler.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index fe0c452e1b..eb6ce3cd32 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -207,10 +207,26 @@ private function getServerPid(): int { return (int) $pid; } + /** + * Parse command + * + * Have commands that need to be executed as sudo otherwise don't will work, + * by example the command runuser or kill. To prevent error when run in a + * GitHub Actions, these commands are executed prefixed by sudo when exists + * an environment called GITHUB_ACTIONS. + */ + private function parseCommand(string $command): string + { + if (getenv('GITHUB_ACTIONS') !== false) { + $command = 'sudo ' . $command; + } + return $command; + } + private function stopIfRunning(): void { $pid = $this->getServerPid(); if ($pid > 0) { - exec('kill ' . $pid); + exec($this->parseCommand('kill ' . $pid)); } } From a3cd8edf1ce517c0477ab8772dd8ef781e8be946 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 13 Mar 2024 15:06:43 -0300 Subject: [PATCH 13/50] cs:fix Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/CfsslHandler.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index eb6ce3cd32..c681d3e177 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -215,8 +215,7 @@ private function getServerPid(): int { * GitHub Actions, these commands are executed prefixed by sudo when exists * an environment called GITHUB_ACTIONS. */ - private function parseCommand(string $command): string - { + private function parseCommand(string $command): string { if (getenv('GITHUB_ACTIONS') !== false) { $command = 'sudo ' . $command; } From 6e632ec24f6d2674ef49f644d50eac538ed7b6a1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 17:31:13 -0300 Subject: [PATCH 14/50] Bump dependencies Signed-off-by: Vitor Mattos --- tests/integration/composer.lock | 41 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/tests/integration/composer.lock b/tests/integration/composer.lock index 481740a883..1af0e112d3 100644 --- a/tests/integration/composer.lock +++ b/tests/integration/composer.lock @@ -3186,16 +3186,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -3207,7 +3207,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3228,8 +3228,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -3237,7 +3236,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -5139,16 +5138,16 @@ }, { "name": "libresign/behat-builtin-extension", - "version": "v0.6.1", + "version": "v0.6.2", "source": { "type": "git", "url": "https://github.com/LibreSign/behat-builtin-extension.git", - "reference": "db449ec7ec4ee6cb966064a55cd6f5ba455bcb75" + "reference": "556d583613985b03b5971b222ee5abcf82b6b63d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LibreSign/behat-builtin-extension/zipball/db449ec7ec4ee6cb966064a55cd6f5ba455bcb75", - "reference": "db449ec7ec4ee6cb966064a55cd6f5ba455bcb75", + "url": "https://api.github.com/repos/LibreSign/behat-builtin-extension/zipball/556d583613985b03b5971b222ee5abcf82b6b63d", + "reference": "556d583613985b03b5971b222ee5abcf82b6b63d", "shasum": "" }, "require": { @@ -5179,29 +5178,29 @@ "description": "Behat extension to run php built-in web server", "support": { "issues": "https://github.com/LibreSign/behat-builtin-extension/issues", - "source": "https://github.com/LibreSign/behat-builtin-extension/tree/v0.6.1" + "source": "https://github.com/LibreSign/behat-builtin-extension/tree/v0.6.2" }, - "time": "2023-12-10T00:06:52+00:00" + "time": "2024-03-14T20:24:42+00:00" }, { "name": "libresign/nextcloud-behat", - "version": "v0.11.0", + "version": "v0.11.1", "source": { "type": "git", "url": "https://github.com/LibreSign/nextcloud-behat.git", - "reference": "e1e90894119ffee9d9ca51bb11a429f76dace6d9" + "reference": "173dda228dc714122ed420f719f1ff20e733f71f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LibreSign/nextcloud-behat/zipball/e1e90894119ffee9d9ca51bb11a429f76dace6d9", - "reference": "e1e90894119ffee9d9ca51bb11a429f76dace6d9", + "url": "https://api.github.com/repos/LibreSign/nextcloud-behat/zipball/173dda228dc714122ed420f719f1ff20e733f71f", + "reference": "173dda228dc714122ed420f719f1ff20e733f71f", "shasum": "" }, "require": { "behat/behat": "^3.13", "estahn/json-query-wrapper": "*", "guzzlehttp/guzzle": "^7.8", - "libresign/behat-builtin-extension": "^0.6.1", + "libresign/behat-builtin-extension": "^0.6.2", "php": ">=8.0", "phpunit/phpunit": "^9.6" }, @@ -5239,9 +5238,9 @@ ], "support": { "issues": "https://github.com/LibreSign/nextcloud-behat/issues", - "source": "https://github.com/LibreSign/nextcloud-behat/tree/v0.11.0" + "source": "https://github.com/LibreSign/nextcloud-behat/tree/v0.11.1" }, - "time": "2024-03-09T19:41:46+00:00" + "time": "2024-03-14T20:29:08+00:00" }, { "name": "symfony/process", From f2d91e0ad95aca0653c708b5bd6a98c06ac3644f Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 17:38:10 -0300 Subject: [PATCH 15/50] Set the name of current user of GitHub Actions Signed-off-by: Vitor Mattos --- .github/workflows/behat.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/behat.yml b/.github/workflows/behat.yml index 49e0800400..16e7eaafc0 100644 --- a/.github/workflows/behat.yml +++ b/.github/workflows/behat.yml @@ -122,6 +122,6 @@ jobs: env: BEHAT_ROOT_DIR: ../../../../ run: | - export BEHAT_RUN_AS=$(ls -ld behat.yml | awk '{print $3}') + export BEHAT_RUN_AS=runner export BEHAT_VERBOSE="$RUNNER_DEBUG" vendor/bin/behat -f junit -f pretty --colors From 44bb86b2746fe3d87454d7a97805600695d918e6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 18:03:24 -0300 Subject: [PATCH 16/50] Add all possible names to root certificate Signed-off-by: Vitor Mattos --- tests/integration/features/account/signature.feature | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 2fd41a2be5..b63b3d9f64 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -2,13 +2,13 @@ Feature: account/signature Scenario: Create root certificate with OpenSSL engine using API Given as user "admin" And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/openssl" - | rootCert | {"commonName":"Common Name"} | + | rootCert | {"commonName":"Common Name","names":{"C":{"id":"C","value":"BR"},"ST":{"id":"ST","value":"State of Company"},"L":{"id":"L","value":"City name"},"O":{"id":"O","value":"Organization"},"OU":{"id":"OU","value":"Organizational Unit"}}} | And the response should have a status code 200 And sending "get" to ocs "/apps/libresign/api/v1/admin/certificate" And the response should have a status code 200 And the response should be a JSON array with the following mandatory values | key | value | - | rootCert | {"commonName":"Common Name","names":[]} | + | rootCert | {"commonName":"Common Name","names":[{"id":"C","value":"BR"},{"id":"ST","value":"State of Company"},{"id":"L","value":"City name"},{"id":"O","value":"Organization"},{"id":"OU","value":"Organizational Unit"}]} | | generated | true | Scenario: Create root certificate with CFSSL engine using API @@ -16,13 +16,13 @@ Feature: account/signature And run the command "config:app:set libresign certificate_engine --value cfssl" And run the command "libresign:install --cfssl" And sending "post" to ocs "/apps/libresign/api/v1/admin/certificate/cfssl" - | rootCert | {"commonName":"Common Name"} | + | rootCert | {"commonName":"Common Name","names":{"C":{"id":"C","value":"BR"},"ST":{"id":"ST","value":"State of Company"},"L":{"id":"L","value":"City name"},"O":{"id":"O","value":"Organization"},"OU":{"id":"OU","value":"Organizational Unit"}}} | And the response should have a status code 200 And sending "get" to ocs "/apps/libresign/api/v1/admin/certificate" And the response should have a status code 200 And the response should be a JSON array with the following mandatory values | key | value | - | rootCert | {"commonName":"Common Name","names":[]} | + | rootCert | {"commonName":"Common Name","names":[{"id":"C","value":"BR"},{"id":"ST","value":"State of Company"},{"id":"L","value":"City name"},{"id":"O","value":"Organization"},{"id":"OU","value":"Organizational Unit"}]} | | generated | true | Scenario: Create pfx with success with CFSSL From f5523635c58f16062b30aa6877906b99aa78c83e Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 18:22:29 -0300 Subject: [PATCH 17/50] show extensions data of cert Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/AEngineHandler.php | 3 +-- src/views/ReadCertificate.vue | 13 ++++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/Handler/CertificateEngine/AEngineHandler.php b/lib/Handler/CertificateEngine/AEngineHandler.php index b5aded892b..6068569613 100644 --- a/lib/Handler/CertificateEngine/AEngineHandler.php +++ b/lib/Handler/CertificateEngine/AEngineHandler.php @@ -133,9 +133,8 @@ public function readCertificate(string $certificate, string $privateKey): array if (is_array($return['subject']['OU']) && !empty($return['subject']['OU'])) { $return['subject']['OU'] = implode(', ', $return['subject']['OU']); } - $return['subjectAltName'] = $parsed['extensions']['subjectAltName']; $return['issuer'] = $parsed['issuer']; - $return['issuerInfoAccess'] = $parsed['extensions']['authorityInfoAccess']; + $return['extensions'] = $parsed['extensions']; $return['validate'] = [ 'from' => $this->dateTimeFormatter->formatDateTime($parsed['validFrom_time_t']), 'to' => $this->dateTimeFormatter->formatDateTime($parsed['validTo_time_t']), diff --git a/src/views/ReadCertificate.vue b/src/views/ReadCertificate.vue index 5368b216f0..a02d7615af 100644 --- a/src/views/ReadCertificate.vue +++ b/src/views/ReadCertificate.vue @@ -56,17 +56,13 @@ - - Info access - {{ certificateData.issuerInfoAccess }} - Name {{ certificateData.name }} - - Alt name - {{ certificateData.subjectAltName }} + + {{ name }} + {{ value }} @@ -184,6 +180,9 @@ td { padding: 5px; border-bottom: 1px solid var(--color-border); } +td:nth-child(2) { + word-break: break-all; +} th { font-weight: bold; From af6a0294ac8f862150957d0c7d72297a82ce43b6 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 19:39:14 -0300 Subject: [PATCH 18/50] Bump dependencies Signed-off-by: Vitor Mattos --- tests/integration/composer.json | 2 +- tests/integration/composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration/composer.json b/tests/integration/composer.json index 6145956dc9..fc8907bf54 100644 --- a/tests/integration/composer.json +++ b/tests/integration/composer.json @@ -9,7 +9,7 @@ "rpkamp/mailhog-behat-extension": "^1.0" }, "require-dev": { - "libresign/nextcloud-behat": "^0.11.0" + "libresign/nextcloud-behat": "^0.12.0" }, "config": { "allow-plugins": { diff --git a/tests/integration/composer.lock b/tests/integration/composer.lock index 1af0e112d3..eaecb3315d 100644 --- a/tests/integration/composer.lock +++ b/tests/integration/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "50bb633009c1c1b485543b2241cf7756", + "content-hash": "6f23626703cfda8ee02f4e9d949e8901", "packages": [ { "name": "behat/behat", @@ -5184,16 +5184,16 @@ }, { "name": "libresign/nextcloud-behat", - "version": "v0.11.1", + "version": "v0.12.0", "source": { "type": "git", "url": "https://github.com/LibreSign/nextcloud-behat.git", - "reference": "173dda228dc714122ed420f719f1ff20e733f71f" + "reference": "a78f0b41432c081ac5854bc70ad4c0be6305e506" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LibreSign/nextcloud-behat/zipball/173dda228dc714122ed420f719f1ff20e733f71f", - "reference": "173dda228dc714122ed420f719f1ff20e733f71f", + "url": "https://api.github.com/repos/LibreSign/nextcloud-behat/zipball/a78f0b41432c081ac5854bc70ad4c0be6305e506", + "reference": "a78f0b41432c081ac5854bc70ad4c0be6305e506", "shasum": "" }, "require": { @@ -5238,9 +5238,9 @@ ], "support": { "issues": "https://github.com/LibreSign/nextcloud-behat/issues", - "source": "https://github.com/LibreSign/nextcloud-behat/tree/v0.11.1" + "source": "https://github.com/LibreSign/nextcloud-behat/tree/v0.12.0" }, - "time": "2024-03-14T20:29:08+00:00" + "time": "2024-03-14T22:37:17+00:00" }, { "name": "symfony/process", From 5a344f6df1882e982ec7ce928c421701b2421229 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 20:00:28 -0300 Subject: [PATCH 19/50] Use jq to split tests Signed-off-by: Vitor Mattos --- .../integration/features/account/signature.feature | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index b63b3d9f64..f6b484c64b 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -7,9 +7,10 @@ Feature: account/signature And sending "get" to ocs "/apps/libresign/api/v1/admin/certificate" And the response should have a status code 200 And the response should be a JSON array with the following mandatory values - | key | value | - | rootCert | {"commonName":"Common Name","names":[{"id":"C","value":"BR"},{"id":"ST","value":"State of Company"},{"id":"L","value":"City name"},{"id":"O","value":"Organization"},{"id":"OU","value":"Organizational Unit"}]} | - | generated | true | + | key | value | + | (jq).rootCert.commonName | Common Name | + | (jq).rootCert.names | [{"id":"C","value":"BR"},{"id":"ST","value":"State of Company"},{"id":"L","value":"City name"},{"id":"O","value":"Organization"},{"id":"OU","value":"Organizational Unit"}] | + | generated | true | Scenario: Create root certificate with CFSSL engine using API Given as user "admin" @@ -21,9 +22,10 @@ Feature: account/signature And sending "get" to ocs "/apps/libresign/api/v1/admin/certificate" And the response should have a status code 200 And the response should be a JSON array with the following mandatory values - | key | value | - | rootCert | {"commonName":"Common Name","names":[{"id":"C","value":"BR"},{"id":"ST","value":"State of Company"},{"id":"L","value":"City name"},{"id":"O","value":"Organization"},{"id":"OU","value":"Organizational Unit"}]} | - | generated | true | + | key | value | + | (jq).rootCert.commonName | Common Name | + | (jq).rootCert.names | [{"id":"C","value":"BR"},{"id":"ST","value":"State of Company"},{"id":"L","value":"City name"},{"id":"O","value":"Organization"},{"id":"OU","value":"Organizational Unit"}] | + | generated | true | Scenario: Create pfx with success with CFSSL Given user "signer1" exists From 559c2d0f45d4c8aec06b3e3650838c8d1c3c7024 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 20:01:06 -0300 Subject: [PATCH 20/50] Generate root cert with same purpose as ICP-Brasil Signed-off-by: Vitor Mattos --- lib/Handler/CfsslServerHandler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Handler/CfsslServerHandler.php b/lib/Handler/CfsslServerHandler.php index 180068b0b5..e2c2b687e4 100644 --- a/lib/Handler/CfsslServerHandler.php +++ b/lib/Handler/CfsslServerHandler.php @@ -81,7 +81,8 @@ private function putConfigServer(string $key, string $configPath): void { 'usages' => [ "signing", "digital signature", - "cert sign" + "cert sign", + "key encipherment" ], ], ], From cc7f61cc03ec749fc3a3a363cc49b811573d9706 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 20:15:36 -0300 Subject: [PATCH 21/50] Fix linter Signed-off-by: Vitor Mattos --- src/views/ReadCertificate.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/ReadCertificate.vue b/src/views/ReadCertificate.vue index a02d7615af..8eb65c0c3b 100644 --- a/src/views/ReadCertificate.vue +++ b/src/views/ReadCertificate.vue @@ -180,6 +180,7 @@ td { padding: 5px; border-bottom: 1px solid var(--color-border); } + td:nth-child(2) { word-break: break-all; } From b31301a762bc1effddaa79d924f7ba8d96b91cad Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 20:54:29 -0300 Subject: [PATCH 22/50] Force kill Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/CfsslHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index c681d3e177..29a2f607cb 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -225,7 +225,7 @@ private function parseCommand(string $command): string { private function stopIfRunning(): void { $pid = $this->getServerPid(); if ($pid > 0) { - exec($this->parseCommand('kill ' . $pid)); + exec($this->parseCommand('kill -9 ' . $pid)); } } From 888c36379288232b36b21bcc45decb59ab34b2b5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 20:54:47 -0300 Subject: [PATCH 23/50] Add extended usage as ICP-Brasil Signed-off-by: Vitor Mattos --- lib/Handler/CfsslServerHandler.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Handler/CfsslServerHandler.php b/lib/Handler/CfsslServerHandler.php index e2c2b687e4..e9a8399906 100644 --- a/lib/Handler/CfsslServerHandler.php +++ b/lib/Handler/CfsslServerHandler.php @@ -82,7 +82,9 @@ private function putConfigServer(string $key, string $configPath): void { "signing", "digital signature", "cert sign", - "key encipherment" + "key encipherment", + "client auth", + "email protection" ], ], ], From df561fd3611cbc7e39f159b9f04d52c5ca49de54 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 20:55:04 -0300 Subject: [PATCH 24/50] Test extended usage Signed-off-by: Vitor Mattos --- .../features/account/signature.feature | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index f6b484c64b..94871c13cd 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -41,12 +41,13 @@ Feature: account/signature | key | value | | password | password | Then the response should be a JSON array with the following mandatory values - | key | value | - | name | /C=BR/ST=State of Company/O=Organization/CN=signer1-displayname | - | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | - | subjectAltName | DNS:signer1 | - | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | - | issuerInfoAccess | | + | key | value | + | name | /C=BR/ST=State of Company/O=Organization/CN=signer1-displayname | + | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | + | (jq).extensions.subjectAltName | DNS:signer1 | + | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | + | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | + | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | Scenario: Create pfx with success with OpenSSL Given user "signer1" exists @@ -60,12 +61,13 @@ Feature: account/signature | key | value | | password | password | Then the response should be a JSON array with the following mandatory values - | key | value | - | name | /CN=Common Name/O=Organization/C=BR/ST=State of Company | - | subject | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | - | subjectAltName | | - | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | - | issuerInfoAccess | | + | key | value | + | name | /CN=Common Name/O=Organization/C=BR/ST=State of Company | + | subject | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | + | (jq).extensions.subjectAltName | DNS:signer1 | + | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | + | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | + | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | Scenario: Upload PFX file with error Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" From 5e5f67179949c5b0fe8b2fdee414485d9c3b027d Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Thu, 14 Mar 2024 22:08:45 -0300 Subject: [PATCH 25/50] Use email as SAN (Subject Alternative Name) Signed-off-by: Vitor Mattos --- lib/Controller/AccountController.php | 11 +++++++++-- lib/Handler/Pkcs12Handler.php | 6 +++--- lib/Service/AccountService.php | 5 +++-- lib/Service/SignFileService.php | 5 +++-- tests/Api/Controller/SignFileControllerTest.php | 10 ++++++---- tests/integration/features/account/signature.feature | 4 ++-- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/Controller/AccountController.php b/lib/Controller/AccountController.php index 419eb34547..441763d97f 100644 --- a/lib/Controller/AccountController.php +++ b/lib/Controller/AccountController.php @@ -141,20 +141,27 @@ public function signatureGenerate( string $signPassword ): JSONResponse { try { + $identify = $this->userSession->getUser()->getEMailAddress(); + if (!$identify) { + $identify = $this->userSession->getUser()->getUID() + . '@' + . $this->request->getServerHost(); + } $data = [ 'user' => [ - 'identify' => $this->userSession->getUser()->getUID(), + 'host' => $identify, 'name' => $this->userSession->getUser()->getDisplayName(), ], 'signPassword' => $signPassword, 'userId' => $this->userSession->getUser()->getUID() ]; $this->accountService->validateCertificateData($data); - $this->pkcs12Handler->generateCertificate( + $certificate = $this->pkcs12Handler->generateCertificate( $data['user'], $data['signPassword'], $this->userSession->getUser()->getDisplayName() ); + $this->pkcs12Handler->savePfx($this->userSession->getUser()->getUID(), $certificate); return new JSONResponse([], Http::STATUS_OK); } catch (\Exception $exception) { diff --git a/lib/Handler/Pkcs12Handler.php b/lib/Handler/Pkcs12Handler.php index cf51efc16b..b4fd734a70 100644 --- a/lib/Handler/Pkcs12Handler.php +++ b/lib/Handler/Pkcs12Handler.php @@ -301,14 +301,14 @@ public function isHandlerOk(): bool { /** * Generate certificate * - * @param array $user Example: ['identify' => '', 'name' => ''] + * @param array $user Example: ['host' => '', 'name' => ''] * @param string $signPassword Password of signature * @param string $friendlyName Friendly name * @param bool $isTempFile */ public function generateCertificate(array $user, string $signPassword, string $friendlyName, bool $isTempFile = false): string { $content = $this->certificateEngineHandler->getEngine() - ->setHosts([$user['identify']]) + ->setHosts([$user['host']]) ->setCommonName($user['name']) ->setFriendlyName($friendlyName) ->setPassword($signPassword) @@ -319,6 +319,6 @@ public function generateCertificate(array $user, string $signPassword, string $f if ($isTempFile) { return $content; } - return $this->savePfx($user['identify'], $content); + return $content; } } diff --git a/lib/Service/AccountService.php b/lib/Service/AccountService.php index 9a8dd6626c..b6ccd9c6dd 100644 --- a/lib/Service/AccountService.php +++ b/lib/Service/AccountService.php @@ -227,14 +227,15 @@ public function createToSign(string $uuid, string $email, string $password, ?str } if ($signPassword) { - $this->pkcs12Handler->generateCertificate( + $certificate = $this->pkcs12Handler->generateCertificate( [ - 'identify' => $newUser->getPrimaryEMailAddress(), + 'host' => $newUser->getPrimaryEMailAddress(), 'name' => $newUser->getDisplayName() ], $signPassword, $newUser->getDisplayName() ); + $this->pkcs12Handler->savePfx($newUser->getPrimaryEMailAddress(), $certificate); } } diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index 38db477e29..cc180e79aa 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -349,15 +349,16 @@ private function getPfxFile(): string { $tempPassword = sha1((string) time()); $this->setPassword($tempPassword); try { - return $this->pkcs12Handler->generateCertificate( + $certificate = $this->pkcs12Handler->generateCertificate( [ - 'identify' => $this->userUniqueIdentifier, + 'host' => $this->userUniqueIdentifier, 'name' => $this->friendlyName, ], $tempPassword, $this->friendlyName, true ); + $this->pkcs12Handler->savePfx($this->userUniqueIdentifier, $certificate); } catch (TypeError $e) { throw new LibresignException($this->l10n->t('Failure to generate certificate')); } catch (EmptyRootCertificateException $e) { diff --git a/tests/Api/Controller/SignFileControllerTest.php b/tests/Api/Controller/SignFileControllerTest.php index d28d222e58..00a868c3fd 100644 --- a/tests/Api/Controller/SignFileControllerTest.php +++ b/tests/Api/Controller/SignFileControllerTest.php @@ -217,14 +217,15 @@ public function testSignUsingFileIdWithEmptyCertificatePassword() { 'userManager' => $user, ]); $pkcs12Handler = \OC::$server->get(\OCA\Libresign\Handler\Pkcs12Handler::class); - $pkcs12Handler->generateCertificate( + $certificate = $pkcs12Handler->generateCertificate( [ - 'identify' => 'person@test.coop', + 'host' => 'person@test.coop', 'name' => 'John Doe', ], 'secretPassword', 'username' ); + $pkcs12Handler->savePfx('person@test.coop', $certificate); $signers = $this->getSignersFromFileId($file->getId()); $this->request @@ -276,14 +277,15 @@ public function testSignUsingFileIdWithSuccess() { 'userManager' => $user, ]); $pkcs12Handler = \OC::$server->get(\OCA\Libresign\Handler\Pkcs12Handler::class); - $pkcs12Handler->generateCertificate( + $certificate = $pkcs12Handler->generateCertificate( [ - 'identify' => 'person@test.coop', + 'host' => 'person@test.coop', 'name' => 'John Doe', ], 'secretPassword', 'username' ); + $pkcs12Handler->savePfx('person@test.coop', $certificate); $mock = $this->createMock(JSignPDF::class); $mock->method('sign')->willReturn('content'); diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 94871c13cd..837ff748c8 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -44,7 +44,7 @@ Feature: account/signature | key | value | | name | /C=BR/ST=State of Company/O=Organization/CN=signer1-displayname | | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | - | (jq).extensions.subjectAltName | DNS:signer1 | + | (jq).extensions.subjectAltName | email:signer@domain.test | | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | @@ -64,7 +64,7 @@ Feature: account/signature | key | value | | name | /CN=Common Name/O=Organization/C=BR/ST=State of Company | | subject | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | - | (jq).extensions.subjectAltName | DNS:signer1 | + | (jq).extensions.subjectAltName | email:signer@domain.test | | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | From f6ab113dacb2cafbdf408d67629f7d6e952a83ff Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 09:23:17 -0300 Subject: [PATCH 26/50] Check if owner of file and user that rum the tests is the same Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 15 ++++++++++++--- .../features/bootstrap/FeatureContext.php | 3 +++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index b59d8112b9..cc9a450027 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -68,9 +68,18 @@ public function generateRootCert( openssl_x509_export($x509, $certout); openssl_pkey_export($privkey, $pkeyout); - file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca.csr', $csrout); - file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem', $certout); - file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem', $pkeyout); + $success = file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca.csr', $csrout); + if ($success === false) { + throw new LibresignException('Failure to save file. Check permission: ' . $configPath . DIRECTORY_SEPARATOR . 'ca.csr'); + } + $success = file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem', $certout); + if ($success === false) { + throw new LibresignException('Failure to save file. Check permission: ' . $configPath . DIRECTORY_SEPARATOR . 'ca.csr'); + } + $success = file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem', $pkeyout); + if ($success === false) { + throw new LibresignException('Failure to save file. Check permission: ' . $configPath . DIRECTORY_SEPARATOR . 'ca.csr'); + } return $pkeyout; } diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 341ba5ce19..986b6a003d 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -21,6 +21,9 @@ class FeatureContext extends NextcloudApiContext implements OpenedEmailStorageAw * @BeforeSuite */ public static function beforeSuite(BeforeSuiteScope $scope) { + if (get_current_user() !== exec('whoami')) { + throw new Exception(sprintf('Have files that %s is the owner.and the user that is running this test is %s, is necessary to be the same user', get_current_user(), exec('whoami'))); + } self::runCommand('config:system:set debug --value true --type boolean'); self::runCommand('app:enable --force notifications'); } From 45be0082462def80a3cfbb4706201e9acad2ae2a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 11:06:03 -0300 Subject: [PATCH 27/50] Reorder methods to follow the order of interface Signed-off-by: Vitor Mattos --- .../CertificateEngine/CfsslHandler.php | 176 +++++++++--------- .../CertificateEngine/OpenSslHandler.php | 42 ++--- 2 files changed, 108 insertions(+), 110 deletions(-) diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index 29a2f607cb..143eff2b8a 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -64,12 +64,32 @@ public function __construct( parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter); } - private function getClient(): Client { - if (!$this->client) { - $this->setClient(new Client(['base_uri' => $this->getCfsslUri()])); + public function generateRootCert( + string $commonName, + array $names = [] + ): string { + $key = bin2hex(random_bytes(16)); + + $configPath = $this->getConfigPath(); + $this->cfsslServerHandler->createConfigServer( + $commonName, + $names, + $key, + $configPath + ); + + $this->genkey(); + + $this->stopIfRunning(); + + for ($i = 1; $i <= 4; $i++) { + if ($this->isUp($this->getCfsslUri())) { + break; + } + sleep(2); } - $this->wakeUp(); - return $this->client; + + return $key; } public function generateCertificate(string $certificate = '', string $privateKey = ''): string { @@ -77,6 +97,52 @@ public function generateCertificate(string $certificate = '', string $privateKey return parent::generateCertificate($certKeys['certificate'], $certKeys['private_key']); } + public function isSetupOk(): bool { + if (!parent::isSetupOk()) { + return false; + }; + $configPath = $this->getConfigPath(); + $certificate = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); + $privateKey = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); + if (!$certificate || !$privateKey) { + return false; + } + try { + $this->getClient(); + return true; + } catch (\Throwable $th) { + } + return false; + } + + public function configureCheck(): array { + $return = $this->checkBinaries(); + $configPath = $this->getConfigPath(); + if (is_dir($configPath)) { + return array_merge( + $return, + [(new ConfigureCheckHelper()) + ->setSuccessMessage('Root certificate config files found.') + ->setResource('cfssl-configure')] + ); + } + return array_merge( + $return, + [(new ConfigureCheckHelper()) + ->setErrorMessage('CFSSL (root certificate) not configured.') + ->setResource('cfssl-configure') + ->setTip('Run occ libresign:configure:cfssl --help')] + ); + } + + public function toArray(): array { + $return = parent::toArray(); + if (!empty($return['configPath'])) { + $return['cfsslUri'] = $this->appConfig->getAppValue('cfssl_uri'); + } + return $return; + } + private function newCert(): array { $json = [ 'json' => [ @@ -120,6 +186,23 @@ private function newCert(): array { return $responseDecoded['result']; } + private function genkey(): void { + $binary = $this->getBinary(); + $configPath = $this->getConfigPath(); + $cmd = $binary . ' genkey ' . + '-initca=true ' . $configPath . DIRECTORY_SEPARATOR . 'csr_server.json | ' . + $binary . 'json -bare ' . $configPath . DIRECTORY_SEPARATOR . 'ca;'; + shell_exec($cmd); + } + + private function getClient(): Client { + if (!$this->client) { + $this->setClient(new Client(['base_uri' => $this->getCfsslUri()])); + } + $this->wakeUp(); + return $this->client; + } + private function isUp(): bool { try { $client = $this->getClient(); @@ -281,81 +364,6 @@ public function setCfsslUri($uri): void { $this->cfsslUri = $uri; } - private function genkey(): void { - $binary = $this->getBinary(); - $configPath = $this->getConfigPath(); - $cmd = $binary . ' genkey ' . - '-initca=true ' . $configPath . DIRECTORY_SEPARATOR . 'csr_server.json | ' . - $binary . 'json -bare ' . $configPath . DIRECTORY_SEPARATOR . 'ca;'; - shell_exec($cmd); - } - - public function generateRootCert( - string $commonName, - array $names = [] - ): string { - $key = bin2hex(random_bytes(16)); - - $configPath = $this->getConfigPath(); - $this->cfsslServerHandler->createConfigServer( - $commonName, - $names, - $key, - $configPath - ); - - $this->genkey(); - - $this->stopIfRunning(); - - for ($i = 1; $i <= 4; $i++) { - if ($this->isUp($this->getCfsslUri())) { - break; - } - sleep(2); - } - - return $key; - } - - public function isSetupOk(): bool { - if (!parent::isSetupOk()) { - return false; - }; - $configPath = $this->getConfigPath(); - $certificate = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); - $privateKey = file_exists($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); - if (!$certificate || !$privateKey) { - return false; - } - try { - $this->getClient(); - return true; - } catch (\Throwable $th) { - } - return false; - } - - public function configureCheck(): array { - $return = $this->checkBinaries(); - $configPath = $this->getConfigPath(); - if (is_dir($configPath)) { - return array_merge( - $return, - [(new ConfigureCheckHelper()) - ->setSuccessMessage('Root certificate config files found.') - ->setResource('cfssl-configure')] - ); - } - return array_merge( - $return, - [(new ConfigureCheckHelper()) - ->setErrorMessage('CFSSL (root certificate) not configured.') - ->setResource('cfssl-configure') - ->setTip('Run occ libresign:configure:cfssl --help')] - ); - } - private function checkBinaries(): array { if (PHP_OS_FAMILY === 'Windows') { return [ @@ -409,12 +417,4 @@ private function checkBinaries(): array { ->setResource('cfssl'); return $return; } - - public function toArray(): array { - $return = parent::toArray(); - if (!empty($return['configPath'])) { - $return['cfsslUri'] = $this->appConfig->getAppValue('cfssl_uri'); - } - return $return; - } } diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index cc9a450027..8cdc14595d 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -35,21 +35,10 @@ * @method CfsslHandler setClient(Client $client) */ class OpenSslHandler extends AEngineHandler implements IEngineHandler { - public function generateCertificate(string $certificate = '', string $privateKey = ''): string { - $configPath = $this->getConfigPath(); - $certificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); - $privateKey = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); - if (empty($certificate) || empty($privateKey)) { - throw new LibresignException('Invalid root certificate'); - } - return parent::generateCertificate($certificate, $privateKey); - } - public function generateRootCert( string $commonName, array $names = [], ): string { - $configPath = $this->getConfigPath(); $privkey = openssl_pkey_new([ 'private_key_bits' => 2048, @@ -68,20 +57,29 @@ public function generateRootCert( openssl_x509_export($x509, $certout); openssl_pkey_export($privkey, $pkeyout); - $success = file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca.csr', $csrout); - if ($success === false) { - throw new LibresignException('Failure to save file. Check permission: ' . $configPath . DIRECTORY_SEPARATOR . 'ca.csr'); - } - $success = file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem', $certout); - if ($success === false) { - throw new LibresignException('Failure to save file. Check permission: ' . $configPath . DIRECTORY_SEPARATOR . 'ca.csr'); + $this->saveFile('ca.csr', $csrout); + $this->saveFile('ca.pem', $certout); + $this->saveFile('ca-key.pem', $pkeyout); + + return $pkeyout; + } + + public function generateCertificate(string $certificate = '', string $privateKey = ''): string { + $configPath = $this->getConfigPath(); + $certificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); + $privateKey = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); + if (empty($certificate) || empty($privateKey)) { + throw new LibresignException('Invalid root certificate'); } - $success = file_put_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem', $pkeyout); + return parent::generateCertificate($certificate, $privateKey); + } + + private function saveFile(string $filename, string $content): void { + $configPath = $this->getConfigPath(); + $success = file_put_contents($configPath . DIRECTORY_SEPARATOR . $filename, $content); if ($success === false) { - throw new LibresignException('Failure to save file. Check permission: ' . $configPath . DIRECTORY_SEPARATOR . 'ca.csr'); + throw new LibresignException('Failure to save file. Check permission: ' . $configPath . DIRECTORY_SEPARATOR . $filename); } - - return $pkeyout; } public function isSetupOk(): bool { From 638bc9ac65285d90e7bae3351bd6e5545a712ede Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 11:34:18 -0300 Subject: [PATCH 28/50] Rename method, change visibility and fix arguments The sign method is not to create certificate Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/AEngineHandler.php | 4 ++-- lib/Handler/CertificateEngine/CfsslHandler.php | 4 ++-- lib/Handler/CertificateEngine/IEngineHandler.php | 2 +- lib/Handler/CertificateEngine/OpenSslHandler.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Handler/CertificateEngine/AEngineHandler.php b/lib/Handler/CertificateEngine/AEngineHandler.php index 6068569613..7bb11966d1 100644 --- a/lib/Handler/CertificateEngine/AEngineHandler.php +++ b/lib/Handler/CertificateEngine/AEngineHandler.php @@ -82,7 +82,7 @@ public function __construct( $this->appData = $appDataFactory->get('libresign'); } - public function generateCertificate(string $certificate, string $privateKey): string { + protected function signCertificate(string $certificate, string $privateKey): string { if (empty($certificate) || empty($privateKey)) { throw new EmptyRootCertificateException(); } @@ -114,7 +114,7 @@ public function updatePassword(string $certificate, string $currentPrivateKey, s throw new InvalidPasswordException(); } $this->setPassword($newPrivateKey); - $certContent = self::generateCertificate($certContent['cert'], $certContent['pkey']); + $certContent = self::signCertificate($certContent['cert'], $certContent['pkey']); return $certContent; } diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index 143eff2b8a..7296232b64 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -92,9 +92,9 @@ public function generateRootCert( return $key; } - public function generateCertificate(string $certificate = '', string $privateKey = ''): string { + public function generateCertificate(): string { $certKeys = $this->newCert(); - return parent::generateCertificate($certKeys['certificate'], $certKeys['private_key']); + return parent::signCertificate($certKeys['certificate'], $certKeys['private_key']); } public function isSetupOk(): bool { diff --git a/lib/Handler/CertificateEngine/IEngineHandler.php b/lib/Handler/CertificateEngine/IEngineHandler.php index 655833abea..0420905cee 100644 --- a/lib/Handler/CertificateEngine/IEngineHandler.php +++ b/lib/Handler/CertificateEngine/IEngineHandler.php @@ -51,7 +51,7 @@ public function generateRootCert( array $names = [], ): string; - public function generateCertificate(string $certificate = '', string $privateKey = ''): string; + public function generateCertificate(): string; public function updatePassword(string $certificate, string $currentPrivateKey, string $newPrivateKey): string; diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 8cdc14595d..a023d52240 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -64,7 +64,7 @@ public function generateRootCert( return $pkeyout; } - public function generateCertificate(string $certificate = '', string $privateKey = ''): string { + public function generateCertificate(): string { $configPath = $this->getConfigPath(); $certificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); $privateKey = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); From 4ee2338603467e0bc6a5d7e989523ca938aa967a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 11:36:50 -0300 Subject: [PATCH 29/50] Rename exception This is not about root certificate Signed-off-by: Vitor Mattos --- ...ificateException.php => EmptyCertificateException.php} | 2 +- lib/Handler/CertificateEngine/AEngineHandler.php | 8 ++++---- lib/Service/SignFileService.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename lib/Exception/{EmptyRootCertificateException.php => EmptyCertificateException.php} (94%) diff --git a/lib/Exception/EmptyRootCertificateException.php b/lib/Exception/EmptyCertificateException.php similarity index 94% rename from lib/Exception/EmptyRootCertificateException.php rename to lib/Exception/EmptyCertificateException.php index 931a6b9fe3..1401e5dd44 100644 --- a/lib/Exception/EmptyRootCertificateException.php +++ b/lib/Exception/EmptyCertificateException.php @@ -24,5 +24,5 @@ namespace OCA\Libresign\Exception; -class EmptyRootCertificateException extends \Exception { +class EmptyCertificateException extends \Exception { } diff --git a/lib/Handler/CertificateEngine/AEngineHandler.php b/lib/Handler/CertificateEngine/AEngineHandler.php index 7bb11966d1..62c92ee189 100644 --- a/lib/Handler/CertificateEngine/AEngineHandler.php +++ b/lib/Handler/CertificateEngine/AEngineHandler.php @@ -24,7 +24,7 @@ namespace OCA\Libresign\Handler\CertificateEngine; -use OCA\Libresign\Exception\EmptyRootCertificateException; +use OCA\Libresign\Exception\EmptyCertificateException; use OCA\Libresign\Exception\InvalidPasswordException; use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Helper\MagicGetterSetterTrait; @@ -84,7 +84,7 @@ public function __construct( protected function signCertificate(string $certificate, string $privateKey): string { if (empty($certificate) || empty($privateKey)) { - throw new EmptyRootCertificateException(); + throw new EmptyCertificateException(); } $certContent = null; try { @@ -107,7 +107,7 @@ protected function signCertificate(string $certificate, string $privateKey): str public function updatePassword(string $certificate, string $currentPrivateKey, string $newPrivateKey): string { if (empty($certificate) || empty($currentPrivateKey) || empty($newPrivateKey)) { - throw new EmptyRootCertificateException(); + throw new EmptyCertificateException(); } openssl_pkcs12_read($certificate, $certContent, $currentPrivateKey); if (empty($certContent)) { @@ -120,7 +120,7 @@ public function updatePassword(string $certificate, string $currentPrivateKey, s public function readCertificate(string $certificate, string $privateKey): array { if (empty($certificate) || empty($privateKey)) { - throw new EmptyRootCertificateException(); + throw new EmptyCertificateException(); } openssl_pkcs12_read($certificate, $certContent, $privateKey); if (empty($certContent)) { diff --git a/lib/Service/SignFileService.php b/lib/Service/SignFileService.php index cc180e79aa..25e4d59e53 100644 --- a/lib/Service/SignFileService.php +++ b/lib/Service/SignFileService.php @@ -39,7 +39,7 @@ use OCA\Libresign\Db\SignRequestMapper; use OCA\Libresign\Db\UserElementMapper; use OCA\Libresign\Events\SignedEvent; -use OCA\Libresign\Exception\EmptyRootCertificateException; +use OCA\Libresign\Exception\EmptyCertificateException; use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Handler\PdfTk\Pdf; use OCA\Libresign\Handler\Pkcs12Handler; @@ -361,7 +361,7 @@ private function getPfxFile(): string { $this->pkcs12Handler->savePfx($this->userUniqueIdentifier, $certificate); } catch (TypeError $e) { throw new LibresignException($this->l10n->t('Failure to generate certificate')); - } catch (EmptyRootCertificateException $e) { + } catch (EmptyCertificateException $e) { throw new LibresignException($this->l10n->t('Empty root certificate data')); } catch (InvalidArgumentException $e) { throw new LibresignException($this->l10n->t('Invalid data to generate certificate')); From 355a92f93d95b4e84c8801de4b4cfd88b8ebc393 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 11:38:19 -0300 Subject: [PATCH 30/50] Reorder steps Put issuer before subject to have a logic sequence: first is generated the root certificate, then, I think that is best to test this before. Signed-off-by: Vitor Mattos --- tests/integration/features/account/signature.feature | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 837ff748c8..b2fadb356f 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -43,9 +43,9 @@ Feature: account/signature Then the response should be a JSON array with the following mandatory values | key | value | | name | /C=BR/ST=State of Company/O=Organization/CN=signer1-displayname | + | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | | (jq).extensions.subjectAltName | email:signer@domain.test | - | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | @@ -63,9 +63,9 @@ Feature: account/signature Then the response should be a JSON array with the following mandatory values | key | value | | name | /CN=Common Name/O=Organization/C=BR/ST=State of Company | - | subject | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | - | (jq).extensions.subjectAltName | email:signer@domain.test | | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | + | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | + | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | From 8c43d7ccf4e655400a0cd4ff8ace6b75325ff5f5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 11:45:23 -0300 Subject: [PATCH 31/50] Rename method Use a similar name to function that is executed Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/AEngineHandler.php | 4 ++-- lib/Handler/CertificateEngine/CfsslHandler.php | 2 +- lib/Handler/CertificateEngine/OpenSslHandler.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Handler/CertificateEngine/AEngineHandler.php b/lib/Handler/CertificateEngine/AEngineHandler.php index 62c92ee189..973e8d2bc4 100644 --- a/lib/Handler/CertificateEngine/AEngineHandler.php +++ b/lib/Handler/CertificateEngine/AEngineHandler.php @@ -82,7 +82,7 @@ public function __construct( $this->appData = $appDataFactory->get('libresign'); } - protected function signCertificate(string $certificate, string $privateKey): string { + protected function exportToPkcs12(string $certificate, string $privateKey): string { if (empty($certificate) || empty($privateKey)) { throw new EmptyCertificateException(); } @@ -114,7 +114,7 @@ public function updatePassword(string $certificate, string $currentPrivateKey, s throw new InvalidPasswordException(); } $this->setPassword($newPrivateKey); - $certContent = self::signCertificate($certContent['cert'], $certContent['pkey']); + $certContent = self::exportToPkcs12($certContent['cert'], $certContent['pkey']); return $certContent; } diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index 7296232b64..2e3c0d4f3d 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -94,7 +94,7 @@ public function generateRootCert( public function generateCertificate(): string { $certKeys = $this->newCert(); - return parent::signCertificate($certKeys['certificate'], $certKeys['private_key']); + return parent::exportToPkcs12($certKeys['certificate'], $certKeys['private_key']); } public function isSetupOk(): bool { diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index a023d52240..7535e4a747 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -71,7 +71,7 @@ public function generateCertificate(): string { if (empty($certificate) || empty($privateKey)) { throw new LibresignException('Invalid root certificate'); } - return parent::generateCertificate($certificate, $privateKey); + return parent::exportToPkcs12($certificate, $privateKey); } private function saveFile(string $filename, string $content): void { From 545efb2966c785d906de4319c8ba55769d0c6f96 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 11:52:44 -0300 Subject: [PATCH 32/50] Change var name This is about root cert Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 7535e4a747..d061962778 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -66,9 +66,9 @@ public function generateRootCert( public function generateCertificate(): string { $configPath = $this->getConfigPath(); - $certificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); - $privateKey = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); - if (empty($certificate) || empty($privateKey)) { + $rootCertificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); + $rootPrivateKey = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca-key.pem'); + if (empty($rootCertificate) || empty($rootPrivateKey)) { throw new LibresignException('Invalid root certificate'); } return parent::exportToPkcs12($certificate, $privateKey); From 41cf28269f4a15e4b661d09aa385081d0ee18ef4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 12:03:19 -0300 Subject: [PATCH 33/50] Do not use abbreviation Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index d061962778..1d4b1fb36f 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -40,7 +40,7 @@ public function generateRootCert( array $names = [], ): string { - $privkey = openssl_pkey_new([ + $privateKey = openssl_pkey_new([ 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); @@ -50,12 +50,12 @@ public function generateRootCert( $dn[$key] = $value['value']; } - $csr = openssl_csr_new($dn, $privkey, array('digest_alg' => 'sha256')); - $x509 = openssl_csr_sign($csr, null, $privkey, $days = 365 * 5, array('digest_alg' => 'sha256')); + $csr = openssl_csr_new($dn, $privateKey, array('digest_alg' => 'sha256')); + $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, array('digest_alg' => 'sha256')); openssl_csr_export($csr, $csrout); openssl_x509_export($x509, $certout); - openssl_pkey_export($privkey, $pkeyout); + openssl_pkey_export($privateKey, $pkeyout); $this->saveFile('ca.csr', $csrout); $this->saveFile('ca.pem', $certout); From dda17f25abba2b46b4d5eb9b904701d543a1e288 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 12:06:35 -0300 Subject: [PATCH 34/50] Use short array syntax Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 1d4b1fb36f..d98cc7c011 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -50,8 +50,8 @@ public function generateRootCert( $dn[$key] = $value['value']; } - $csr = openssl_csr_new($dn, $privateKey, array('digest_alg' => 'sha256')); - $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, array('digest_alg' => 'sha256')); + $csr = openssl_csr_new($dn, $privateKey, ['digest_alg' => 'sha256']); + $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, ['digest_alg' => 'sha256']); openssl_csr_export($csr, $csrout); openssl_x509_export($x509, $certout); From 65319dc0812c403081f5ffa88271b4b0d80ee5bf Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 14:30:54 -0300 Subject: [PATCH 35/50] Accept objects as argument Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/AEngineHandler.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Handler/CertificateEngine/AEngineHandler.php b/lib/Handler/CertificateEngine/AEngineHandler.php index 973e8d2bc4..269148f29a 100644 --- a/lib/Handler/CertificateEngine/AEngineHandler.php +++ b/lib/Handler/CertificateEngine/AEngineHandler.php @@ -34,6 +34,8 @@ use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IConfig; use OCP\IDateTimeFormatter; +use OpenSSLAsymmetricKey; +use OpenSSLCertificate; use ReflectionClass; /** @@ -82,7 +84,10 @@ public function __construct( $this->appData = $appDataFactory->get('libresign'); } - protected function exportToPkcs12(string $certificate, string $privateKey): string { + protected function exportToPkcs12( + OpenSSLCertificate|string $certificate, + OpenSSLAsymmetricKey|OpenSSLCertificate|string $privateKey + ): string { if (empty($certificate) || empty($privateKey)) { throw new EmptyCertificateException(); } From 98ac73b840c88e1f7b82dcc7861ad7ac93af244a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 14:34:50 -0300 Subject: [PATCH 36/50] Fix names of OpenSSL Signed-off-by: Vitor Mattos --- .../CertificateEngine/OpenSslHandler.php | 40 +++++++++++++++---- .../features/account/signature.feature | 2 +- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index d98cc7c011..718b1205fe 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -39,18 +39,12 @@ public function generateRootCert( string $commonName, array $names = [], ): string { - $privateKey = openssl_pkey_new([ 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); - $dn['commonName'] = $commonName; - foreach ($names as $key => $value) { - $dn[$key] = $value['value']; - } - - $csr = openssl_csr_new($dn, $privateKey, ['digest_alg' => 'sha256']); + $csr = openssl_csr_new($this->getNames(), $privateKey, ['digest_alg' => 'sha256']); $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, ['digest_alg' => 'sha256']); openssl_csr_export($csr, $csrout); @@ -71,7 +65,37 @@ public function generateCertificate(): string { if (empty($rootCertificate) || empty($rootPrivateKey)) { throw new LibresignException('Invalid root certificate'); } - return parent::exportToPkcs12($certificate, $privateKey); + + $privateKey = openssl_pkey_new([ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); + $csr = openssl_csr_new($this->getNames(), $privateKey); + $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365); + return parent::exportToPkcs12($x509, $privateKey); + } + + /** + * Convert to names as necessary to OpenSSL + * + * Read more here: https://www.php.net/manual/en/function.openssl-csr-new.php + */ + protected function getNames(): array { + $distinguishedNames = []; + $names = parent::getNames(); + foreach ($names as $name => $value) { + if ($name === 'ST') { + $distinguishedNames['stateOrProvinceName'] = $value; + continue; + } + $longName = $this->translateToLong($name); + $longName = lcfirst($longName) . 'Name'; + $distinguishedNames[$longName] = $value; + } + if ($this->getCommonName()) { + $distinguishedNames['commonName'] = $this->getCommonName(); + } + return $distinguishedNames; } private function saveFile(string $filename, string $content): void { diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index b2fadb356f..17f7976f7f 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -62,7 +62,7 @@ Feature: account/signature | password | password | Then the response should be a JSON array with the following mandatory values | key | value | - | name | /CN=Common Name/O=Organization/C=BR/ST=State of Company | + | name | /C=BR/ST=State of Company/O=Organization/CN=signer1-displayname | | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | | (jq).extensions.subjectAltName | email:signer@domain.test | From 65fb8388f14f928a40e40eff5ba1eedc8b1a5d19 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 15:05:20 -0300 Subject: [PATCH 37/50] Add location to tests Signed-off-by: Vitor Mattos --- .../features/account/signature.feature | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 17f7976f7f..8ee41216d5 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -33,7 +33,7 @@ Feature: account/signature And as user "signer1" And run the command "config:app:set libresign certificate_engine --value cfssl" And run the command "libresign:install --cfssl" - And run the command "libresign:configure:cfssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + And run the command "libresign:configure:cfssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" And sending "post" to ocs "/apps/libresign/api/v1/account/signature" | signPassword | password | And the response should have a status code 200 @@ -42,9 +42,9 @@ Feature: account/signature | password | password | Then the response should be a JSON array with the following mandatory values | key | value | - | name | /C=BR/ST=State of Company/O=Organization/CN=signer1-displayname | - | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | - | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | + | name | /C=BR/ST=State of Company/L=City Name/O=Organization/CN=signer1-displayname | + | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | + | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | @@ -53,7 +53,7 @@ Feature: account/signature Given user "signer1" exists And set the email of user "signer1" to "signer@domain.test" And as user "signer1" - And run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + And run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" And sending "post" to ocs "/apps/libresign/api/v1/account/signature" | signPassword | password | And the response should have a status code 200 @@ -62,15 +62,15 @@ Feature: account/signature | password | password | Then the response should be a JSON array with the following mandatory values | key | value | - | name | /C=BR/ST=State of Company/O=Organization/CN=signer1-displayname | - | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","O": "Organization"} | - | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","O": "Organization"} | + | name | /C=BR/ST=State of Company/L=City Name/O=Organization/CN=signer1-displayname | + | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | + | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | Scenario: Upload PFX file with error - Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" And user "signer1" exists And as user "signer1" When sending "post" to ocs "/apps/libresign/api/v1/account/pfx" @@ -80,7 +80,7 @@ Feature: account/signature | message | No certificate file provided | Scenario: Change pfx password with success - Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" And user "signer1" exists And as user "signer1" And sending "post" to ocs "/apps/libresign/api/v1/account/signature" @@ -102,7 +102,7 @@ Feature: account/signature | message | New password to sign documents has been created | Scenario: Delete pfx password with success - Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company" + Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" And user "signer1" exists And as user "signer1" And sending "post" to ocs "/apps/libresign/api/v1/account/signature" From 7126df5f43c7346643ddee33ecf253dbec6334f3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 15:23:25 -0300 Subject: [PATCH 38/50] Temporary comment steps that is failing Signed-off-by: Vitor Mattos --- tests/integration/features/account/signature.feature | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 8ee41216d5..ff1c928d4b 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -65,9 +65,9 @@ Feature: account/signature | name | /C=BR/ST=State of Company/L=City Name/O=Organization/CN=signer1-displayname | | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | - | (jq).extensions.subjectAltName | email:signer@domain.test | - | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | - | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | + # | (jq).extensions.subjectAltName | email:signer@domain.test | + # | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | + # | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | Scenario: Upload PFX file with error Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" From e87fa7ca9e80e4571b986f10644d1cc72e3f7d4b Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 15:23:52 -0300 Subject: [PATCH 39/50] Convert the signer cert generated by OpenSSL to CA:false Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 718b1205fe..ba1fe21c91 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -71,7 +71,11 @@ public function generateCertificate(): string { 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); $csr = openssl_csr_new($this->getNames(), $privateKey); - $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365); + $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365, [ + // This will set "basicConstraints" to CA:FALSE, the default is CA:TRUE + // The signer certificate is not a Certificate Authority + 'x509_extensions' => 'v3_req', + ]); return parent::exportToPkcs12($x509, $privateKey); } From 3e17e91179fa00b9de55c70e96240c36502db936 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 15:27:05 -0300 Subject: [PATCH 40/50] Cover previous commit with integration test Signed-off-by: Vitor Mattos --- tests/integration/features/account/signature.feature | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index ff1c928d4b..2a30e1f3f5 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -45,6 +45,7 @@ Feature: account/signature | name | /C=BR/ST=State of Company/L=City Name/O=Organization/CN=signer1-displayname | | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | + | (jq).extensions.basicConstraints | CA:FALSE | | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | @@ -65,6 +66,7 @@ Feature: account/signature | name | /C=BR/ST=State of Company/L=City Name/O=Organization/CN=signer1-displayname | | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | + | (jq).extensions.basicConstraints | CA:FALSE | # | (jq).extensions.subjectAltName | email:signer@domain.test | # | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | # | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | From b97f071fe6ba944df335432f9ce3b19a14656a23 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 15:30:55 -0300 Subject: [PATCH 41/50] fix integration tests Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index ba1fe21c91..010e2b5d6c 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -44,7 +44,7 @@ public function generateRootCert( 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); - $csr = openssl_csr_new($this->getNames(), $privateKey, ['digest_alg' => 'sha256']); + $csr = openssl_csr_new($this->getCsrNames(), $privateKey, ['digest_alg' => 'sha256']); $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, ['digest_alg' => 'sha256']); openssl_csr_export($csr, $csrout); @@ -70,7 +70,7 @@ public function generateCertificate(): string { 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); - $csr = openssl_csr_new($this->getNames(), $privateKey); + $csr = openssl_csr_new($this->getCsrNames(), $privateKey); $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365, [ // This will set "basicConstraints" to CA:FALSE, the default is CA:TRUE // The signer certificate is not a Certificate Authority @@ -84,7 +84,7 @@ public function generateCertificate(): string { * * Read more here: https://www.php.net/manual/en/function.openssl-csr-new.php */ - protected function getNames(): array { + protected function getCsrNames(): array { $distinguishedNames = []; $names = parent::getNames(); foreach ($names as $name => $value) { From 05174d14e121caa31871d282370aedec05fe3f90 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 15:35:18 -0300 Subject: [PATCH 42/50] Fix table Signed-off-by: Vitor Mattos --- tests/integration/features/admin/certificate_openssl.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/features/admin/certificate_openssl.feature b/tests/integration/features/admin/certificate_openssl.feature index 5ddb8fc970..31e461bbb5 100644 --- a/tests/integration/features/admin/certificate_openssl.feature +++ b/tests/integration/features/admin/certificate_openssl.feature @@ -19,6 +19,6 @@ Feature: admin/certificate_openssl Then sending "get" to ocs "/apps/libresign/api/v1/admin/certificate" And the response should have a status code 200 And the response should be a JSON array with the following mandatory values - | key | value | + | key | value | | rootCert | {"commonName":"Common Name","names":[{"id":"C","value":"BR"}]} | - | generated | true | + | generated | true | From 1a1c779aea06a92e4f13244aac4af100117157c3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 17:52:07 -0300 Subject: [PATCH 43/50] Make method private Is not necessary to be used outside of this class context Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 010e2b5d6c..29fc402232 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -79,12 +79,21 @@ public function generateCertificate(): string { return parent::exportToPkcs12($x509, $privateKey); } + private function getSubjectAltNames(): string { + $hosts = $this->getHosts(); + $altNames = []; + foreach ($hosts as $email) { + $altNames[] = 'email:' . $email; + } + return implode(', ', $altNames); + } + /** * Convert to names as necessary to OpenSSL * * Read more here: https://www.php.net/manual/en/function.openssl-csr-new.php */ - protected function getCsrNames(): array { + private function getCsrNames(): array { $distinguishedNames = []; $names = parent::getNames(); foreach ($names as $name => $value) { From d31bbf92be0bf52dd933f7a105c0611ba06d8ff1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 17:53:04 -0300 Subject: [PATCH 44/50] Make to work: subjectAltName, keyUsage and extendedKeyUsage Signed-off-by: Vitor Mattos --- .../CertificateEngine/OpenSslHandler.php | 24 +++++++++++++++++++ .../features/account/signature.feature | 6 ++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 29fc402232..48fba55d65 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -26,6 +26,11 @@ use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Helper\ConfigureCheckHelper; +use OCP\AppFramework\Services\IAppConfig; +use OCP\Files\AppData\IAppDataFactory; +use OCP\IConfig; +use OCP\IDateTimeFormatter; +use OCP\ITempManager; /** * Class FileMapper @@ -35,6 +40,16 @@ * @method CfsslHandler setClient(Client $client) */ class OpenSslHandler extends AEngineHandler implements IEngineHandler { + public function __construct( + protected IConfig $config, + protected IAppConfig $appConfig, + protected IAppDataFactory $appDataFactory, + protected IDateTimeFormatter $dateTimeFormatter, + protected ITempManager $tempManager, + ) { + parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter); + } + public function generateRootCert( string $commonName, array $names = [], @@ -70,8 +85,17 @@ public function generateCertificate(): string { 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); + $temporaryFile = $this->tempManager->getTemporaryFile('.cfg'); + file_put_contents($temporaryFile, <<getSubjectAltNames()} + CONFIG); $csr = openssl_csr_new($this->getCsrNames(), $privateKey); $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365, [ + 'config' => $temporaryFile, // This will set "basicConstraints" to CA:FALSE, the default is CA:TRUE // The signer certificate is not a Certificate Authority 'x509_extensions' => 'v3_req', diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 2a30e1f3f5..5dfdfc82ec 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -67,9 +67,9 @@ Feature: account/signature | issuer | {"CN": "Common Name","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | | subject | {"CN": "signer1-displayname","C": "BR","ST": "State of Company","L":"City Name","O": "Organization"} | | (jq).extensions.basicConstraints | CA:FALSE | - # | (jq).extensions.subjectAltName | email:signer@domain.test | - # | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | - # | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | + | (jq).extensions.subjectAltName | email:signer@domain.test | + | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | + | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | Scenario: Upload PFX file with error Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" From 927110c98aa943598074545644187995f97401b4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 18:07:39 -0300 Subject: [PATCH 45/50] Add authorityKeyIdentifier Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 1 + tests/integration/features/account/signature.feature | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 48fba55d65..89cea244a1 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -92,6 +92,7 @@ public function generateCertificate(): string { keyUsage = digitalSignature, keyEncipherment, keyCertSign extendedKeyUsage = clientAuth, emailProtection subjectAltName = {$this->getSubjectAltNames()} + authorityKeyIdentifier = keyid CONFIG); $csr = openssl_csr_new($this->getCsrNames(), $privateKey); $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365, [ diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 5dfdfc82ec..e871d34374 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -49,6 +49,7 @@ Feature: account/signature | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | + | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | Scenario: Create pfx with success with OpenSSL Given user "signer1" exists @@ -70,6 +71,7 @@ Feature: account/signature | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | + | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | Scenario: Upload PFX file with error Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" From 50b905b46185edc290f545d4ddd3cb35250bd46b Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 18:16:00 -0300 Subject: [PATCH 46/50] Add subjectKeyIdentifier to OpenSSL Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 1 + tests/integration/features/account/signature.feature | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 89cea244a1..15357ef2ec 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -93,6 +93,7 @@ public function generateCertificate(): string { extendedKeyUsage = clientAuth, emailProtection subjectAltName = {$this->getSubjectAltNames()} authorityKeyIdentifier = keyid + subjectKeyIdentifier = hash CONFIG); $csr = openssl_csr_new($this->getCsrNames(), $privateKey); $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365, [ diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index e871d34374..8e1707eb7e 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -49,7 +49,8 @@ Feature: account/signature | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | - | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | + | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | + | (jq).extensions | (jq).subjectKeyIdentifier != "" | Scenario: Create pfx with success with OpenSSL Given user "signer1" exists @@ -71,7 +72,8 @@ Feature: account/signature | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | - | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | + | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | + | (jq).extensions | (jq).subjectKeyIdentifier != "" | Scenario: Upload PFX file with error Given run the command "libresign:configure:openssl --cn=Common\ Name --c=BR --o=Organization --st=State\ of\ Company --l=City\ Name" From bd54327341e004d5a0c0fa838cfa1ed43afc0d66 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 18:29:14 -0300 Subject: [PATCH 47/50] add policy commented Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 15357ef2ec..2c3febb2ee 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -94,6 +94,7 @@ public function generateCertificate(): string { subjectAltName = {$this->getSubjectAltNames()} authorityKeyIdentifier = keyid subjectKeyIdentifier = hash + # certificatePolicies = CPS: http://url/with/policy/informations.pdf CONFIG); $csr = openssl_csr_new($this->getCsrNames(), $privateKey); $x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, 365, [ From ad911f8a11be8a4d3fb26b91176a1c5f60fb1e4c Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 18:30:22 -0300 Subject: [PATCH 48/50] Add documentation about x509v3 Signed-off-by: Vitor Mattos --- lib/Handler/CertificateEngine/OpenSslHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 2c3febb2ee..c9a519a57a 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -86,6 +86,7 @@ public function generateCertificate(): string { 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); $temporaryFile = $this->tempManager->getTemporaryFile('.cfg'); + // More information about x509v3: https://www.openssl.org/docs/manmaster/man5/x509v3_config.html file_put_contents($temporaryFile, << Date: Sat, 16 Mar 2024 18:35:15 -0300 Subject: [PATCH 49/50] Change scenario name Signed-off-by: Vitor Mattos --- tests/integration/features/account/signature.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index 8e1707eb7e..d5efcaf469 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -27,7 +27,7 @@ Feature: account/signature | (jq).rootCert.names | [{"id":"C","value":"BR"},{"id":"ST","value":"State of Company"},{"id":"L","value":"City name"},{"id":"O","value":"Organization"},{"id":"OU","value":"Organizational Unit"}] | | generated | true | - Scenario: Create pfx with success with CFSSL + Scenario: Create pfx with success using CFSSL Given user "signer1" exists And set the email of user "signer1" to "signer@domain.test" And as user "signer1" @@ -52,7 +52,7 @@ Feature: account/signature | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | | (jq).extensions | (jq).subjectKeyIdentifier != "" | - Scenario: Create pfx with success with OpenSSL + Scenario: Create pfx with success using OpenSSL Given user "signer1" exists And set the email of user "signer1" to "signer@domain.test" And as user "signer1" From a2771facf936b0d1ed7c81134e4e5e7751963bb9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Sat, 16 Mar 2024 19:01:48 -0300 Subject: [PATCH 50/50] Match using regex Signed-off-by: Vitor Mattos --- tests/integration/features/account/signature.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/features/account/signature.feature b/tests/integration/features/account/signature.feature index d5efcaf469..d80172c8ea 100644 --- a/tests/integration/features/account/signature.feature +++ b/tests/integration/features/account/signature.feature @@ -49,7 +49,7 @@ Feature: account/signature | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | - | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | + | (jq).extensions | (jq).authorityKeyIdentifier \| test("([0-9A-F]{2}:)+[0-9A-F]{2}") | | (jq).extensions | (jq).subjectKeyIdentifier != "" | Scenario: Create pfx with success using OpenSSL @@ -72,7 +72,7 @@ Feature: account/signature | (jq).extensions.subjectAltName | email:signer@domain.test | | (jq).extensions.keyUsage | Digital Signature, Key Encipherment, Certificate Sign | | (jq).extensions.extendedKeyUsage | TLS Web Client Authentication, E-mail Protection | - | (jq).extensions.authorityKeyIdentifier \| capture("(?(?:[^:])*)").keyid | keyid | + | (jq).extensions | (jq).authorityKeyIdentifier \| test("([0-9A-F]{2}:)+[0-9A-F]{2}") | | (jq).extensions | (jq).subjectKeyIdentifier != "" | Scenario: Upload PFX file with error