Newer
Older
* Copyright Maarch since 2008 under license.
* See LICENSE.txt file at the root folder for more details.
* This file is part of Maarch software.
*
*/
/**
* @brief Certificate Signature Controller
* @author dev@maarch.org
*/
namespace Document\controllers;
use Docserver\controllers\DocserverController;
use Docserver\models\AdrModel;
use Docserver\models\DocserverModel;

Florian Azizian
committed
use Document\models\DocumentModel;
use SrcCore\models\CoreConfigModel;

Florian Azizian
committed
use User\models\UserModel;
class CertificateSignatureController
{
public static $signatureLength = 30000;
public static function getHashedCertificate(array $args)
$tmpPath = CoreConfigModel::getTmpPath();
if (!empty($args['body']['tmpUniqueId'])) {
if (is_file("{$tmpPath}tmpSignatureEsign_{$GLOBALS['id']}_{$args['body']['tmpUniqueId']}.pdf")) {
$pathToDocument = "{$tmpPath}tmpSignatureEsign_{$GLOBALS['id']}_{$args['body']['tmpUniqueId']}.pdf";
}
}
if (empty($pathToDocument)) {
$adr = AdrModel::getDocumentsAdr([
'select' => ['path', 'filename'],
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'ESIGN']
$docserver = DocserverModel::getByType(['type' => 'ESIGN', 'select' => ['path']]);
} else {
$adr = AdrModel::getDocumentsAdr([
'select' => ['path', 'filename'],
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'DOC']
]);
$docserver = DocserverModel::getByType(['type' => 'DOC', 'select' => ['path']]);
$pathToDocument = $docserver['path'] . $adr[0]['path'] . $adr[0]['filename'];
$signedDocumentPath = $tmpPath . $GLOBALS['id'] . '_' . rand() . '_signedDocument.pdf';
$writer = new \SetaPDF_Core_Writer_File($signedDocumentPath);
$document = \SetaPDF_Core_Document::loadByFilename($pathToDocument, $writer);
$signer = new \SetaPDF_Signer($document);
$module = new \SetaPDF_Signer_Signature_Module_Pades();
$certificate = new \SetaPDF_Signer_X509_Certificate($args['body']['certificate']);
$module->setCertificate($certificate);
$informationResolverManager = new \SetaPDF_Signer_InformationResolver_Manager();
$informationResolverManager->addResolver(new \SetaPDF_Signer_InformationResolver_HttpCurlResolver());
$extraCerts = new \SetaPDF_Signer_X509_Collection();
$certificates = [$certificate];
while (count($certificates) > 0) {
$currentCertificate = array_pop($certificates);
$aia = $currentCertificate->getExtensions()->get(\SetaPDF_Signer_X509_Extension_AuthorityInformationAccess::OID);
if ($aia instanceof \SetaPDF_Signer_X509_Extension_AuthorityInformationAccess) {
foreach ($aia->fetchIssuers($informationResolverManager)->getAll() as $issuer) {
$extraCerts->add($issuer);
$certificates[] = $issuer;
}
$module->setExtraCertificates($extraCerts);
$signatureContentLength = $args['body']['signatureLength'] ?? CertificateSignatureController::$signatureLength;
foreach ($extraCerts->getAll() as $extraCert) {
$signatureContentLength += (strlen($extraCert->get(\SetaPDF_Signer_X509_Format::DER)) * 2);

Florian Azizian
committed
$user = UserModel::getById(['id' => $GLOBALS['id'], 'select' => ['firstname', 'lastname', 'email']]);
$signer->setName($user['firstname'] . ' ' . $user['lastname'] . ' ('. $user['email'] . ')');
$signer->setReason('Signature du document par ' . $user['firstname'] . ' ' . $user['lastname']);
$signer->setLocation('Maarch Parapheur');
$signer->setSignatureContentLength($signatureContentLength);
if (!empty($args['body']['signatures'][0])) {

Florian Azizian
committed
$pages = $document->getCatalog()->getPages();
$pageCount = $pages->count();
for ($pageNumber = 1; $pageNumber <= $pageCount; $pageNumber++) {
$signature = $args['body']['signatures'][0];

Florian Azizian
committed
if ($signature['page'] == $pageNumber) {

Florian Azizian
committed
$page = $pages->getPage($pageNumber);
$format = \SetaPDF_Core_PageFormats::getFormat($page->getWidthAndHeight(), \SetaPDF_Core_PageFormats::ORIENTATION_AUTO);

Florian Azizian
committed
$image = base64_decode($signature['encodedImage']);
if ($image === false) {
return ['errors' => 'base64_decode failed'];
}
$imageTmpPath = $tmpPath . $GLOBALS['id'] . '_' . rand() . '_writing.png';

Florian Azizian
committed
if ($signature['positionX'] == 0 && $signature['positionY'] == 0) {
$signWidth = $format['width'];
$signPosX = 0;
$signPosY = 0;
} else {
$signWidth = ($signature['width'] * $format['width']) / 100;
$signPosX = ($signature['positionX'] * $format['width']) / 100;
$signPosY = ($signature['positionY'] * $format['height']) / 100;
}
$signatureInfo = [
'page' => $signature['page'],
'positionX' => $signPosX,
'positionY' => $signPosY,
'filePath' => $imageTmpPath,
'signWidth' => $signWidth
];

Florian Azizian
committed
if ($signature['type'] == 'SVG') {
$Imagick = new \Imagick();
$Imagick->readImageBlob($image);
$Imagick->setImageFormat("png24");
$Imagick->writeImage($imageTmpPath);
$Imagick->clear();
$Imagick->destroy();
} else {
file_put_contents($imageTmpPath, $image);
}

Florian Azizian
committed
$signatureInfo['type'] = $signature['type'];

Florian Azizian
committed
}
}
}
if (!empty($signatureInfo)) {
$image = \SetaPDF_Core_Image::getByPath($signatureInfo['filePath']);
$imageXObject = $image->toXObject($document);
$width = $signatureInfo['signWidth'];
$height = $imageXObject->getHeight($width);

Florian Azizian
committed
$signatureFieldWith = $width > 200 ? $width : 200;

Florian Azizian
committed
$fieldName = $signer->addSignatureField(
'Signature_' . rand(),

Florian Azizian
committed
$signatureInfo['page'],
\SetaPDF_Signer_SignatureField::POSITION_LEFT_TOP,
['x' => $signatureInfo['positionX'], 'y' => -$signatureInfo['positionY']],

Florian Azizian
committed
$signatureFieldWith,

Florian Azizian
committed
$height + 50
)->getQualifiedName();
$signer->setSignatureFieldName($fieldName);

Florian Azizian
committed
$xObject = \SetaPDF_Core_XObject_Form::create($document, [0, 0, $signatureFieldWith, $height + 50]);

Florian Azizian
committed
$canvas = $xObject->getCanvas();
$imageXObject->draw($canvas, 0, 50, $width, $height);
$font = new \SetaPDF_Core_Font_Type0_Subset(
$document,
__DIR__ . '/fonts/dejavu-fonts-ttf-2.37/ttf/DejaVuSans.ttf'
);

Florian Azizian
committed
$loadedXml = CoreConfigModel::getConfig();

Florian Azizian
committed
if ($loadedXml->textWithDigitalSignature == 'true' && $signatureInfo['type'] != 'SVG') {

Florian Azizian
committed
$textBlock = new \SetaPDF_Core_Text_Block($font, 6);
$textBlock->setWidth($signatureFieldWith);
$textBlock->setLineHeight(14);

Florian Azizian
committed
$textBlock->setPaddingBottom(-5);

Florian Azizian
committed
$textBlock->setText("Signé électroniquement par : " . $user['firstname'] . ' ' . $user['lastname'] . "\nLe " . date('c'));
$textBlock->draw($canvas, 0, 30);
}

Florian Azizian
committed
$appearance = new \SetaPDF_Signer_Signature_Appearance_XObject($xObject);
$signer->setAppearance($appearance);
} else {
$fieldName = $signer->addSignatureField()->getQualifiedName();
$signer->setSignatureFieldName($fieldName);
}
$tempPath = \SetaPDF_Core_Writer_TempFile::createTempPath();
new \SetaPDF_Core_Writer_File($tempPath),
$module
$_SESSION['module'] = $module;
'dataToSign' => \SetaPDF_Core_Type_HexString::str2hex($module->getDataToSign($_SESSION['tmpDocument']->getHashFile())),

Florian Azizian
committed
'signatureContentLength' => $signatureContentLength,
'signatureFieldName' => $fieldName,
'tmpUniqueId' => $args['body']['tmpUniqueId'] ?? null
public static function signDocument(array $args)
$certificateSignature = \SetaPDF_Core_Type_HexString::hex2str($args['hashSignature']);
$tmpPath = CoreConfigModel::getTmpPath();
if (is_file("{$tmpPath}tmpSignatureEsign_{$GLOBALS['id']}_{$args['tmpUniqueId']}.pdf")) {
$pathToDocument = "{$tmpPath}tmpSignatureEsign_{$GLOBALS['id']}_{$args['tmpUniqueId']}.pdf";
}
}
if (empty($pathToDocument)) {
$adr = AdrModel::getDocumentsAdr([
'select' => ['path', 'filename'],
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'ESIGN']
$docserver = DocserverModel::getByType(['type' => 'ESIGN', 'select' => ['path']]);
} else {
$adr = AdrModel::getDocumentsAdr([
'select' => ['path', 'filename'],
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'DOC']
]);
$docserver = DocserverModel::getByType(['type' => 'DOC', 'select' => ['path']]);
$pathToDocument = $docserver['path'] . $adr[0]['path'] . $adr[0]['filename'];
$signedDocumentPath = $tmpPath . $GLOBALS['id'] . '_' . rand() . '_signedDocument.pdf';
$writer = new \SetaPDF_Core_Writer_File($signedDocumentPath);
$document = \SetaPDF_Core_Document::loadByFilename($pathToDocument, $writer);
$signer = new \SetaPDF_Signer($document);
$_SESSION['module']->setSignatureValue($certificateSignature);
$cms = (string)$_SESSION['module']->getCms();
$signatureContentLength = $args['signatureContentLength'];
$signer->saveSignature($_SESSION['tmpDocument'], $cms);
// unlink($tmpDocument->getWriter()->getPath());
} catch (\SetaPDF_Signer_Exception_ContentLength $e) {
// unlink($tmpDocument->getWriter()->getPath());
$signatureContentLength = $signatureContentLength + 1000;
return ['errors' => 'Not enough space for signature', 'newSignatureLength' => $signatureContentLength];
if ($args['signatureMode'] == 'rgs_2stars_timestamped') {

Florian Azizian
committed
$document = DocumentModel::getById(['select' => ['digital_signature_transaction_id'], 'id' => $args['id']]);
$config = DigitalSignatureController::getConfig();
$signedDocumentPath = DigitalSignatureController::timestampHashes([
'config' => $config,
'signedDocumentPath' => $signedDocumentPath,
'transactionId' => $document['digital_signature_transaction_id'],
'fieldName' => $args['signatureFieldName'],
'extraCertificate' => $args['certificate']

Florian Azizian
committed
]);
}

Florian Azizian
committed
if ($args['signatureMode'] == 'rgs_2stars_timestamped') {
DigitalSignatureController::terminate(['config' => $config, 'transactionId' => $document['digital_signature_transaction_id']]);
}
$storeInfos = DocserverController::storeResourceOnDocServer([
'encodedFile' => base64_encode(file_get_contents($signedDocumentPath)),
'format' => 'pdf',
'docserverType' => 'ESIGN'
]);

Florian Azizian
committed
if (!empty($storeInfos['errors'])) {
return ['errors' => $storeInfos['errors']];
}
AdrModel::deleteDocumentAdr([
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'ESIGN']
]);
AdrModel::createDocumentAdr([
'documentId' => $args['id'],
'type' => 'ESIGN',
'path' => $storeInfos['path'],
'filename' => $storeInfos['filename'],
'fingerprint' => $storeInfos['fingerprint']
]);
} else {
copy($signedDocumentPath, "{$tmpPath}tmpSignatureEsign_{$GLOBALS['id']}_{$args['tmpUniqueId']}.pdf");
}
unlink($signedDocumentPath);
public static function signWithServerCertificate(\setasign\Fpdi\Tcpdf\Fpdi &$pdf)
{
$loadedXml = CoreConfigModel::getConfig();
if ($loadedXml->electronicSignature->enable == 'true') {
$certPath = realpath((string)$loadedXml->electronicSignature->certPath);
$privateKeyPath = realpath((string)$loadedXml->electronicSignature->privateKeyPath);
if (!is_file($certPath) || !is_file($privateKeyPath)) {
return ['errors' => 'Signature with server certificate failed : certPath or privateKeyPath is not valid'];
}
$certificate = 'file://' . $certPath;
$privateKey = 'file://' . $privateKeyPath;
$info = [
'Name' => (string)$loadedXml->electronicSignature->certInfo->name,
'Location' => (string)$loadedXml->electronicSignature->certInfo->location,
'Reason' => (string)$loadedXml->electronicSignature->certInfo->reason,
'ContactInfo' => (string)$loadedXml->electronicSignature->certInfo->contactInfo
];
$pdf->setSignature($certificate, $privateKey, (string)$loadedXml->electronicSignature->password, '', 2, $info);
}
return true;
}