diff --git a/lib/Db/CrlMapper.php b/lib/Db/CrlMapper.php index 491ff7e0a1..8371db059a 100644 --- a/lib/Db/CrlMapper.php +++ b/lib/Db/CrlMapper.php @@ -90,6 +90,7 @@ public function revokeCertificate( ?string $revokedBy = null, ?DateTime $invalidityDate = null, ?int $crlNumber = null, + ?DateTime $revokedAt = null, ): Crl { $certificate = $this->findBySerialNumber($serialNumber); return $this->revokeCertificateEntity( @@ -98,7 +99,8 @@ public function revokeCertificate( $comment, $revokedBy, $invalidityDate, - $crlNumber + $crlNumber, + $revokedAt, ); } @@ -109,6 +111,7 @@ public function revokeCertificateEntity( ?string $revokedBy = null, ?DateTime $invalidityDate = null, ?int $crlNumber = null, + ?DateTime $revokedAt = null, ): Crl { if (CRLStatus::from($certificate->getStatus()) !== CRLStatus::ISSUED) { throw new \InvalidArgumentException('Certificate is not in issued status'); @@ -118,7 +121,7 @@ public function revokeCertificateEntity( $certificate->setReasonCode($reason->value); $certificate->setComment($comment !== '' ? $comment : null); $certificate->setRevokedBy($revokedBy); - $certificate->setRevokedAt(new DateTime()); + $certificate->setRevokedAt($revokedAt ?? new DateTime()); $certificate->setInvalidityDate($invalidityDate); $certificate->setCrlNumber($crlNumber); diff --git a/lib/Listener/RevokeClickToSignCertificateListener.php b/lib/Listener/RevokeClickToSignCertificateListener.php index 1be3369688..8e10fa1749 100644 --- a/lib/Listener/RevokeClickToSignCertificateListener.php +++ b/lib/Listener/RevokeClickToSignCertificateListener.php @@ -9,6 +9,7 @@ namespace OCA\Libresign\Listener; +use DateTime; use OCA\Libresign\Enum\CRLReason; use OCA\Libresign\Events\SignedEvent; use OCA\Libresign\Service\Crl\CrlService; @@ -47,7 +48,9 @@ public function handle(Event $event): void { $serialNumber, CRLReason::SUPERSEDED, 'Temporary certificate issued for click-to-sign. Automatically revoked after document signing.', - 'system' + 'system', + null, + new DateTime('+1 second'), ); if ($success) { diff --git a/lib/Service/Crl/CrlService.php b/lib/Service/Crl/CrlService.php index 7013d2e8dc..d0a6f707a0 100644 --- a/lib/Service/Crl/CrlService.php +++ b/lib/Service/Crl/CrlService.php @@ -60,6 +60,7 @@ public function revokeCertificate( ?string $reasonText = null, ?string $revokedBy = null, ?DateTime $invalidityDate = null, + ?DateTime $revokedAt = null, ): bool { try { @@ -73,7 +74,8 @@ public function revokeCertificate( $reasonText, $revokedBy, $invalidityDate, - $crlNumber + $crlNumber, + $revokedAt, ); return true; diff --git a/lib/Service/File/CertificateSignersMergeService.php b/lib/Service/File/CertificateSignersMergeService.php new file mode 100644 index 0000000000..cdb1756d48 --- /dev/null +++ b/lib/Service/File/CertificateSignersMergeService.php @@ -0,0 +1,438 @@ +signers ?? []; + $hasContractSigners = $this->hasContractSigners($existingSigners); + $indexMap = $this->buildSignerIndexMap($existingSigners, $buildIdentifier); + $usedIndexes = []; + $lastMatchedSignerIndex = null; + $singleContractSignerIndex = $this->getSingleContractSignerIndex($existingSigners); + + foreach ($certData as $index => $signer) { + $resolvedUid = $this->resolveCertSignerUid($signer, $existingSigners, $host, $resolveUid); + $matchedIndex = $this->findMatchingSignerIndex($indexMap, $resolvedUid, $signer); + $targetIndex = $this->resolveTargetIndex( + $matchedIndex, + $existingSigners, + $usedIndexes, + $hasContractSigners, + $index, + ); + if ($targetIndex === null) { + $timestampTargetIndex = $lastMatchedSignerIndex ?? $singleContractSignerIndex; + if ($timestampTargetIndex !== null + && isset($fileData->signers[$timestampTargetIndex]) + && $this->isTechnicalTimestampEntry($signer) + ) { + $this->hydrateTimestampOnly($fileData->signers[$timestampTargetIndex], $signer); + } + continue; + } + + $isLibreSignMatch = $matchedIndex !== null && isset($existingSigners[$matchedIndex]->signRequestId); + $usedIndexes[$targetIndex] = true; + + $this->ensureSignerSlotExists($fileData, $targetIndex); + $this->hydrateSignerFromCertData( + $fileData->signers[$targetIndex], + $signer, + $resolvedUid, + $isLibreSignMatch, + $host, + $signedStatusText, + $resolveUid, + $lookupAccountDisplayName, + ); + + if (isset($fileData->signers[$targetIndex]->uid)) { + $indexMap[strtolower((string)$fileData->signers[$targetIndex]->uid)] = $targetIndex; + } + + $lastMatchedSignerIndex = $targetIndex; + } + } + + private function isTechnicalTimestampEntry(array $signer): bool { + if (!isset($signer['timestamp']) || !is_array($signer['timestamp'])) { + return false; + } + + if (isset($signer['uid']) && is_string($signer['uid']) && $signer['uid'] !== '') { + return false; + } + + $subjectUid = $signer['chain'][0]['subject']['UID'] ?? null; + if (is_string($subjectUid) && $subjectUid !== '') { + return false; + } + + return true; + } + + private function getSingleContractSignerIndex(array $signers): ?int { + $contractIndexes = []; + foreach ($signers as $index => $signer) { + if (is_object($signer) && isset($signer->signRequestId) && is_numeric($signer->signRequestId)) { + $contractIndexes[] = $index; + } + } + + if (count($contractIndexes) === 1) { + return $contractIndexes[0]; + } + + return null; + } + + private function hydrateTimestampOnly(\stdClass $targetSigner, array $signer): void { + $targetSigner->timestamp = $signer['timestamp']; + if (isset($signer['timestamp']['genTime']) && $signer['timestamp']['genTime'] instanceof DateTimeInterface) { + $targetSigner->timestamp['genTime'] = $signer['timestamp']['genTime']->format(DateTimeInterface::ATOM); + } + } + + /** + * @param callable(array, string): (?string) $resolveUid + */ + private function resolveCertSignerUid(array $signer, array $existingSigners, string $host, callable $resolveUid): ?string { + if (!isset($signer['chain'][0]) || !is_array($signer['chain'][0])) { + return is_string($signer['uid'] ?? null) ? $signer['uid'] : null; + } + + $resolvedUid = $this->tryMatchWithExistingSigners($signer['chain'][0], $existingSigners, $host, $resolveUid); + if ($resolvedUid) { + return $resolvedUid; + } + + $isLibreSignCert = isset($signer['chain'][0]['isLibreSignRootCA']) + && $signer['chain'][0]['isLibreSignRootCA'] === true; + if ($isLibreSignCert) { + $certUid = $signer['chain'][0]['subject']['UID'] ?? null; + if (!is_string($certUid) || $certUid === '') { + return null; + } + return str_contains($certUid, ':') ? $certUid : 'account:' . $certUid; + } + + if (is_string($signer['uid'] ?? null) && $signer['uid'] !== '') { + return $signer['uid']; + } + + return $resolveUid($signer['chain'][0], $host); + } + + private function resolveTargetIndex( + ?int $matchedIndex, + array $existingSigners, + array $usedIndexes, + bool $hasContractSigners, + int $defaultIndex, + ): ?int { + if ($matchedIndex !== null) { + return $matchedIndex; + } + + if ($hasContractSigners) { + return null; + } + + if (empty($existingSigners)) { + return $defaultIndex; + } + + return $this->nextAvailableSignerIndex($existingSigners, $usedIndexes); + } + + private function ensureSignerSlotExists(\stdClass $fileData, int $targetIndex): void { + if (!isset($fileData->signers[$targetIndex])) { + $fileData->signers[$targetIndex] = new \stdClass(); + } + } + + /** + * @param callable(array, string): (?string) $resolveUid + * @param callable(string): (?string) $lookupAccountDisplayName + */ + private function hydrateSignerFromCertData( + \stdClass $targetSigner, + array $signer, + ?string $resolvedUid, + bool $isLibreSignMatch, + string $host, + string $signedStatusText, + callable $resolveUid, + callable $lookupAccountDisplayName, + ): void { + $preservedDisplayName = $isLibreSignMatch && isset($targetSigner->displayName) + ? $targetSigner->displayName + : null; + + $targetSigner->status = 2; + $targetSigner->statusText = $signedStatusText; + + if (isset($signer['timestamp'])) { + $targetSigner->timestamp = $signer['timestamp']; + if (isset($signer['timestamp']['genTime']) && $signer['timestamp']['genTime'] instanceof DateTimeInterface) { + $targetSigner->timestamp['genTime'] = $signer['timestamp']['genTime']->format(DateTimeInterface::ATOM); + } + } + if (isset($signer['signingTime']) && $signer['signingTime'] instanceof DateTimeInterface) { + $targetSigner->signingTime = $signer['signingTime']; + $targetSigner->signed = $signer['signingTime']->format(DateTimeInterface::ATOM); + } + if (isset($signer['docmdp'])) { + $targetSigner->docmdp = $signer['docmdp']; + } + if (isset($signer['docmdp_validation'])) { + $targetSigner->docmdp_validation = $signer['docmdp_validation']; + } + if (isset($signer['modifications'])) { + $targetSigner->modifications = $signer['modifications']; + } + if (isset($signer['modification_validation'])) { + $targetSigner->modification_validation = $signer['modification_validation']; + } + + if (isset($signer['chain']) && is_array($signer['chain'])) { + $this->processChainData($targetSigner, $signer['chain']); + } + + $this->assignSignerUid($targetSigner, $signer, $resolvedUid, $host, $resolveUid); + + if (isset($signer['signDate'])) { + $targetSigner->signDate = $signer['signDate']; + } + if (isset($signer['type'])) { + $targetSigner->type = $signer['type']; + } + + $this->assignSignerDisplayName($targetSigner, $signer, $preservedDisplayName, $lookupAccountDisplayName); + } + + /** + * @param callable(array, string): (?string) $resolveUid + */ + private function assignSignerUid(\stdClass $targetSigner, array $signer, ?string $resolvedUid, string $host, callable $resolveUid): void { + if (isset($signer['uid'])) { + $targetSigner->uid = $signer['uid']; + return; + } + + if ($resolvedUid) { + $targetSigner->uid = $resolvedUid; + return; + } + + if (isset($signer['chain'][0]) && is_array($signer['chain'][0])) { + $targetSigner->uid = $resolveUid($signer['chain'][0], $host); + } + } + + /** + * @param callable(string): (?string) $lookupAccountDisplayName + */ + private function assignSignerDisplayName(\stdClass $targetSigner, array $signer, ?string $preservedDisplayName, callable $lookupAccountDisplayName): void { + if ($preservedDisplayName) { + $targetSigner->displayName = $preservedDisplayName; + return; + } + + if (isset($targetSigner->uid) && str_starts_with($targetSigner->uid, 'account:')) { + $accountId = substr($targetSigner->uid, strlen('account:')); + $displayName = $lookupAccountDisplayName($accountId); + $targetSigner->displayName = $displayName ?: $accountId; + return; + } + + if (!isset($targetSigner->displayName) && isset($signer['chain'][0])) { + $targetSigner->displayName = $signer['chain'][0]['name'] ?? ($signer['chain'][0]['subject']['CN'] ?? ''); + } + } + + private function hasContractSigners(array $signers): bool { + foreach ($signers as $signer) { + if (is_object($signer) && isset($signer->signRequestId) && is_numeric($signer->signRequestId)) { + return true; + } + if (is_array($signer) && isset($signer['signRequestId']) && is_numeric($signer['signRequestId'])) { + return true; + } + } + + return false; + } + + /** + * @param callable(string, string): string $buildIdentifier + */ + private function buildSignerIndexMap(array $signers, callable $buildIdentifier): array { + $map = []; + foreach ($signers as $index => $signer) { + if (isset($signer->uid)) { + $map[strtolower((string)$signer->uid)] = $index; + } + if (!empty($signer->identifyMethods)) { + foreach ($signer->identifyMethods as $identifyMethod) { + if (isset($identifyMethod['method']) && isset($identifyMethod['value'])) { + $identifier = $buildIdentifier($identifyMethod['method'], $identifyMethod['value']); + $map[strtolower($identifier)] = $index; + } + } + } + } + return $map; + } + + private function findMatchingSignerIndex(array $indexMap, ?string $resolvedUid, array $certSigner): ?int { + $identifiers = []; + if ($resolvedUid) { + $identifiers[] = strtolower($resolvedUid); + } + if (!empty($certSigner['uid'])) { + $identifiers[] = strtolower((string)$certSigner['uid']); + } + foreach ($identifiers as $identifier) { + if (isset($indexMap[$identifier])) { + return $indexMap[$identifier]; + } + } + return null; + } + + private function nextAvailableSignerIndex(array $existingSigners, array $usedIndexes): int { + $index = count($existingSigners); + while (isset($existingSigners[$index]) || isset($usedIndexes[$index])) { + $index++; + } + return $index; + } + + private function processChainData(\stdClass $signer, array $chain): void { + $signer->chain = []; + + foreach ($chain as $chainIndex => $chainItem) { + $chainArr = $chainItem; + + if (isset($chainItem['validFrom_time_t']) && is_numeric($chainItem['validFrom_time_t'])) { + $chainArr['valid_from'] = (new DateTime('@' . $chainItem['validFrom_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM); + } + if (isset($chainItem['validTo_time_t']) && is_numeric($chainItem['validTo_time_t'])) { + $chainArr['valid_to'] = (new DateTime('@' . $chainItem['validTo_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM); + } + + $chainArr['displayName'] = $chainArr['name'] ?? ($chainArr['subject']['CN'] ?? ''); + $signer->chain[$chainIndex] = $chainArr; + } + + if (isset($chain[0])) { + $this->enrichSignerWithCertificateValidation($signer, $chain[0]); + } + } + + private function enrichSignerWithCertificateValidation(\stdClass $signer, array $endEntityCert): void { + if (isset($endEntityCert['name']) && !isset($signer->name)) { + $signer->name = $endEntityCert['name']; + } + if (isset($endEntityCert['hash']) && !isset($signer->hash)) { + $signer->hash = $endEntityCert['hash']; + } + if (isset($endEntityCert['serialNumber']) && !isset($signer->serialNumber)) { + $signer->serialNumber = $endEntityCert['serialNumber']; + } + if (isset($endEntityCert['serialNumberHex']) && !isset($signer->serialNumberHex)) { + $signer->serialNumberHex = $endEntityCert['serialNumberHex']; + } + if (isset($endEntityCert['signatureTypeSN']) && !isset($signer->signatureTypeSN)) { + $signer->signatureTypeSN = $endEntityCert['signatureTypeSN']; + } + + if (isset($endEntityCert['subject']) && !isset($signer->subject)) { + $signer->subject = $endEntityCert['subject']; + } + + if (isset($endEntityCert['crl_urls']) && !isset($signer->crl_urls)) { + $signer->crl_urls = $endEntityCert['crl_urls']; + } + if (isset($endEntityCert['crl_validation']) && !isset($signer->crl_validation)) { + $signer->crl_validation = $endEntityCert['crl_validation']; + } + if (isset($endEntityCert['crl_revoked_at']) && !isset($signer->crl_revoked_at)) { + $signer->crl_revoked_at = $endEntityCert['crl_revoked_at']; + } + + if (isset($endEntityCert['signature_validation']) && !isset($signer->signature_validation)) { + $signer->signature_validation = $endEntityCert['signature_validation']; + } + + if (isset($endEntityCert['isLibreSignRootCA']) && !isset($signer->isLibreSignRootCA)) { + $signer->isLibreSignRootCA = $endEntityCert['isLibreSignRootCA']; + } + } + + /** + * @param callable(array, string): (?string) $resolveUid + */ + private function tryMatchWithExistingSigners(array $certData, array $existingSigners, string $host, callable $resolveUid): ?string { + if (empty($existingSigners)) { + return null; + } + + $certSerialNumber = $certData['serialNumber'] ?? null; + $certSerialNumberHex = $certData['serialNumberHex'] ?? null; + $certHash = $certData['hash'] ?? null; + + if (!$certSerialNumber && !$certSerialNumberHex && !$certHash) { + return null; + } + + foreach ($existingSigners as $signer) { + if (!isset($signer->metadata) || !is_array($signer->metadata)) { + continue; + } + + $certInfo = $signer->metadata['certificate_info'] ?? null; + if (!is_array($certInfo)) { + continue; + } + + if ($certSerialNumber && isset($certInfo['serialNumber']) && $certSerialNumber === $certInfo['serialNumber']) { + return $signer->uid ?? $resolveUid($certData, $host); + } + + if ($certSerialNumberHex && isset($certInfo['serialNumberHex']) && $certSerialNumberHex === $certInfo['serialNumberHex']) { + return $signer->uid ?? $resolveUid($certData, $host); + } + + if ($certHash && isset($certInfo['hash']) && $certHash === $certInfo['hash']) { + return $signer->uid ?? $resolveUid($certData, $host); + } + } + + return null; + } +} diff --git a/lib/Service/File/SignersLoader.php b/lib/Service/File/SignersLoader.php index ae1ec21d6b..caf935c374 100644 --- a/lib/Service/File/SignersLoader.php +++ b/lib/Service/File/SignersLoader.php @@ -8,7 +8,6 @@ namespace OCA\Libresign\Service\File; -use DateTime; use DateTimeInterface; use OCA\Libresign\Db\File; use OCA\Libresign\Db\SignRequestMapper; @@ -16,13 +15,13 @@ use OCA\Libresign\Service\SubjectAlternativeNameService; use OCP\Accounts\IAccountManager; use OCP\IUserManager; -use stdClass; /** * Handles loading signer data for files */ class SignersLoader { private bool $signersLibreSignLoaded = false; + private CertificateSignersMergeService $certificateSignersMergeService; public function __construct( private SignRequestMapper $signRequestMapper, @@ -31,11 +30,12 @@ public function __construct( private IAccountManager $accountManager, private IUserManager $userManager, ) { + $this->certificateSignersMergeService = new CertificateSignersMergeService(); } public function loadLibreSignSigners( ?File $file, - stdClass $fileData, + \stdClass $fileData, FileResponseOptions $options, array $certData = [], ): void { @@ -54,7 +54,7 @@ public function loadLibreSignSigners( foreach ($signers as $signer) { $identifyMethods = $identifyMethodsBatch[$signer->getId()] ?? []; if (!empty($fileData->signers)) { - $found = array_filter($fileData->signers, function (stdClass $found) use ($identifyMethods) { + $found = array_filter($fileData->signers, function (\stdClass $found) use ($identifyMethods) { if (!isset($found->uid)) { return false; } @@ -78,7 +78,7 @@ public function loadLibreSignSigners( $index = 0; } if (!isset($fileData->signers[$index])) { - $fileData->signers[$index] = new stdClass(); + $fileData->signers[$index] = new \stdClass(); } $fileData->signers[$index]->signRequestId = $signer->getId(); $fileData->signers[$index]->signed = $signer->getSigned()?->format(DateTimeInterface::ATOM); @@ -160,7 +160,7 @@ public function loadLibreSignSigners( } $fileData->signers[$index]->me = false; if ($options->getMe() || $options->getIdentifyMethodId()) { - $currentUserData = new stdClass(); + $currentUserData = new \stdClass(); $currentUserData->me = false; foreach ($identifyMethods as $methods) { foreach ($methods as $identifyMethod) { @@ -209,269 +209,23 @@ public function loadLibreSignSigners( $this->signersLibreSignLoaded = true; } - public function loadSignersFromCertData(stdClass $fileData, array $certData, string $host): void { - $existingSigners = $fileData->signers ?? []; - $indexMap = $this->buildSignerIndexMap($existingSigners); - $usedIndexes = []; - - foreach ($certData as $index => $signer) { - $targetIndex = $index; - $isLibreSignMatch = false; - - $resolvedUid = $this->tryMatchWithExistingSigners($signer['chain'][0], $existingSigners, $host); - if (!$resolvedUid) { - $isLibreSignCert = isset($signer['chain'][0]['isLibreSignRootCA']) - && $signer['chain'][0]['isLibreSignRootCA'] === true; - if ($isLibreSignCert) { - $certUid = $signer['chain'][0]['subject']['UID'] ?? null; - if ($certUid) { - $resolvedUid = str_contains($certUid, ':') ? $certUid : 'account:' . $certUid; - } else { - $resolvedUid = null; - } - } else { - $resolvedUid = $signer['uid'] ?? null; - if (!$resolvedUid && isset($signer['chain'][0])) { - $resolvedUid = $this->identifyMethodService->resolveUid($signer['chain'][0], $host); - } - } - } - - $matchedIndex = $this->findMatchingSignerIndex($indexMap, $resolvedUid, $signer); - if ($matchedIndex !== null) { - $targetIndex = $matchedIndex; - $isLibreSignMatch = isset($existingSigners[$matchedIndex]->signRequestId); - } else { - if (!empty($existingSigners)) { - $targetIndex = $this->nextAvailableSignerIndex($existingSigners, $usedIndexes); - } - } - $usedIndexes[$targetIndex] = true; - - if (!isset($fileData->signers[$targetIndex])) { - $fileData->signers[$targetIndex] = new stdClass(); - } - - $preservedDisplayName = $isLibreSignMatch && isset($fileData->signers[$targetIndex]->displayName) - ? $fileData->signers[$targetIndex]->displayName - : null; - - $fileData->signers[$targetIndex]->status = 2; - $fileData->signers[$targetIndex]->statusText = $this->signRequestMapper->getTextOfSignerStatus(2); - - if (isset($signer['timestamp'])) { - $fileData->signers[$targetIndex]->timestamp = $signer['timestamp']; - if (isset($signer['timestamp']['genTime']) && $signer['timestamp']['genTime'] instanceof DateTimeInterface) { - $fileData->signers[$targetIndex]->timestamp['genTime'] = $signer['timestamp']['genTime']->format(DateTimeInterface::ATOM); - } - } - if (isset($signer['signingTime']) && $signer['signingTime'] instanceof DateTimeInterface) { - $fileData->signers[$targetIndex]->signingTime = $signer['signingTime']; - $fileData->signers[$targetIndex]->signed = $signer['signingTime']->format(DateTimeInterface::ATOM); - } - if (isset($signer['docmdp'])) { - $fileData->signers[$targetIndex]->docmdp = $signer['docmdp']; - } - if (isset($signer['docmdp_validation'])) { - $fileData->signers[$targetIndex]->docmdp_validation = $signer['docmdp_validation']; - } - if (isset($signer['modifications'])) { - $fileData->signers[$targetIndex]->modifications = $signer['modifications']; - } - if (isset($signer['modification_validation'])) { - $fileData->signers[$targetIndex]->modification_validation = $signer['modification_validation']; - } - - if (isset($signer['chain'])) { - $this->processChainData($fileData->signers[$targetIndex], $signer['chain']); - } - - if (isset($signer['uid'])) { - $fileData->signers[$targetIndex]->uid = $signer['uid']; - } elseif ($resolvedUid) { - $fileData->signers[$targetIndex]->uid = $resolvedUid; - } elseif (isset($signer['chain'][0])) { - $fileData->signers[$targetIndex]->uid = $this->identifyMethodService->resolveUid($signer['chain'][0], $host); - } - - if (isset($signer['signDate'])) { - $fileData->signers[$targetIndex]->signDate = $signer['signDate']; - } - if (isset($signer['type'])) { - $fileData->signers[$targetIndex]->type = $signer['type']; - } - - if ($preservedDisplayName) { - $fileData->signers[$targetIndex]->displayName = $preservedDisplayName; - } elseif (isset($fileData->signers[$targetIndex]->uid) && str_starts_with($fileData->signers[$targetIndex]->uid, 'account:')) { - $accountId = substr($fileData->signers[$targetIndex]->uid, strlen('account:')); + public function loadSignersFromCertData(\stdClass $fileData, array $certData, string $host): void { + $this->certificateSignersMergeService->merge( + $fileData, + $certData, + $host, + $this->signRequestMapper->getTextOfSignerStatus(2), + fn (array $certData, string $currentHost): ?string => $this->identifyMethodService->resolveUid($certData, $currentHost), + fn (string $method, string $value): string => $this->subjectAlternativeNameService->build($method, $value), + function (string $accountId): ?string { $user = $this->userManager->get($accountId); - if ($user) { - $fileData->signers[$targetIndex]->displayName = $user->getDisplayName(); - } else { - $fileData->signers[$targetIndex]->displayName = $accountId; - } - } elseif (!isset($fileData->signers[$targetIndex]->displayName) && isset($signer['chain'][0])) { - $fileData->signers[$targetIndex]->displayName = $signer['chain'][0]['name'] ?? ($signer['chain'][0]['subject']['CN'] ?? ''); - } - - if (isset($fileData->signers[$targetIndex]->uid)) { - $indexMap[strtolower((string)$fileData->signers[$targetIndex]->uid)] = $targetIndex; - } - } - } - - private function buildSignerIndexMap(array $signers): array { - $map = []; - foreach ($signers as $index => $signer) { - if (isset($signer->uid)) { - $map[strtolower((string)$signer->uid)] = $index; - } - if (!empty($signer->identifyMethods)) { - foreach ($signer->identifyMethods as $identifyMethod) { - if (isset($identifyMethod['method']) && isset($identifyMethod['value'])) { - $identifier = $this->subjectAlternativeNameService->build($identifyMethod['method'], $identifyMethod['value']); - $map[strtolower($identifier)] = $index; - } - } - } - } - return $map; - } - - private function findMatchingSignerIndex(array $indexMap, ?string $resolvedUid, array $certSigner): ?int { - $identifiers = []; - if ($resolvedUid) { - $identifiers[] = strtolower($resolvedUid); - } - if (!empty($certSigner['uid'])) { - $identifiers[] = strtolower((string)$certSigner['uid']); - } - foreach ($identifiers as $identifier) { - if (isset($indexMap[$identifier])) { - return $indexMap[$identifier]; - } - } - return null; - } - - private function nextAvailableSignerIndex(array $existingSigners, array $usedIndexes): int { - $index = count($existingSigners); - while (isset($existingSigners[$index]) || isset($usedIndexes[$index])) { - $index++; - } - return $index; + return $user ? $user->getDisplayName() : null; + }, + ); } public function reset(): void { $this->signersLibreSignLoaded = false; } - private function processChainData(stdClass $signer, array $chain): void { - $signer->chain = []; - - foreach ($chain as $chainIndex => $chainItem) { - $chainArr = $chainItem; - - if (isset($chainItem['validFrom_time_t']) && is_numeric($chainItem['validFrom_time_t'])) { - $chainArr['valid_from'] = (new DateTime('@' . $chainItem['validFrom_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM); - } - if (isset($chainItem['validTo_time_t']) && is_numeric($chainItem['validTo_time_t'])) { - $chainArr['valid_to'] = (new DateTime('@' . $chainItem['validTo_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM); - } - - $chainArr['displayName'] = $chainArr['name'] ?? ($chainArr['subject']['CN'] ?? ''); - $signer->chain[$chainIndex] = $chainArr; - } - - if (isset($chain[0])) { - $this->enrichSignerWithCertificateValidation($signer, $chain[0]); - } - } - - private function enrichSignerWithCertificateValidation(stdClass $signer, array $endEntityCert): void { - if (isset($endEntityCert['name']) && !isset($signer->name)) { - $signer->name = $endEntityCert['name']; - } - if (isset($endEntityCert['hash']) && !isset($signer->hash)) { - $signer->hash = $endEntityCert['hash']; - } - if (isset($endEntityCert['serialNumber']) && !isset($signer->serialNumber)) { - $signer->serialNumber = $endEntityCert['serialNumber']; - } - if (isset($endEntityCert['serialNumberHex']) && !isset($signer->serialNumberHex)) { - $signer->serialNumberHex = $endEntityCert['serialNumberHex']; - } - if (isset($endEntityCert['signatureTypeSN']) && !isset($signer->signatureTypeSN)) { - $signer->signatureTypeSN = $endEntityCert['signatureTypeSN']; - } - - if (isset($endEntityCert['subject']) && !isset($signer->subject)) { - $signer->subject = $endEntityCert['subject']; - } - - if (isset($endEntityCert['crl_urls']) && !isset($signer->crl_urls)) { - $signer->crl_urls = $endEntityCert['crl_urls']; - } - if (isset($endEntityCert['crl_validation']) && !isset($signer->crl_validation)) { - $signer->crl_validation = $endEntityCert['crl_validation']; - } - if (isset($endEntityCert['crl_revoked_at']) && !isset($signer->crl_revoked_at)) { - $signer->crl_revoked_at = $endEntityCert['crl_revoked_at']; - } - - if (isset($endEntityCert['signature_validation']) && !isset($signer->signature_validation)) { - $signer->signature_validation = $endEntityCert['signature_validation']; - } - - if (isset($endEntityCert['isLibreSignRootCA']) && !isset($signer->isLibreSignRootCA)) { - $signer->isLibreSignRootCA = $endEntityCert['isLibreSignRootCA']; - } - } - - private function tryMatchWithExistingSigners(array $certData, array $existingSigners, string $host): ?string { - if (empty($existingSigners)) { - return null; - } - - $certSerialNumber = $certData['serialNumber'] ?? null; - $certSerialNumberHex = $certData['serialNumberHex'] ?? null; - $certHash = $certData['hash'] ?? null; - - if (!$certSerialNumber && !$certSerialNumberHex && !$certHash) { - return null; - } - - foreach ($existingSigners as $signer) { - if (!isset($signer->metadata) || !is_array($signer->metadata)) { - continue; - } - - $certInfo = $signer->metadata['certificate_info'] ?? null; - if (!is_array($certInfo)) { - continue; - } - - if ($certSerialNumber && isset($certInfo['serialNumber'])) { - if ($certSerialNumber === $certInfo['serialNumber']) { - return $signer->uid ?? $this->identifyMethodService->resolveUid($certData, $host); - } - } - - if ($certSerialNumberHex && isset($certInfo['serialNumberHex'])) { - if ($certSerialNumberHex === $certInfo['serialNumberHex']) { - return $signer->uid ?? $this->identifyMethodService->resolveUid($certData, $host); - } - } - - if ($certHash && isset($certInfo['hash'])) { - if ($certHash === $certInfo['hash']) { - return $signer->uid ?? $this->identifyMethodService->resolveUid($certData, $host); - } - } - } - - return null; - } - } diff --git a/src/components/validation/SignerDetails.vue b/src/components/validation/SignerDetails.vue index 89e2ef89c2..7e26bc1413 100644 --- a/src/components/validation/SignerDetails.vue +++ b/src/components/validation/SignerDetails.vue @@ -59,6 +59,9 @@ + + +