From 9d1060bd4ca60c71e78009323a09efd8a414b988 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 11 Apr 2025 17:30:24 -0300 Subject: [PATCH 01/15] feat: manage certificate policy Signed-off-by: Vitor Mattos --- .../CertificatePolicyController.php | 106 ++++++++++++++++++ lib/Service/CertificatePolicyService.php | 44 ++++++++ 2 files changed, 150 insertions(+) create mode 100644 lib/Controller/CertificatePolicyController.php create mode 100644 lib/Service/CertificatePolicyService.php diff --git a/lib/Controller/CertificatePolicyController.php b/lib/Controller/CertificatePolicyController.php new file mode 100644 index 0000000000..fa7285942b --- /dev/null +++ b/lib/Controller/CertificatePolicyController.php @@ -0,0 +1,106 @@ +|DataResponse + * + * 200: OK + * 404: Not found + */ + #[PublicPage] + #[AnonRateLimit(limit: 10, period: 60)] + #[FrontpageRoute(verb: 'POST', url: '/certificate-policy.pdf')] + public function saveCertificatePolicy(): DataResponse { + $pdf = $this->request->getUploadedFile('pdf'); + $phpFileUploadErrors = [ + UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'), + UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'), + UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'), + UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'), + UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'), + UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'), + UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'), + UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'), + ]; + if (empty($pdf)) { + $error = $this->l10n->t('No file uploaded'); + } elseif (!empty($pdf) && array_key_exists('error', $pdf) && $pdf['error'] !== UPLOAD_ERR_OK) { + $error = $phpFileUploadErrors[$pdf['error']]; + } + if ($error !== null) { + return new DataResponse( + [ + 'message' => $error, + 'status' => 'failure', + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + try { + $this->certificatePolicyService->updateFile($pdf['tmp_name']); + } catch (\Exception $e) { + return new DataResponse( + [ + 'message' => $e->getMessage(), + 'status' => 'failure', + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + + return new DataResponse( + [ + 'status' => 'success', + ] + ); + } + + /** + * Certificate policy of this instance + * + * @return FileDisplayResponse|DataResponse, array{}> + * + * 200: OK + * 404: Not found + */ + #[PublicPage] + #[AnonRateLimit(limit: 10, period: 60)] + #[FrontpageRoute(verb: 'GET', url: '/certificate-policy.pdf')] + public function getCertificatePolicy(): FileDisplayResponse { + $file = $this->certificatePolicyService->getFile(); + return new FileDisplayResponse($file, Http::STATUS_OK, [ + 'Content-Disposition' => 'inline; filename="certificate-policy.pdf"', + 'Content-Type' => 'application/pdf', + ]); + } +} diff --git a/lib/Service/CertificatePolicyService.php b/lib/Service/CertificatePolicyService.php new file mode 100644 index 0000000000..8f4c9774d5 --- /dev/null +++ b/lib/Service/CertificatePolicyService.php @@ -0,0 +1,44 @@ +appData->getFolder('/'); + try { + $rootFolder->newFile('certificate-policy.pdf', $blob); + } catch (NotFoundException $e) { + $file = $rootFolder->getFile('certificate-policy.pdf'); + $file->putContent($blob); + } + } + + public function getFile(): ISimpleFile { + return $this->appData->getFolder('/')->getFile('certificate-policy.pdf'); + } + + public function deleteFile(): void { + $this->appData->getFolder('/')->getFile('certificate-policy.pdf')->delete(); + } +} From 2c958077a2a006124f47ee7304641374a175d669 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Mon, 14 Apr 2025 16:43:32 -0300 Subject: [PATCH 02/15] chore: Implement administration settings of Certificate Policy Signed-off-by: Vitor Mattos --- lib/Controller/AdminController.php | 99 +++++ .../CertificatePolicyController.php | 60 +-- lib/Service/CertificatePolicyService.php | 47 +- lib/Settings/Admin.php | 4 + openapi-administration.json | 355 +++++++++++++++ openapi-full.json | 404 ++++++++++++++++++ openapi.json | 49 +++ src/types/openapi/openapi-administration.ts | 184 ++++++++ src/types/openapi/openapi-full.ts | 231 ++++++++++ src/types/openapi/openapi.ts | 47 ++ src/views/Settings/CertificatePolicy.vue | 181 ++++++++ src/views/Settings/Settings.vue | 25 +- tests/Unit/Settings/AdminTest.php | 4 + 13 files changed, 1619 insertions(+), 71 deletions(-) create mode 100644 src/views/Settings/CertificatePolicy.vue diff --git a/lib/Controller/AdminController.php b/lib/Controller/AdminController.php index 51c3b56023..4db5f8d79c 100644 --- a/lib/Controller/AdminController.php +++ b/lib/Controller/AdminController.php @@ -14,6 +14,7 @@ use OCA\Libresign\Handler\CertificateEngine\IEngineHandler; use OCA\Libresign\Helper\ConfigureCheckHelper; use OCA\Libresign\ResponseDefinitions; +use OCA\Libresign\Service\CertificatePolicyService; use OCA\Libresign\Service\Install\ConfigureCheckService; use OCA\Libresign\Service\Install\InstallService; use OCA\Libresign\Service\SignatureBackgroundService; @@ -51,6 +52,7 @@ public function __construct( private IL10N $l10n, protected ISession $session, private SignatureBackgroundService $signatureBackgroundService, + private CertificatePolicyService $certificatePolicyService, ) { parent::__construct(Application::APP_ID, $request); $this->eventSource = $this->eventSourceFactory->create(); @@ -525,4 +527,101 @@ public function signerName( ); } } + + /** + * Update certificate policy of this instance + * + * @return DataResponse|DataResponse + * + * 200: OK + * 422: Not found + */ + #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/admin/certificate-policy', requirements: ['apiVersion' => '(v1)'])] + public function saveCertificatePolicy(): DataResponse { + $pdf = $this->request->getUploadedFile('pdf'); + $phpFileUploadErrors = [ + UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'), + UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'), + UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'), + UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'), + UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'), + UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'), + UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'), + UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'), + ]; + if (empty($pdf)) { + $error = $this->l10n->t('No file uploaded'); + } elseif (!empty($pdf) && array_key_exists('error', $pdf) && $pdf['error'] !== UPLOAD_ERR_OK) { + $error = $phpFileUploadErrors[$pdf['error']]; + } + if ($error !== null) { + return new DataResponse( + [ + 'message' => $error, + 'status' => 'failure', + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + try { + $url = $this->certificatePolicyService->updateFile($pdf['tmp_name']); + } catch (\Exception $e) { + return new DataResponse( + [ + 'message' => $e->getMessage(), + 'status' => 'failure', + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + return new DataResponse( + [ + 'url' => $url, + 'status' => 'success', + ] + ); + } + + /** + * Delete certificate policy of this instance + * + * @return DataResponse + * + * 200: OK + * 404: Not found + */ + #[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/admin/certificate-policy', requirements: ['apiVersion' => '(v1)'])] + public function deleteCertificatePolicy(): DataResponse { + $this->certificatePolicyService->deleteFile(); + return new DataResponse(); + } + + /** + * Update OID + * + * @param string $oid OID is a unique numeric identifier for certificate policies in digital certificates. + * @return DataResponse|DataResponse + * + * 200: OK + * 422: Validation error + */ + #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/admin/certificate-policy/oid', requirements: ['apiVersion' => '(v1)'])] + public function updateOID(string $oid): DataResponse { + try { + $this->certificatePolicyService->updateOid($oid); + return new DataResponse( + [ + 'status' => 'success', + ] + ); + } catch (\Exception $e) { + return new DataResponse( + [ + 'message' => $e->getMessage(), + 'status' => 'failure', + ], + Http::STATUS_UNPROCESSABLE_ENTITY + ); + } + } } diff --git a/lib/Controller/CertificatePolicyController.php b/lib/Controller/CertificatePolicyController.php index fa7285942b..9ddc0c9857 100644 --- a/lib/Controller/CertificatePolicyController.php +++ b/lib/Controller/CertificatePolicyController.php @@ -14,77 +14,20 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AnonRateLimit; use OCP\AppFramework\Http\Attribute\FrontpageRoute; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; -use OCP\IL10N; use OCP\IRequest; class CertificatePolicyController extends Controller { public function __construct( IRequest $request, private CertificatePolicyService $certificatePolicyService, - private IL10N $l10n, ) { parent::__construct(Application::APP_ID, $request); } - /** - * Update certificate policy of this instance - * - * @return DataResponse|DataResponse - * - * 200: OK - * 404: Not found - */ - #[PublicPage] - #[AnonRateLimit(limit: 10, period: 60)] - #[FrontpageRoute(verb: 'POST', url: '/certificate-policy.pdf')] - public function saveCertificatePolicy(): DataResponse { - $pdf = $this->request->getUploadedFile('pdf'); - $phpFileUploadErrors = [ - UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'), - UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'), - UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'), - UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'), - UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'), - UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'), - UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'), - UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'), - ]; - if (empty($pdf)) { - $error = $this->l10n->t('No file uploaded'); - } elseif (!empty($pdf) && array_key_exists('error', $pdf) && $pdf['error'] !== UPLOAD_ERR_OK) { - $error = $phpFileUploadErrors[$pdf['error']]; - } - if ($error !== null) { - return new DataResponse( - [ - 'message' => $error, - 'status' => 'failure', - ], - Http::STATUS_UNPROCESSABLE_ENTITY - ); - } - try { - $this->certificatePolicyService->updateFile($pdf['tmp_name']); - } catch (\Exception $e) { - return new DataResponse( - [ - 'message' => $e->getMessage(), - 'status' => 'failure', - ], - Http::STATUS_UNPROCESSABLE_ENTITY - ); - } - - return new DataResponse( - [ - 'status' => 'success', - ] - ); - } - /** * Certificate policy of this instance * @@ -94,6 +37,7 @@ public function saveCertificatePolicy(): DataResponse { * 404: Not found */ #[PublicPage] + #[NoCSRFRequired] #[AnonRateLimit(limit: 10, period: 60)] #[FrontpageRoute(verb: 'GET', url: '/certificate-policy.pdf')] public function getCertificatePolicy(): FileDisplayResponse { diff --git a/lib/Service/CertificatePolicyService.php b/lib/Service/CertificatePolicyService.php index 8f4c9774d5..28769b38f6 100644 --- a/lib/Service/CertificatePolicyService.php +++ b/lib/Service/CertificatePolicyService.php @@ -8,17 +8,25 @@ namespace OCA\Libresign\Service; +use OCA\Libresign\AppInfo\Application; +use OCA\Libresign\Exception\LibresignException; use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; +use OCP\IAppConfig; +use OCP\IL10N; +use OCP\IURLGenerator; class CertificatePolicyService { public function __construct( private IAppData $appData, + private IURLGenerator $urlGenerator, + private IAppConfig $appConfig, + private IL10N $l10n, ) { } - public function updateFile(string $tmpFile): void { + public function updateFile(string $tmpFile): string { $detectedMimeType = mime_content_type($tmpFile); if (!in_array($detectedMimeType, ['application/pdf'], true)) { throw new \Exception('Unsupported image type: ' . $detectedMimeType); @@ -32,6 +40,7 @@ public function updateFile(string $tmpFile): void { $file = $rootFolder->getFile('certificate-policy.pdf'); $file->putContent($blob); } + return $this->urlGenerator->linkToRouteAbsolute('libresign.CertificatePolicy.getCertificatePolicy'); } public function getFile(): ISimpleFile { @@ -39,6 +48,40 @@ public function getFile(): ISimpleFile { } public function deleteFile(): void { - $this->appData->getFolder('/')->getFile('certificate-policy.pdf')->delete(); + try { + $this->appData->getFolder('/')->getFile('certificate-policy.pdf')->delete(); + } catch (NotFoundException $e) { + } + } + + public function updateOid(string $oid): string { + if (empty($oid)) { + $this->appConfig->deleteKey(Application::APP_ID, 'certificate_policies_oid'); + return ''; + } + $regex = '/^(0|1|2)(\.\d+)+$/'; + preg_match($regex, $oid, $matches); + if (empty($matches)) { + // TRANSLATORS This message appears when an invalid Object + // Identifier (OID) is entered. It informs the admin that the input + // must follow a specific numeric pattern used in digital + // certificate policies. + throw new LibresignException($this->l10n->t('Invalid OID format. Expected pattern: %s', [$regex])); + } + $this->appConfig->setValueString(Application::APP_ID, 'certificate_policies_oid', $oid); + return $oid; + } + + public function getOid(): string { + return $this->appConfig->getValueString(Application::APP_ID, 'certificate_policies_oid', ''); + } + + public function getUrl(): string { + try { + $this->getFile(); + } catch (NotFoundException $e) { + return ''; + } + return $this->urlGenerator->linkToRouteAbsolute('libresign.CertificatePolicy.getCertificatePolicy'); } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 1465473126..acf7976e3c 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -11,6 +11,7 @@ use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory; +use OCA\Libresign\Service\CertificatePolicyService; use OCA\Libresign\Service\IdentifyMethodService; use OCA\Libresign\Service\SignatureBackgroundService; use OCA\Libresign\Service\SignatureTextService; @@ -25,6 +26,7 @@ public function __construct( private IInitialState $initialState, private IdentifyMethodService $identifyMethodService, private CertificateEngineFactory $certificateEngineFactory, + private CertificatePolicyService $certificatePolicyService, private IAppConfig $appConfig, private SignatureTextService $signatureTextService, private SignatureBackgroundService $signatureBackgroundService, @@ -40,6 +42,8 @@ public function getForm(): TemplateResponse { $this->initialState->provideInitialState('signature_text_template_error', $e->getMessage()); } $this->initialState->provideInitialState('certificate_engine', $this->certificateEngineFactory->getEngine()->getName()); + $this->initialState->provideInitialState('certificate_policies_oid', $this->certificatePolicyService->getOid()); + $this->initialState->provideInitialState('certificate_policies_url', $this->certificatePolicyService->getUrl()); $this->initialState->provideInitialState('config_path', $this->appConfig->getValueString(Application::APP_ID, 'config_path')); $this->initialState->provideInitialState('default_signature_font_size', SignatureTextService::SIGNATURE_DEFAULT_FONT_SIZE); $this->initialState->provideInitialState('default_signature_height', SignatureTextService::DEFAULT_SIGNATURE_HEIGHT); diff --git a/openapi-administration.json b/openapi-administration.json index 8fa25f8719..d54711466b 100644 --- a/openapi-administration.json +++ b/openapi-administration.json @@ -1737,6 +1737,361 @@ } } }, + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy": { + "post": { + "operationId": "admin-save-certificate-policy", + "summary": "Update certificate policy of this instance", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "url" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + }, + "url": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "message" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "failure" + ] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "admin-delete-certificate-policy", + "summary": "Delete certificate policy of this instance", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy/oid": { + "post": { + "operationId": "admin-updateoid", + "summary": "Update OID", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "oid" + ], + "properties": { + "oid": { + "type": "string", + "description": "OID is a unique numeric identifier for certificate policies in digital certificates." + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "message" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "failure" + ] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/libresign/api/{apiVersion}/setting/has-root-cert": { "get": { "operationId": "setting-has-root-cert", diff --git a/openapi-full.json b/openapi-full.json index b633460629..6799b531c9 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -1057,6 +1057,55 @@ } }, "paths": { + "/index.php/apps/libresign/certificate-policy.pdf": { + "get": { + "operationId": "certificate_policy-get-certificate-policy", + "summary": "Certificate policy of this instance", + "tags": [ + "certificate_policy" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Content-Disposition": { + "schema": { + "type": "string", + "enum": [ + "inline; filename=\"certificate-policy.pdf\"" + ] + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/index.php/apps/libresign/develop/pdf": { "get": { "operationId": "develop-pdf", @@ -9473,6 +9522,361 @@ } } }, + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy": { + "post": { + "operationId": "admin-save-certificate-policy", + "summary": "Update certificate policy of this instance", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "url" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + }, + "url": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "message" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "failure" + ] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "admin-delete-certificate-policy", + "summary": "Delete certificate policy of this instance", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy/oid": { + "post": { + "operationId": "admin-updateoid", + "summary": "Update OID", + "description": "This endpoint requires admin access", + "tags": [ + "admin" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "oid" + ], + "properties": { + "oid": { + "type": "string", + "description": "OID is a unique numeric identifier for certificate policies in digital certificates." + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "success" + ] + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status", + "message" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "failure" + ] + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/libresign/api/{apiVersion}/setting/has-root-cert": { "get": { "operationId": "setting-has-root-cert", diff --git a/openapi.json b/openapi.json index 27cf6934a1..57c35fc92d 100644 --- a/openapi.json +++ b/openapi.json @@ -961,6 +961,55 @@ } }, "paths": { + "/index.php/apps/libresign/certificate-policy.pdf": { + "get": { + "operationId": "certificate_policy-get-certificate-policy", + "summary": "Certificate policy of this instance", + "tags": [ + "certificate_policy" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "Content-Disposition": { + "schema": { + "type": "string", + "enum": [ + "inline; filename=\"certificate-policy.pdf\"" + ] + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/index.php/apps/libresign/develop/pdf": { "get": { "operationId": "develop-pdf", diff --git a/src/types/openapi/openapi-administration.ts b/src/types/openapi/openapi-administration.ts index 46aa6a517c..41b490724b 100644 --- a/src/types/openapi/openapi-administration.ts +++ b/src/types/openapi/openapi-administration.ts @@ -203,6 +203,50 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update certificate policy of this instance + * @description This endpoint requires admin access + */ + post: operations["admin-save-certificate-policy"]; + /** + * Delete certificate policy of this instance + * @description This endpoint requires admin access + */ + delete: operations["admin-delete-certificate-policy"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy/oid": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update OID + * @description This endpoint requires admin access + */ + post: operations["admin-updateoid"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/libresign/api/{apiVersion}/setting/has-root-cert": { parameters: { query?: never; @@ -905,6 +949,146 @@ export interface operations { }; }; }; + "admin-save-certificate-policy": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "success"; + url: string; + }; + }; + }; + }; + }; + /** @description Not found */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "failure"; + message: string; + }; + }; + }; + }; + }; + }; + }; + "admin-delete-certificate-policy": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: Record; + }; + }; + }; + }; + }; + }; + "admin-updateoid": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description OID is a unique numeric identifier for certificate policies in digital certificates. */ + oid: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "success"; + }; + }; + }; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "failure"; + message: string; + }; + }; + }; + }; + }; + }; + }; "setting-has-root-cert": { parameters: { query?: never; diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index c2a22e3ede..de57210715 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -4,6 +4,23 @@ */ export type paths = { + "/index.php/apps/libresign/certificate-policy.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Certificate policy of this instance */ + get: operations["certificate_policy-get-certificate-policy"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/index.php/apps/libresign/develop/pdf": { parameters: { query?: never; @@ -1113,6 +1130,50 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update certificate policy of this instance + * @description This endpoint requires admin access + */ + post: operations["admin-save-certificate-policy"]; + /** + * Delete certificate policy of this instance + * @description This endpoint requires admin access + */ + delete: operations["admin-delete-certificate-policy"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/libresign/api/{apiVersion}/admin/certificate-policy/oid": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update OID + * @description This endpoint requires admin access + */ + post: operations["admin-updateoid"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/libresign/api/{apiVersion}/setting/has-root-cert": { parameters: { query?: never; @@ -1442,6 +1503,36 @@ export type components = { }; export type $defs = Record; export interface operations { + "certificate_policy-get-certificate-policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "Content-Disposition"?: "inline; filename=\"certificate-policy.pdf\""; + [name: string]: unknown; + }; + content: { + "application/pdf": string; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; "develop-pdf": { parameters: { query?: never; @@ -4903,6 +4994,146 @@ export interface operations { }; }; }; + "admin-save-certificate-policy": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "success"; + url: string; + }; + }; + }; + }; + }; + /** @description Not found */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "failure"; + message: string; + }; + }; + }; + }; + }; + }; + }; + "admin-delete-certificate-policy": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: Record; + }; + }; + }; + }; + }; + }; + "admin-updateoid": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description OID is a unique numeric identifier for certificate policies in digital certificates. */ + oid: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "success"; + }; + }; + }; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + status: "failure"; + message: string; + }; + }; + }; + }; + }; + }; + }; "setting-has-root-cert": { parameters: { query?: never; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 423666b382..8192c1a674 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -4,6 +4,23 @@ */ export type paths = { + "/index.php/apps/libresign/certificate-policy.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Certificate policy of this instance */ + get: operations["certificate_policy-get-certificate-policy"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/index.php/apps/libresign/develop/pdf": { parameters: { query?: never; @@ -1200,6 +1217,36 @@ export type components = { }; export type $defs = Record; export interface operations { + "certificate_policy-get-certificate-policy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + "Content-Disposition"?: "inline; filename=\"certificate-policy.pdf\""; + [name: string]: unknown; + }; + content: { + "application/pdf": string; + }; + }; + /** @description Not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; "develop-pdf": { parameters: { query?: never; diff --git a/src/views/Settings/CertificatePolicy.vue b/src/views/Settings/CertificatePolicy.vue new file mode 100644 index 0000000000..a91eb1b142 --- /dev/null +++ b/src/views/Settings/CertificatePolicy.vue @@ -0,0 +1,181 @@ + + + + + + + diff --git a/src/views/Settings/Settings.vue b/src/views/Settings/Settings.vue index 2fa17d4bab..06196183e9 100644 --- a/src/views/Settings/Settings.vue +++ b/src/views/Settings/Settings.vue @@ -10,6 +10,7 @@ + @@ -28,6 +29,7 @@ import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' import AllowedGroups from './AllowedGroups.vue' import CertificateEngine from './CertificateEngine.vue' +import CertificatePolicy from './CertificatePolicy.vue' import CollectMetadata from './CollectMetadata.vue' import ConfigureCheck from './ConfigureCheck.vue' import DefaultUserFolder from './DefaultUserFolder.vue' @@ -45,22 +47,23 @@ import Validation from './Validation.vue' export default { name: 'Settings', components: { - NcSettingsSection, + AllowedGroups, CertificateEngine, - DownloadBinaries, + CertificatePolicy, + CollectMetadata, ConfigureCheck, - RootCertificateCfssl, - RootCertificateOpenSsl, - IdentificationFactors, + DefaultUserFolder, + DownloadBinaries, ExpirationRules, - Validation, - AllowedGroups, - LegalInformation, IdentificationDocuments, - CollectMetadata, - SignatureStamp, + IdentificationFactors, + LegalInformation, + NcSettingsSection, + RootCertificateCfssl, + RootCertificateOpenSsl, SignatureHashAlgorithm, - DefaultUserFolder, + SignatureStamp, + Validation, }, data() { return { diff --git a/tests/Unit/Settings/AdminTest.php b/tests/Unit/Settings/AdminTest.php index 86595c0df3..9b3121042f 100644 --- a/tests/Unit/Settings/AdminTest.php +++ b/tests/Unit/Settings/AdminTest.php @@ -10,6 +10,7 @@ use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory; +use OCA\Libresign\Service\CertificatePolicyService; use OCA\Libresign\Service\IdentifyMethodService; use OCA\Libresign\Service\SignatureBackgroundService; use OCA\Libresign\Service\SignatureTextService; @@ -26,6 +27,7 @@ final class AdminTest extends \OCA\Libresign\Tests\Unit\TestCase { private IInitialState&MockObject $initialState; private IdentifyMethodService&MockObject $identifyMethodService; private CertificateEngineFactory&MockObject $certificateEngineFactory; + private CertificatePolicyService&MockObject $certificatePolicyService; private IAppConfig&MockObject $appConfig; private SignatureTextService&MockObject $signatureTextService; private SignatureBackgroundService&MockObject $signatureBackgroundService; @@ -33,6 +35,7 @@ public function setUp(): void { $this->initialState = $this->createMock(IInitialState::class); $this->identifyMethodService = $this->createMock(IdentifyMethodService::class); $this->certificateEngineFactory = $this->createMock(CertificateEngineFactory::class); + $this->certificatePolicyService = $this->createMock(CertificatePolicyService::class); $this->appConfig = $this->createMock(IAppConfig::class); $this->signatureTextService = $this->createMock(SignatureTextService::class); $this->signatureBackgroundService = $this->createMock(SignatureBackgroundService::class); @@ -40,6 +43,7 @@ public function setUp(): void { $this->initialState, $this->identifyMethodService, $this->certificateEngineFactory, + $this->certificatePolicyService, $this->appConfig, $this->signatureTextService, $this->signatureBackgroundService, From f397fb78a4a86c38991a3df68a363d04673cdbfd Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 16 Apr 2025 20:36:02 -0300 Subject: [PATCH 03/15] feat: implement changes at backend to support Certificate Policy Signed-off-by: Vitor Mattos --- .../CertificateEngine/CfsslHandler.php | 3 +- .../CertificateEngine/OpenSslHandler.php | 58 +++++++++++++++++-- lib/Handler/CfsslServerHandler.php | 19 ++++++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/lib/Handler/CertificateEngine/CfsslHandler.php b/lib/Handler/CertificateEngine/CfsslHandler.php index fd7937b0f7..422f7b8f2b 100644 --- a/lib/Handler/CertificateEngine/CfsslHandler.php +++ b/lib/Handler/CertificateEngine/CfsslHandler.php @@ -37,7 +37,6 @@ class CfsslHandler extends AEngineHandler implements IEngineHandler { protected $client; protected $cfsslUri; private string $binary = ''; - private CfsslServerHandler $cfsslServerHandler; public function __construct( protected IConfig $config, @@ -46,10 +45,10 @@ public function __construct( protected IAppDataFactory $appDataFactory, protected IDateTimeFormatter $dateTimeFormatter, protected ITempManager $tempManager, + protected CfsslServerHandler $cfsslServerHandler, ) { parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter, $tempManager); - $this->cfsslServerHandler = new CfsslServerHandler(); $this->cfsslServerHandler->configCallback(fn () => $this->getConfigPath()); } diff --git a/lib/Handler/CertificateEngine/OpenSslHandler.php b/lib/Handler/CertificateEngine/OpenSslHandler.php index 4b4288876f..ff795ce40d 100644 --- a/lib/Handler/CertificateEngine/OpenSslHandler.php +++ b/lib/Handler/CertificateEngine/OpenSslHandler.php @@ -10,6 +10,7 @@ use OCA\Libresign\Exception\LibresignException; use OCA\Libresign\Helper\ConfigureCheckHelper; +use OCA\Libresign\Service\CertificatePolicyService; use OCP\Files\AppData\IAppDataFactory; use OCP\IAppConfig; use OCP\IConfig; @@ -30,6 +31,7 @@ public function __construct( protected IAppDataFactory $appDataFactory, protected IDateTimeFormatter $dateTimeFormatter, protected ITempManager $tempManager, + protected CertificatePolicyService $certificatePolicyService, ) { parent::__construct($config, $appConfig, $appDataFactory, $dateTimeFormatter, $tempManager); } @@ -44,7 +46,8 @@ public function generateRootCert( ]); $csr = openssl_csr_new($this->getCsrNames(), $privateKey, ['digest_alg' => 'sha256']); - $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, ['digest_alg' => 'sha256']); + $options = $this->getRootCertOptions(); + $x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, $options); openssl_csr_export($csr, $csrout); openssl_x509_export($x509, $certout); @@ -57,6 +60,18 @@ public function generateRootCert( return $pkeyout; } + private function getRootCertOptions(): array { + $this->generateRootCertConfig(); + $configPath = $this->getConfigPath(); + + $options = [ + 'digest_alg' => 'sha256', + 'config' => $configPath . DIRECTORY_SEPARATOR . 'openssl.cnf', + 'x509_extensions' => 'v3_ca', + ]; + return $options; + } + public function generateCertificate(): string { $configPath = $this->getConfigPath(); $rootCertificate = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); @@ -105,10 +120,17 @@ private function getFilenameToLeafCert(): string { 'subjectAltName' => $this->getSubjectAltNames(), 'authorityKeyIdentifier' => 'keyid', 'subjectKeyIdentifier' => 'hash', - // @todo Implement a feature to define this PDF at Administration Settings - // 'certificatePolicies' => ' CPS: http://url/with/policy/informations.pdf', - ] + ], ]; + $oid = $this->certificatePolicyService->getOid(); + $url = $this->certificatePolicyService->getUrl(); + if ($oid && $url) { + $config['v3_req']['certificatePolicies'] = '@policy_section'; + $config['policy_section'] = [ + 'policyIdentifier' => $oid, + 'CPS.1' => $url, + ]; + } if (empty($config['v3_req']['subjectAltName'])) { unset($config['v3_req']['subjectAltName']); } @@ -117,6 +139,34 @@ private function getFilenameToLeafCert(): string { return $temporaryFile; } + private function generateRootCertConfig(): void { + // More information about x509v3: https://www.openssl.org/docs/manmaster/man5/x509v3_config.html + $config = [ + 'v3_ca' => [ + 'basicConstraints' => 'critical, CA:TRUE', + 'keyUsage' => 'critical, digitalSignature, keyCertSign', + 'extendedKeyUsage' => 'clientAuth, emailProtection', + 'subjectAltName' => $this->getSubjectAltNames(), + 'authorityKeyIdentifier' => 'keyid', + 'subjectKeyIdentifier' => 'hash', + ], + ]; + $oid = $this->certificatePolicyService->getOid(); + $url = $this->certificatePolicyService->getUrl(); + if ($oid && $url) { + $config['v3_ca']['certificatePolicies'] = '@policy_section'; + $config['policy_section'] = [ + 'policyIdentifier' => $oid, + 'CPS.1' => $url, + ]; + } + if (empty($config['v3_ca']['subjectAltName'])) { + unset($config['v3_ca']['subjectAltName']); + } + $iniContent = $this->arrayToIni($config); + $this->saveFile('openssl.cnf', $iniContent); + } + private function arrayToIni(array $config) { $fileContent = ''; foreach ($config as $i => $v) { diff --git a/lib/Handler/CfsslServerHandler.php b/lib/Handler/CfsslServerHandler.php index e4dbe2fe6c..9245d9bd55 100644 --- a/lib/Handler/CfsslServerHandler.php +++ b/lib/Handler/CfsslServerHandler.php @@ -10,6 +10,7 @@ use Closure; use OCA\Libresign\Exception\LibresignException; +use OCA\Libresign\Service\CertificatePolicyService; class CfsslServerHandler { private string $csrServerFile = ''; @@ -17,6 +18,11 @@ class CfsslServerHandler { private string $configServerFileHash = ''; private Closure $getConfigPath; + public function __construct( + private CertificatePolicyService $certificatePolicyService, + ) { + } + /** * Create a callback to get config path not at the constructor because * getting at constructor, every time that the class is instantiated, will @@ -98,6 +104,19 @@ private function saveNewConfig(string $key, int $expirity): void { ], ], ]; + $oid = $this->certificatePolicyService->getOid(); + $url = $this->certificatePolicyService->getUrl(); + if ($oid && $url) { + $config['signing']['profiles']['CA']['policies'][] = [ + 'id' => $oid, + 'qualifiers' => [ + [ + 'type' => 'id-qt-cps', + 'value' => $url, + ], + ], + ]; + } $this->saveConfig($config); } From 02f46d17292f1b7dd61bd78ed23a013a42000416 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 16 Apr 2025 21:12:57 -0300 Subject: [PATCH 04/15] chore: reduce regex to be more readdable by sysadmin Signed-off-by: Vitor Mattos --- lib/Service/CertificatePolicyService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Service/CertificatePolicyService.php b/lib/Service/CertificatePolicyService.php index 28769b38f6..4b43dda835 100644 --- a/lib/Service/CertificatePolicyService.php +++ b/lib/Service/CertificatePolicyService.php @@ -59,8 +59,8 @@ public function updateOid(string $oid): string { $this->appConfig->deleteKey(Application::APP_ID, 'certificate_policies_oid'); return ''; } - $regex = '/^(0|1|2)(\.\d+)+$/'; - preg_match($regex, $oid, $matches); + $regex = '(0|1|2)(\.\d+)+'; + preg_match('/^' . $regex . '$/', $oid, $matches); if (empty($matches)) { // TRANSLATORS This message appears when an invalid Object // Identifier (OID) is entered. It informs the admin that the input From 1bde1bdf95c7edc09c5c2fbb0377491304a89a99 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 16 Apr 2025 22:46:56 -0300 Subject: [PATCH 05/15] chore: move CertificatePolocy to root cert flow Signed-off-by: Vitor Mattos --- src/views/Settings/CertificatePolicy.vue | 49 ++++++++++++------- src/views/Settings/RootCertificateCfssl.vue | 37 +++++++++++++- src/views/Settings/RootCertificateOpenSsl.vue | 37 +++++++++++++- src/views/Settings/Settings.vue | 2 - 4 files changed, 102 insertions(+), 23 deletions(-) diff --git a/src/views/Settings/CertificatePolicy.vue b/src/views/Settings/CertificatePolicy.vue index a91eb1b142..9f9dfd2035 100644 --- a/src/views/Settings/CertificatePolicy.vue +++ b/src/views/Settings/CertificatePolicy.vue @@ -4,38 +4,40 @@ -->