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 @@
+
+
+