From 4a63c0daa76358f12c41a8cc7767da9b1088f769 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Mon, 30 Dec 2024 21:42:42 -0300 Subject: [PATCH] feat: validate and get signatures from signed pdf Signed-off-by: Vitor Mattos --- lib/Controller/FileController.php | 1 + lib/Handler/Pkcs12Handler.php | 33 ++++++++++++++++++ lib/Service/FileService.php | 58 +++++++++++++++++++++++++++++++ src/views/Validation.vue | 27 ++++++++++++++ 4 files changed, 119 insertions(+) diff --git a/lib/Controller/FileController.php b/lib/Controller/FileController.php index bcced31fb..46e3c0a44 100644 --- a/lib/Controller/FileController.php +++ b/lib/Controller/FileController.php @@ -182,6 +182,7 @@ public function validate(?string $type = null, $identifier = null): DataResponse ->showSigners() ->showSettings() ->showMessages() + ->showValidateFile() ->toArray() ); diff --git a/lib/Handler/Pkcs12Handler.php b/lib/Handler/Pkcs12Handler.php index c12b90a43..c653b7f79 100644 --- a/lib/Handler/Pkcs12Handler.php +++ b/lib/Handler/Pkcs12Handler.php @@ -16,6 +16,7 @@ use OCP\AppFramework\Services\IAppConfig; use OCP\Files\File; use OCP\IL10N; +use OCP\ITempManager; use TypeError; class Pkcs12Handler extends SignEngineHandler { @@ -30,6 +31,7 @@ public function __construct( private IL10N $l10n, private JSignPdfHandler $jSignPdfHandler, private FooterHandler $footerHandler, + private ITempManager $tempManager, ) { } @@ -79,6 +81,37 @@ public function readCertificate(string $uid, string $privateKey): array { ); } + /** + * @param resource $resource + * @return array + */ + public function validatePdfContent($resource): array { + $content = stream_get_contents($resource); + preg_match_all('/ByteRange\s*\[(\d+) (?\d+) (?\d+) (\d+)?/', $content, $bytes); + if (empty($bytes['start']) || empty($bytes['end'])) { + throw new LibresignException($this->l10n->t('Unsigned file.')); + } + + $parsed = []; + for ($i = 0; $i < count($bytes['start']); $i++) { + rewind($resource); + $signature = stream_get_contents( + $resource, + $bytes['end'][$i] - $bytes['start'][$i] - 2, + $bytes['start'][$i] + 1 + ); + $pfxCertificate = hex2bin($signature); + if (empty($tempFile)) { + $tempFile = $this->tempManager->getTemporaryFile('cert.pfx'); + } + file_put_contents($tempFile, $pfxCertificate); + $output = shell_exec("openssl pkcs7 -in {$tempFile} -inform DER -print_certs"); + $parsed[] = openssl_x509_parse($output); + } + $this->tempManager->clean(); + return $parsed; + } + public function setPfxContent(string $content): void { $this->pfxContent = $content; } diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 0fa672ea4..a130ecafc 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -16,12 +16,15 @@ use OCA\Libresign\Db\SignRequest; use OCA\Libresign\Db\SignRequestMapper; use OCA\Libresign\Exception\LibresignException; +use OCA\Libresign\Handler\Pkcs12Handler; use OCA\Libresign\Helper\ValidateHelper; use OCA\Libresign\ResponseDefinitions; use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod; use OCP\Accounts\IAccountManager; use OCP\AppFramework\Services\IAppConfig; +use OCP\Files\Config\IUserMountCache; use OCP\Files\IMimeTypeDetector; +use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Http\Client\IClientService; use OCP\IDateTimeFormatter; @@ -42,10 +45,12 @@ class FileService { private bool $showSettings = false; private bool $showVisibleElements = false; private bool $showMessages = false; + private bool $validateFile = false; private ?File $file = null; private ?SignRequest $signRequest = null; private ?IUser $me = null; private ?int $identifyMethodId = null; + private array $certData = []; private array $signers = []; private array $settings = [ 'canSign' => false, @@ -74,6 +79,9 @@ public function __construct( private IAppConfig $appConfig, private IURLGenerator $urlGenerator, protected IMimeTypeDetector $mimeTypeDetector, + protected Pkcs12Handler $pkcs12Handler, + private IUserMountCache $userMountCache, + private IRootFolder $root, protected LoggerInterface $logger, protected IL10N $l10n, ) { @@ -137,6 +145,11 @@ public function setSignRequest(SignRequest $signRequest): self { return $this; } + public function showValidateFile(bool $validateFile = true): self { + $this->validateFile = $validateFile; + return $this; + } + /** * @return static */ @@ -162,6 +175,7 @@ private function getSigners(): array { return $this->signers; } $signers = $this->signRequestMapper->getByFileId($this->file->getId()); + $certData = $this->getCertData(); foreach ($signers as $signer) { $signatureToShow = [ 'signed' => $signer->getSigned() ? @@ -191,6 +205,29 @@ private function getSigners(): array { $data['sign_date'] = (new \DateTime()) ->setTimestamp($signer->getSigned()) ->format('Y-m-d H:i:s'); + $mySignature = array_filter($certData, function ($data) use ($signatureToShow) { + foreach ($signatureToShow['identifyMethods'] as $methods) { + foreach ($methods as $identifyMethod) { + $entity = $identifyMethod->getEntity(); + if ($data['subject']['uid'] === $entity->getIdentifierKey() . ':' . $entity->getIdentifierValue()) { + return true; + } + } + } + }); + if ($mySignature) { + $mySignature = current($mySignature); + $signatureToShow['subject'] = implode( + ', ', + array_map( + fn (string $key, string $value) => "$key: $value", + array_keys($mySignature['subject']), + $mySignature['subject'] + ) + ); + $signatureToShow['valid_from'] = $mySignature['validFrom_time_t']; + $signatureToShow['valid_to'] = $mySignature['validTo_time_t']; + } } // @todo refactor this code if ($this->me || $this->identifyMethodId) { @@ -396,6 +433,27 @@ private function getFile(): array { return $return; } + private function getCertData(): array { + if (!empty($this->certData) || !$this->validateFile || !$this->file->getSignedNodeId()) { + return $this->certData; + } + $mountsContainingFile = $this->userMountCache->getMountsForFileId($this->file->getSignedNodeId()); + foreach ($mountsContainingFile as $fileInfo) { + $this->root->getByIdInPath($this->file->getSignedNodeId(), $fileInfo->getMountPoint()); + } + $fileToValidate = $this->root->getById($this->file->getSignedNodeId()); + if (!count($fileToValidate)) { + throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404); + } + /** @var \OCP\Files\File */ + $file = current($fileToValidate); + + $resource = $file->fopen('rb'); + $this->certData = $this->pkcs12Handler->validatePdfContent($resource); + fclose($resource); + return $this->certData; + } + /** * @return string[][] * diff --git a/src/views/Validation.vue b/src/views/Validation.vue index c3996b607..78a6d334a 100644 --- a/src/views/Validation.vue +++ b/src/views/Validation.vue @@ -128,6 +128,33 @@ + + + + + + + + +