Skip to content

Commit

Permalink
Merge branch 'master' of github.com:24eme/signaturepdf
Browse files Browse the repository at this point in the history
  • Loading branch information
tale-fau committed Jul 31, 2024
2 parents 45e61cc + 01879b8 commit aad3ddd
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 31 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,25 @@ For example, for Apache:
```
chown www-data /path/to/folder/to/store/pdf
```
### Enabling digital signature

The digital signature depends on `pdfsig` from the poppler project (poppler-utils debian package) and `certutil` from libnss3 project (libnss3-tools debian package).

On debian :

```
sudo apt-get install poppler-utils libnss3-tool
```

To enable digital signature, create certificates in a NSS database. The shell script `create_nss_certs.sh` in `tools` helps to do it :

bash tools/create_nss_certs.sh NSS_DIRECORY/ MY_NSS_PASSWORD MY_CERT_NICK MY_SIGNATUREPDF_URL

Once created, set the following directives in the `config/config.ini` file.

NSS3_DIRECTORY=NSS_DIRECORY/
NSS3_PASSWORD="MY_NSS_PASSWORD"
NSS3_NICK="MY_CERT_NICK"

### Disabling the Organize Mode

Expand Down
60 changes: 32 additions & 28 deletions app.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

setlocale(LC_ALL, "");
require(__DIR__.'/lib/GPGCryptography.class.php');
require(__DIR__.'/lib/NSSCryptography.class.php');
require(__DIR__.'/lib/PDFSignature.class.php');
require(__DIR__.'/lib/Image2SVG.class.php');
require(__DIR__.'/lib/Compression.class.php');
Expand Down Expand Up @@ -68,6 +69,11 @@
$f3->set('PDF_STORAGE_ENCRYPTION', GPGCryptography::isGpgInstalled());
}

if ($f3->exists('NSS3_DIRECTORY') && $f3->exists('NSS3_PASSWORD') && $f3->exists('NSS3_NICK')) {
NSSCryptography::getInstance($f3->get('NSS3_DIRECTORY'), $f3->get('NSS3_PASSWORD'), $f3->get('NSS3_NICK'));
}


$domain = basename(glob($f3->get('ROOT')."/locale/application_*.pot")[0], '.pot');

bindtextdomain($domain, $f3->get('ROOT')."/locale");
Expand Down Expand Up @@ -167,7 +173,7 @@ function($f3) {
$filename = null;
$tmpfile = tempnam($f3->get('UPLOADS'), 'pdfsignature_sign');
unlink($tmpfile);
$svgFiles = "";
$svgFiles = [];

$files = Web::instance()->receive(function($file,$formFieldName){
if($formFieldName == "pdf" && strpos(Web::instance()->mime($file['tmp_name'], true), 'application/pdf') !== 0) {
Expand All @@ -186,7 +192,7 @@ function($f3) {
}

if($formFieldName == "svg") {
$svgFiles .= " ".$tmpfile."_".$fileBaseName;
$svgFiles[] = $tmpfile."_".$fileBaseName;

return basename($tmpfile."_".$fileBaseName);
}
Expand All @@ -196,12 +202,13 @@ function($f3) {
$f3->error(403);
}

if(!$svgFiles) {
if(!count($svgFiles)) {
$f3->error(403);
}

shell_exec(sprintf("rsvg-convert -f pdf -o %s %s", $tmpfile.'.svg.pdf', $svgFiles));
shell_exec(sprintf("pdftk %s multistamp %s output %s", $tmpfile.".pdf", $tmpfile.'.svg.pdf', $tmpfile.'_signe.pdf'));
PDFSignature::createPDFFromSvg($svgFiles, $tmpfile.'.svg.pdf');
PDFSignature::addSvgToPDF($tmpfile.'.pdf', $tmpfile.'.svg.pdf', $tmpfile.'_signe.pdf');

Web::instance()->send($tmpfile.'_signe.pdf', null, 0, TRUE, $filename);

if($f3->get('DEBUG')) {
Expand All @@ -215,21 +222,24 @@ function($f3) {
function($f3) {
$hash = Web::instance()->slug($_POST['hash']);
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$f3->set('UPLOADS', $sharingFolder."/");
$symmetricKey = (isset($_COOKIE[$hash])) ? GPGCryptography::protectSymmetricKey($_COOKIE[$hash]) : null;

if (!is_dir($f3->get('PDF_STORAGE_PATH'))) {
$f3->error(500, 'Sharing folder doesn\'t exist');
}
if (!is_writable($f3->get('PDF_STORAGE_PATH'))) {
$f3->error(500, 'Sharing folder is not writable');
}
mkdir($sharingFolder);
$expireFile = $sharingFolder.".expire";
file_put_contents($expireFile, $f3->get('POST.duration'));
touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));

$pdfSignature = new PDFSignature($sharingFolder, $symmetricKey);
$pdfSignature->createShare($f3->get('POST.duration'));

$f3->set('UPLOADS', $sharingFolder."/");

$filename = "original.pdf";
$tmpfile = tempnam($sharingFolder, date('YmdHis'));
unlink($tmpfile);
$svgFiles = "";
$svgFiles = [];
$files = Web::instance()->receive(function($file,$formFieldName){
if($formFieldName == "pdf" && strpos(Web::instance()->mime($file['tmp_name'], true), 'application/pdf') !== 0) {
$f3->error(403);
Expand All @@ -245,34 +255,28 @@ function($f3) {
return $filename;
}
if($formFieldName == "svg") {
$svgFiles .= " ".$tmpfile."_".$fileBaseName;
$svgFiles[] = $tmpfile."_".$fileBaseName;
return basename($tmpfile."_".$fileBaseName);
}
});

if(!count($files)) {
$f3->error(403);
}
if($svgFiles) {
shell_exec(sprintf("rsvg-convert -f pdf -o %s %s", $tmpfile.'.svg.pdf', $svgFiles));
}
if(!$f3->get('DEBUG')) {
array_map('GPGCryptography::hardUnlink', glob($tmpfile."*.svg"));

$pdfSignature->saveShare();

if(count($svgFiles)) {
$pdfSignature->addSignature($svgFiles, $tmpfile.".svg.pdf");
}

$symmetricKey = "";
if (isset($_COOKIE[$hash])) {
$symmetricKey = "#" . $_COOKIE[$hash];
$encryptor = new GPGCryptography($_COOKIE[$hash], $f3->get('PDF_STORAGE_PATH').$hash);
if (!$encryptor->encrypt()) {
GPGCryptography::hardUnlink($sharingFolder);
$f3->error(500);
}
if(!$f3->get('DEBUG')) {
$pdfSignature->clean();
}

\Flash::instance()->setKey('openModal', 'shareinformations');

$f3->reroute($f3->get('REVERSE_PROXY_URL').'/signature/'.$hash.$symmetricKey);
$f3->reroute($f3->get('REVERSE_PROXY_URL').'/signature/'.$hash.(($symmetricKey) ? '#'.$symmetricKey : null));
}

);
Expand Down Expand Up @@ -320,7 +324,7 @@ function($f3) {
}

$pdfSignature = new PDFSignature($f3->get('PDF_STORAGE_PATH').$hash, $symmetricKey);
$pdfSignature->addSignature($svgFiles, $tmpfile."svg.pdf");
$pdfSignature->addSignature($svgFiles, $tmpfile.".svg.pdf");

if(!$f3->get('DEBUG')) {
$pdfSignature->clean();
Expand All @@ -339,7 +343,7 @@ function($f3) {
$files = scandir($f3->get('PDF_STORAGE_PATH').$hash);
$nbLayers = 0;
foreach($files as $file) {
if(strpos($file, 'svg.pdf') !== false) {
if(strpos($file, '.svg.pdf') !== false) {
$nbLayers++;
}
}
Expand Down
5 changes: 5 additions & 0 deletions config/config.ini.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ PDF_STORAGE_PATH=/path/to/folder

; Encryption activation (default activation if GPG is installed)
;PDF_STORAGE_ENCRYPTION=true

;NSS3 configuration (used to sign pdf with pdfsig)
;NSS3_DIRECTORY=/path/to/nss3
;NSS3_PASSWORD="my secret password"
;NSS3_NICK="certificate nick"
113 changes: 113 additions & 0 deletions lib/NSSCryptography.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

class NSSCryptography
{
private $nss_directory = null;
private $nss_password = null;
private $nss_nick = null;

private function __construct($dir, $pass, $nick) {
$this->nss_directory = $dir;
$this->nss_password = $pass;
$this->nss_nick = $nick;
}

private static $instance = null;

public static function getInstance($dir = null, $pass = null, $nick = null) {
if (!self::$instance) {
self::$instance = new NSSCryptography($dir, $pass, $nick);
}
return self::$instance;
}

public function addSignature($pdf_path, $reason) {
putenv('NSSPASS='.$this->nss_password);
exec('pdfsig '.$pdf_path.' '.$pdf_path.'.signed.pdf -add-signature -nssdir "'.$this->nss_directory.'" -nss-pwd "$NSSPASS" -nick "'.$this->nss_nick.'" -reason "'.$reason.'" 2>&1', $output, $returnCode);
if ($returnCode) {
throw new Exception('pdfsign error: '.implode(' ', $output));
}
rename($pdf_path.'.signed.pdf', $pdf_path);
}

public function verify($pdf_path) {
$signatures = [];

putenv('NSSPASS='.$this->nss_password);
exec('pdfsig -nssdir "'.$this->nss_directory.'" -nss-pwd "$NSSPASS" '.$pdf_path.' 2>&1', $output, $returnCode);

if ($returnCode && !preg_match('/does not contain any signatures/', $output[0])) {
throw new Exception('pdfsign error: '.implode(' ', $output));
}

$index = null;
foreach($output as $l) {
if (preg_match('/^(Signature[^:]*):/', $l, $m)) {
$index = $m[1];
$signatures[$index] = [];
continue;
}
if (preg_match('/^ - ([^:]*):(.*)/', $l, $m)) {
$signatures[$index][$m[1]] = $m[2];
}elseif (preg_match('/^ - (.*) (document signed)/', $l, $m)) {
$signatures[$index]["Document signed"] = $m[1];
}
}
return $signatures;
}

public function isEnabled() {
if (!$this->nss_directory || !$this->nss_nick) {
return false;
}
return true;
}

public function isPDFSigConfigured() {
if (!$this->isEnabled()) {
return false;
}
if (!$this->isPDFSigInstalled()) {
return false;
}
if (!$this->isCertUtilInstalled()) {
return false;
}

$file = tempnam('/tmp', 'certutil');
file_put_contents($file, $this->nss_password);
exec('certutil -f '.$file.' -d '.$this->nss_directory.' -L -n "'.$this->nss_nick.'" 2>&1', $output, $returnCodeL);
exec('certutil -f '.$file.' -d '.$this->nss_directory.' -K | grep ":'.$this->nss_nick.'" 2>&1', $output, $returnCodeK);
unlink($file);

return ($returnCodeL == 0 && $returnCodeK == 0);
}

public static function isCertUtilInstalled() {
$output = null;
$returnCode = null;
exec('certutil -v 2>&1', $output, $returnCode);
if ($returnCode != 1) {
return false;
}
return "OK";
}


public static function isPDFSigInstalled() {
$output = null;
$returnCode = null;
exec('pdfsig -v 2>&1', $output, $returnCode);

if ($returnCode != 0) {
return false;
}

$version = explode(' ', $output[0])[2];
if (! $version >= "21") {
return false;
}
return $version;

}
}
31 changes: 28 additions & 3 deletions lib/PDFSignature.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,24 @@ public function __construct($pathHash, $symmetricKey = null) {
$this->gpg = new GPGCryptography($symmetricKey, $pathHash);
}


public function createShare($duration) {
mkdir($this->pathHash);
$expireFile = $this->pathHash.".expire";
file_put_contents($expireFile, $duration);
touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));
}

public function saveShare() {
if($this->symmetricKey) {
$this->gpg->encrypt();
}
}

public function getPDF() {
$sharingFolder = $this->gpg->decrypt();
if ($sharingFolder == false) {
throw new Exception( "PDF file could not be decrypted. Cookie encryption key might be missing.");
throw new Exception("PDF file could not be decrypted. Cookie encryption key might be missing.");
}
if ($this->pathHash != $sharingFolder && $this->gpg->isEncrypted()) {
$this->toClean[] = $sharingFolder;
Expand All @@ -44,7 +58,7 @@ public function getPDF() {
copy($originalFile, $finalFile);
$bufferFile = $finalFile.".tmp";
foreach($layers as $layerFile) {
shell_exec(sprintf("pdftk %s multistamp %s output %s", $finalFile, $layerFile, $bufferFile));
self::addSvgToPDF($finalFile, $layerFile, $bufferFile, false);
rename($bufferFile, $finalFile);
}

Expand All @@ -59,14 +73,25 @@ public function addSignature(array $svgFiles, $outputPdfFile) {
$expireFile = $this->pathHash.".expire";
touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));

shell_exec(sprintf("rsvg-convert -f pdf -o %s %s", $outputPdfFile, implode(" ", $svgFiles)));
self::createPDFFromSvg($svgFiles, $outputPdfFile);

if($this->gpg->isEncrypted()) {
$this->gpg->encrypt();
}
$this->toClean = array_merge($this->toClean, $svgFiles);
}

public static function createPDFFromSvg(array $svgFiles, $outputPdfFile) {
shell_exec(sprintf("rsvg-convert -f pdf -o %s %s", $outputPdfFile, implode(" ", $svgFiles)));
}

public static function addSvgToPDF($pdfOrigin, $pdfSvg, $pdfOutput, $digitalSignature = true) {
shell_exec(sprintf("pdftk %s multistamp %s output %s", $pdfOrigin, $pdfSvg, $pdfOutput));
if (NSSCryptography::getInstance()->isEnabled() && $digitalSignature) {
NSSCryptography::getInstance()->addSignature($pdfOutput, 'Signed with SignaturePDF');
}
}

public function clean() {
foreach($this->toClean as $path) {
GPGCryptography::hardUnlink($path);
Expand Down
Loading

0 comments on commit aad3ddd

Please sign in to comment.