diff --git a/3rdparty b/3rdparty index 63d763a37f..9b2d970391 160000 --- a/3rdparty +++ b/3rdparty @@ -1 +1 @@ -Subproject commit 63d763a37f2a1f26a312a24e0286c4281eb3cc86 +Subproject commit 9b2d970391b802245dc2bcf8ee384ccf44d70bf3 diff --git a/lib/Handler/SignEngine/Pkcs12Handler.php b/lib/Handler/SignEngine/Pkcs12Handler.php index 4d0ac369ae..1259881921 100644 --- a/lib/Handler/SignEngine/Pkcs12Handler.php +++ b/lib/Handler/SignEngine/Pkcs12Handler.php @@ -18,8 +18,8 @@ use OCA\Libresign\Service\Crl\CrlService; use OCA\Libresign\Service\FolderService; use OCA\Libresign\Service\Signature\PdfSignatureValidationService; -use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Exception\UnsignedPdfException; -use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Parser\PdfSignatureExtractor; +use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Model\ExtractedSignature; +use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Model\TimestampToken; use OCA\Libresign\Vendor\phpseclib4\Exception\UnexpectedValueException; use OCA\Libresign\Vendor\phpseclib4\File\ASN1; use OCP\Files\File; @@ -47,7 +47,6 @@ public function __construct( private DocMdpHandler $docMdpHandler, private CrlService $crlService, private PdfSignatureValidationService $pdfSignatureValidationService, - private PdfSignatureExtractor $pdfSignatureExtractor, ) { parent::__construct($l10n, $folderService, $logger); } @@ -57,38 +56,6 @@ protected function getCertificateEngineFactory(): CertificateEngineFactory { return $this->certificateEngineFactory; } - /** - * @throws LibresignException When is not a signed file - */ - private function getSignatures($resource): iterable { - rewind($resource); - $content = stream_get_contents($resource); - - preg_match_all('/\/Contents\s*<([0-9a-fA-F]+)>/', $content, $contents, PREG_OFFSET_CAPTURE); - - if (empty($contents[1])) { - // TRANSLATORS Error while LibreSign reads a PDF for signature validation: the file has no embedded PKCS#12/PDF signature bytes yet. - throw new LibresignException($this->l10n->t('Unsigned file.')); - } - - $seenHexSignatures = []; - foreach ($contents[1] as $match) { - $signatureHex = $match[0]; - - if (isset($seenHexSignatures[$signatureHex])) { - continue; - } - $seenHexSignatures[$signatureHex] = true; - - $decodedSignature = @hex2bin($signatureHex); - if ($decodedSignature === false) { - yield null; - continue; - } - yield $decodedSignature; - } - } - public function setIsLibreSignFile(): void { $this->isLibreSignFile = true; } @@ -113,25 +80,25 @@ public function getCertificateChain($resource): array { $certificateEngine->setPolicyUserIdForValidation($this->policyUserIdForValidation); try { - $nativeMetadata = array_values($this->extractNativeSignatureMetadata($resource)); rewind($resource); - $nativeValidation = array_values($this->pdfSignatureValidationService->validateFromResource($resource)); - $index = 0; + $validationResults = array_values( + $this->pdfSignatureValidationService->validateFromResource($resource) + ); - foreach ($this->getSignatures($resource) as $signature) { - $metadata = $nativeMetadata[$index] ?? []; - $validation = $nativeValidation[$index] ?? []; - $index++; + if ($validationResults === []) { + throw new LibresignException($this->l10n->t('Unsigned file.')); + } - if (!$signature) { + foreach ($validationResults as $validation) { + $signature = $validation['signature'] ?? null; + if (!$signature instanceof ExtractedSignature) { continue; } $result = $this->processSignature( $resource, $signature, - $metadata, - $validation + $validation, ); if (empty($result['chain'])) { @@ -148,35 +115,53 @@ public function getCertificateChain($resource): array { return $certificates; } - private function processSignature($resource, ?string $signature, array $metadata = [], array $validation = []): array { - $result = []; - - if (!$signature) { - $result['chain'][0]['signature_validation'] = [ - 'id' => 3, - // TRANSLATORS Status label on LibreSign's public/document validation UI when the PDF signature hash does not match the document bytes (tamper or corrupt signature). - 'label' => $this->l10n->t('Digest mismatch.'), - ]; - return $result; + private function processSignature( + $resource, + ExtractedSignature $signature, + array $validation = [], + ): array { + $binarySignature = $signature->binarySignature; + if ($binarySignature === null || $binarySignature === '') { + return $this->enrichLeafWithNativeData( + ['chain' => [[]]], + $signature, + $validation, + ); } + $result = []; + try { - $decoded = ASN1::decodeBER($signature); + $decoded = ASN1::decodeBER($binarySignature); } catch (UnexpectedValueException) { - return []; + $decoded = null; + } + + $result = $this->extractSigningTime($decoded, $result); + + $timestamp = $validation['timestamp'] ?? null; + if ($timestamp instanceof TimestampToken) { + $result['timestamp'] = $this->mapTimestampToken($timestamp); } - $result = $this->extractTimestampData($decoded, $result); - $chain = $this->extractCertificateChain($signature); + $pemCertificates = $validation['certificates'] ?? []; + if (!is_array($pemCertificates)) { + $pemCertificates = []; + } + + $chain = $this->extractCertificateChain($pemCertificates); if (!empty($chain)) { $result['chain'] = $this->orderCertificates($chain); - $result = $this->enrichLeafWithNativeData($result, $metadata, $validation); + $result = $this->enrichLeafWithNativeData( + $result, + $signature, + $validation, + ); } $result = $this->extractDocMdpData($resource, $result); - $result = $this->applyLibreSignRootCAFlag($result); - return $result; + return $this->applyLibreSignRootCAFlag($result); } private function applyLibreSignRootCAFlag(array $signer): array { @@ -209,55 +194,71 @@ private function extractDocMdpData($resource, array $result): array { return array_merge($result, $docMdpData); } - private function extractTimestampData(?array $decoded, array $result): array { + private function extractSigningTime(?array $decoded, array $result): array { if ($decoded === null) { return $result; } $tsa = new TSA(); - - $timestampData = $tsa->extract($decoded); - if (!empty($timestampData['genTime']) || !empty($timestampData['policy']) || !empty($timestampData['serialNumber'])) { - $result['timestamp'] = $timestampData; + $signingTime = $tsa->getSigninTime($decoded); + if ($signingTime instanceof \DateTime) { + $result['signingTime'] = $signingTime; } - if (!isset($result['signingTime']) || !$result['signingTime'] instanceof \DateTime) { - $result['signingTime'] = $tsa->getSigninTime($decoded); - } return $result; } - private function extractCertificateChain(string $signature): array { - $pkcs7PemSignature = $this->der2pem($signature); - $pemCertificates = []; - - if (!openssl_pkcs7_read($pkcs7PemSignature, $pemCertificates)) { - return []; - } + private function mapTimestampToken(TimestampToken $timestamp): array { + $result = [ + 'genTime' => $timestamp->generatedAt, + 'policy' => $timestamp->policyOid, + 'serialNumber' => $timestamp->serialNumber, + 'cnHints' => $timestamp->certificateSubject, + 'tsaName' => $timestamp->certificateSubject['commonName'] ?? null, + ]; + + return array_filter( + $result, + static fn (mixed $value): bool => $value !== null + && $value !== '' + && $value !== [], + ); + } + /** + * @param list $pemCertificates + */ + private function extractCertificateChain(array $pemCertificates): array { $chain = []; $isLibreSignRootCA = false; $certificateEngine = $this->getCertificateEngine(); foreach ($pemCertificates as $index => $pemCertificate) { + if (!is_string($pemCertificate) || $pemCertificate === '') { + continue; + } + $parsed = $certificateEngine->parseCertificate($pemCertificate); - if ($parsed) { - $parsed['signature_validation'] = [ - 'id' => 1, - // TRANSLATORS Status label on LibreSign signature validation when the cryptographic PDF signature checks out successfully. - 'label' => $this->l10n->t('Signature is valid.'), - ]; - if (!$isLibreSignRootCA) { - $isLibreSignRootCA = $this->isLibreSignRootCA($pemCertificate, $parsed); - } - $parsed['isLibreSignRootCA'] = $isLibreSignRootCA; - $chain[$index] = $parsed; + if (!$parsed) { + continue; + } + + if (!$isLibreSignRootCA) { + $isLibreSignRootCA = $this->isLibreSignRootCA( + $pemCertificate, + $parsed, + ); } + + $parsed['isLibreSignRootCA'] = $isLibreSignRootCA; + $chain[$index] = $parsed; } + if ($isLibreSignRootCA || $this->isLibreSignFile) { foreach ($chain as &$cert) { $cert['isLibreSignRootCA'] = true; } + unset($cert); } return $chain; @@ -310,13 +311,16 @@ private function getRootCertificatePem(): string { return $this->rootCertificatePem; } $configPath = $this->appConfig->getValueString(Application::APP_ID, 'config_path'); + $caPemPath = $configPath . DIRECTORY_SEPARATOR . 'ca.pem'; + if (empty($configPath) || !is_dir($configPath) - || !is_readable($configPath . DIRECTORY_SEPARATOR . 'ca.pem') + || !is_readable($caPemPath) ) { return ''; } - $rootCertificatePem = file_get_contents($configPath . DIRECTORY_SEPARATOR . 'ca.pem'); + + $rootCertificatePem = file_get_contents($caPemPath); if ($rootCertificatePem === false) { return ''; } @@ -324,24 +328,40 @@ private function getRootCertificatePem(): string { return $this->rootCertificatePem; } - private function enrichLeafWithNativeData(array $result, array $metadata, array $validation): array { + private function enrichLeafWithNativeData( + array $result, + ExtractedSignature $signature, + array $validation, + ): array { if (empty($result['chain'])) { return $result; } $leaf = &$result['chain'][0]; + $metadata = $signature->metadata; - foreach (['field', 'range', 'signature_type', 'signing_hash_algorithm', 'covers_entire_document'] as $key) { - if (array_key_exists($key, $metadata)) { - $leaf[$key] = $metadata[$key]; - } + $leaf['field'] = $metadata->field; + $leaf['range'] = $metadata->range; + $leaf['signature_type'] = $metadata->signatureType; + $leaf['signing_hash_algorithm'] = $signature->hashAlgorithm; + $leaf['covers_entire_document'] = $metadata->coversEntireDocument; + + if ($metadata->documentModificationState !== null) { + $leaf['document_modification_state'] + = $metadata->documentModificationState->value; } - if (isset($validation['signatureValidation']) && is_array($validation['signatureValidation'])) { + if ( + isset($validation['signatureValidation']) + && is_array($validation['signatureValidation']) + ) { $leaf['signature_validation'] = $validation['signatureValidation']; } - if (isset($validation['certificateValidation']) && is_array($validation['certificateValidation'])) { + if ( + isset($validation['certificateValidation']) + && is_array($validation['certificateValidation']) + ) { $leaf['certificate_validation'] = $validation['certificateValidation']; } @@ -356,46 +376,6 @@ private function enrichLeafWithNativeData(array $result, array $metadata, array return $result; } - /** - * @param resource $resource - * @return array - */ - private function extractNativeSignatureMetadata($resource): array { - rewind($resource); - $content = stream_get_contents($resource); - if (!is_string($content) || $content === '') { - return []; - } - - try { - $signatures = $this->extractNativeSignaturesFromContent($content); - } catch (UnsignedPdfException) { - return []; - } - $metadata = []; - - foreach ($signatures as $index => $signature) { - $metadata[$index] = [ - 'field' => $signature->metadata->field, - 'range' => $signature->metadata->range, - 'signature_type' => $signature->metadata->signatureType, - 'covers_entire_document' => $signature->metadata->coversEntireDocument, - ]; - } - - return $metadata; - } - - protected function extractNativeSignaturesFromContent(string $content): array { - return $this->pdfSignatureExtractor->extractFromString($content); - } - - private function der2pem($derData) { - $pem = chunk_split(base64_encode((string)$derData), 64, "\n"); - $pem = "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n"; - return $pem; - } - private function getHandler(): SignEngineHandler { $sign_engine = $this->appConfig->getValueString(Application::APP_ID, 'signature_engine', 'JSignPdf'); $property = lcfirst($sign_engine) . 'Handler'; diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 0d4c12222d..23ccb4a435 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -223,6 +223,7 @@ * sign_request_uuid?: string, * hash_algorithm?: string, * covers_entire_document?: bool, + * document_modification_state?: 'unchanged'|'unsigned_content'|'trailing_data'|'invalid_byte_range'|'invalid_eof_boundary', * me: bool, * signingOrder?: non-negative-int, * visibleElements: LibresignVisibleElement[], diff --git a/lib/Service/File/CertificateSignersMergeService.php b/lib/Service/File/CertificateSignersMergeService.php index 64812f4f3f..c0190290b8 100644 --- a/lib/Service/File/CertificateSignersMergeService.php +++ b/lib/Service/File/CertificateSignersMergeService.php @@ -406,6 +406,14 @@ private function enrichSignerWithCertificateValidation(\stdClass $signer, array if (isset($endEntityCert['covers_entire_document']) && !isset($signer->covers_entire_document)) { $signer->covers_entire_document = $endEntityCert['covers_entire_document']; } + + if ( + isset($endEntityCert['document_modification_state']) + && !isset($signer->document_modification_state) + ) { + $signer->document_modification_state + = $endEntityCert['document_modification_state']; + } } /** diff --git a/lib/Service/Signature/PdfSignatureValidationDefinitions.php b/lib/Service/Signature/PdfSignatureValidationDefinitions.php new file mode 100644 index 0000000000..1e6699b80c --- /dev/null +++ b/lib/Service/Signature/PdfSignatureValidationDefinitions.php @@ -0,0 +1,31 @@ +, + * timestamp: ?TimestampToken, + * signatureValidation: array, + * certificateValidation: array, + * raw: array{ + * signature: ValidationResult, + * certificate: ValidationResult, + * }, + * } + */ +final class PdfSignatureValidationDefinitions { +} diff --git a/lib/Service/Signature/PdfSignatureValidationService.php b/lib/Service/Signature/PdfSignatureValidationService.php index e27b077b85..865422ede3 100644 --- a/lib/Service/Signature/PdfSignatureValidationService.php +++ b/lib/Service/Signature/PdfSignatureValidationService.php @@ -11,7 +11,7 @@ use OCA\Libresign\AppInfo\Application; use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Exception\UnsignedPdfException; use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Model\ExtractedSignature; -use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Model\TimestampToken; +use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Model\ValidationReason; use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Model\ValidationResult; use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Model\ValidationState; use OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Parser\PdfSignatureValidator; @@ -20,11 +20,10 @@ use Psr\Log\LoggerInterface; /** - * Service to validate PDF signatures using the pdf-signature-validator package. + * Validate PDF signatures using the pdf-signature-validator package. * - * This replaces shell calls to pdfsig with pure PHP validation. - * Supports custom trusted roots (e.g., LibreSign CA) to recognize - * certificates without requiring system-level CA registration. + * @psalm-import-type PdfSignatureValidationResult from \OCA\Libresign\Vendor\LibreSign\PdfSignatureValidator\Parser\PdfSignatureValidator + * @psalm-import-type MappedPdfValidationResult from PdfSignatureValidationDefinitions */ class PdfSignatureValidationService { private PdfSignatureValidator $validator; @@ -75,7 +74,7 @@ public function setTrustedRoots(array $certificates): void { * Validate PDF signatures from file resource. * * @param resource $resource PDF file resource - * @return list + * @return list */ public function validateFromResource($resource): array { try { @@ -91,7 +90,7 @@ public function validateFromResource($resource): array { * Validate PDF signatures from binary content. * * @param string $pdfContent Binary PDF content - * @return list + * @return list */ public function validateFromString(string $pdfContent): array { try { @@ -105,14 +104,14 @@ public function validateFromString(string $pdfContent): array { /** * @param resource $resource - * @return list, certificateValidation: ValidationResult, timestamp: TimestampToken|null}> + * @return list */ protected function validateNativeFromResource($resource): array { return $this->validator->validateFromResource($resource); } /** - * @return list, certificateValidation: ValidationResult, timestamp: TimestampToken|null}> + * @return list */ protected function validateNativeFromString(string $pdfContent): array { return $this->validator->validateFromString($pdfContent); @@ -121,21 +120,29 @@ protected function validateNativeFromString(string $pdfContent): array { /** * Map validation results from PdfSignatureValidator to LibreSign format. * - * @param list $results Results from PdfSignatureValidator - * @return list + * @param list $results Results from PdfSignatureValidator + * @return list */ private function mapValidationResults(array $results): array { $mapped = []; foreach ($results as $result) { + $signature = $result['signature'] ?? null; $sigValidation = $result['signatureValidation'] ?? null; $certValidation = $result['certificateValidation'] ?? null; - if (!$sigValidation instanceof ValidationResult || !$certValidation instanceof ValidationResult) { + if ( + !$signature instanceof ExtractedSignature + || !$sigValidation instanceof ValidationResult + || !$certValidation instanceof ValidationResult + ) { continue; } $mapped[] = [ + 'signature' => $signature, + 'certificates' => $result['certificates'], + 'timestamp' => $result['timestamp'], 'signatureValidation' => $this->mapSignatureValidation($sigValidation), 'certificateValidation' => $this->mapCertificateValidation($certValidation), 'raw' => [ @@ -160,28 +167,28 @@ private function mapSignatureValidation(ValidationResult $result): array { 'id' => 2, // TRANSLATORS User-facing status when signature cryptographic validation fails. 'label' => $this->l10n->t('Signature is invalid.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], ValidationState::DIGEST_MISMATCH => [ 'id' => 3, // TRANSLATORS User-facing status when signed digest does not match PDF content. 'label' => $this->l10n->t('Digest mismatch.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], ValidationState::NOT_VERIFIED => [ 'id' => 5, // TRANSLATORS User-facing status when validation could not be fully completed. 'label' => $this->l10n->t('Signature has not yet been verified.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], default => [ 'id' => 6, // TRANSLATORS Generic fallback status for unexpected signature validation failures. 'label' => $this->l10n->t('Unknown validation failure.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], }; @@ -199,47 +206,84 @@ private function mapCertificateValidation(ValidationResult $result): array { 'id' => 2, // TRANSLATORS User-facing status when issuing CA is known but not trusted. 'label' => $this->l10n->t("Certificate issuer isn't trusted."), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], ValidationState::CERT_ISSUER_UNKNOWN => [ 'id' => 3, // TRANSLATORS User-facing status when certificate issuer cannot be identified/trusted. 'label' => $this->l10n->t('Certificate issuer is unknown.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], ValidationState::CERT_REVOKED => [ 'id' => 4, // TRANSLATORS User-facing status when certificate is revoked. 'label' => $this->l10n->t('Certificate has been revoked.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], ValidationState::CERT_EXPIRED => [ 'id' => 5, // TRANSLATORS User-facing status when certificate is expired. 'label' => $this->l10n->t('Certificate has expired.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], ValidationState::CERT_NOT_VERIFIED => [ 'id' => 6, // TRANSLATORS User-facing status when certificate validation could not be completed. 'label' => $this->l10n->t('Certificate has not yet been verified.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], default => [ 'id' => 7, // TRANSLATORS Generic fallback status for unexpected certificate validation failures. 'label' => $this->l10n->t('Unknown issue with certificate or corrupted data.'), - 'reason' => $this->translateKnownReason($result->reason), + 'reason' => $this->translateValidationReason($result), 'isValid' => false, ], }; } + private function translateValidationReason(ValidationResult $result): ?string { + if ($result->reasonCode !== null) { + return match ($result->reasonCode) { + ValidationReason::NO_BYTE_RANGE => ( + // TRANSLATORS PDF digital signature validation. ByteRange is a PDF signature structure that defines exactly which byte ranges are protected by the cryptographic signature. This result means that structure is missing, so LibreSign cannot determine which document content was signed. Keep the user-facing text simple. + $this->l10n->t('The signature does not contain the information needed to verify the document') + ), + ValidationReason::DIGEST_MISMATCH => ( + // TRANSLATORS PDF digital signature validation. LibreSign recalculated the cryptographic digest from the signed PDF byte ranges and it does not match the digest stored in the signature. This can indicate changed content or inconsistent signature data. Keep the user-facing text simple. + $this->l10n->t('The document content does not match the signed content') + ), + ValidationReason::NO_BINARY_SIGNATURE => ( + // TRANSLATORS PDF digital signature validation. The PDF contains signature metadata, but the CMS/PKCS#7 binary signature payload required for cryptographic verification is missing. CMS/PKCS#7 is the standard container commonly used for PDF digital signatures. + $this->l10n->t('The digital signature data is missing') + ), + ValidationReason::SIGNATURE_CERTIFICATE_MISMATCH => ( + // TRANSLATORS PDF digital signature validation. LibreSign verified the signature bytes against the signer's X.509 certificate, but the cryptographic verification failed. The certificate and the digital signature do not match as expected. + $this->l10n->t('The signature could not be verified with its certificate') + ), + ValidationReason::INVALID_BYTE_RANGE => ( + // TRANSLATORS PDF digital signature validation. ByteRange defines which byte ranges are protected by the digital signature. The structure exists but its offsets or lengths are invalid, so cryptographic verification cannot safely continue. Keep the user-facing text simple. + $this->l10n->t('The signature contains an invalid ByteRange') + ), + ValidationReason::INVALID_EOF_BOUNDARY => ( + // TRANSLATORS PDF digital signature validation. The signed PDF revision does not end at a valid PDF end-of-file boundary. This means the signed revision structure is invalid. Keep the user-facing text simple. + $this->l10n->t('The signed PDF revision has an invalid end-of-file boundary') + ), + ValidationReason::UNSUPPORTED_SUBFILTER => ( + // TRANSLATORS PDF digital signature validation. SubFilter identifies the PDF signature format. This result means LibreSign does not support cryptographic validation for that format. + $this->l10n->t('The PDF signature format is not supported') + ), + }; + } + + return $this->translateKnownReason($result->reason); + } + private function translateKnownReason(?string $reason): ?string { if ($reason === null || $reason === '') { return $reason; diff --git a/openapi-full.json b/openapi-full.json index 7b4bcac7c4..117d87f4f7 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -3003,6 +3003,16 @@ "covers_entire_document": { "type": "boolean" }, + "document_modification_state": { + "type": "string", + "enum": [ + "unchanged", + "unsigned_content", + "trailing_data", + "invalid_byte_range", + "invalid_eof_boundary" + ] + }, "me": { "type": "boolean" }, diff --git a/openapi.json b/openapi.json index c375c0d6bf..b33ed368f3 100644 --- a/openapi.json +++ b/openapi.json @@ -2376,6 +2376,16 @@ "covers_entire_document": { "type": "boolean" }, + "document_modification_state": { + "type": "string", + "enum": [ + "unchanged", + "unsigned_content", + "trailing_data", + "invalid_byte_range", + "invalid_eof_boundary" + ] + }, "me": { "type": "boolean" }, diff --git a/src/components/validation/EnvelopeValidation.vue b/src/components/validation/EnvelopeValidation.vue index 2302e83c95..f73fb422ca 100644 --- a/src/components/validation/EnvelopeValidation.vue +++ b/src/components/validation/EnvelopeValidation.vue @@ -10,7 +10,7 @@

{{ t('libresign', 'Envelope information') }}

- + {{ documentValidMessage }} @@ -196,10 +196,12 @@ const props = withDefaults(defineProps<{ document: EnvelopeDocument legalInformation?: string documentValidMessage?: string | null + documentValidType?: 'success' | 'warning' | 'error' | 'info' isAfterSigned?: boolean }>(), { legalInformation: '', documentValidMessage: null, + documentValidType: 'success', isAfterSigned: false, }) diff --git a/src/components/validation/FileValidation.vue b/src/components/validation/FileValidation.vue index bcc95080a7..84cce6abf7 100644 --- a/src/components/validation/FileValidation.vue +++ b/src/components/validation/FileValidation.vue @@ -9,7 +9,7 @@

{{ t('libresign', 'Document information') }}

- + {{ documentValidMessage }} @@ -38,6 +38,7 @@ defineProps<{ document: LoadedValidationFileDocument legalInformation?: string documentValidMessage?: string | null + documentValidType?: 'success' | 'warning' | 'error' | 'info' isAfterSigned?: boolean }>() diff --git a/src/components/validation/SignerDetails.vue b/src/components/validation/SignerDetails.vue index eae54fa872..659cbb7cb5 100644 --- a/src/components/validation/SignerDetails.vue +++ b/src/components/validation/SignerDetails.vue @@ -13,8 +13,7 @@ :is-no-user="true" /> + :class="getSignerValidationClass(signer)" />