mirror of
https://github.com/24eme/signaturepdf
synced 2026-03-14 13:55:44 +01:00
Merge branch 'master' of github.com:24eme/signaturepdf
This commit is contained in:
commit
aad3dddf88
6 changed files with 243 additions and 31 deletions
19
README.md
19
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
60
app.php
60
app.php
|
|
@ -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');
|
||||
|
|
@ -68,6 +69,11 @@ if (!$f3->exists('PDF_STORAGE_ENCRYPTION')) {
|
|||
$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");
|
||||
|
|
@ -167,7 +173,7 @@ $f3->route('POST /sign',
|
|||
$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) {
|
||||
|
|
@ -186,7 +192,7 @@ $f3->route('POST /sign',
|
|||
}
|
||||
|
||||
if($formFieldName == "svg") {
|
||||
$svgFiles .= " ".$tmpfile."_".$fileBaseName;
|
||||
$svgFiles[] = $tmpfile."_".$fileBaseName;
|
||||
|
||||
return basename($tmpfile."_".$fileBaseName);
|
||||
}
|
||||
|
|
@ -196,12 +202,13 @@ $f3->route('POST /sign',
|
|||
$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')) {
|
||||
|
|
@ -215,21 +222,24 @@ $f3->route('POST /share',
|
|||
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);
|
||||
|
|
@ -245,7 +255,7 @@ $f3->route('POST /share',
|
|||
return $filename;
|
||||
}
|
||||
if($formFieldName == "svg") {
|
||||
$svgFiles .= " ".$tmpfile."_".$fileBaseName;
|
||||
$svgFiles[] = $tmpfile."_".$fileBaseName;
|
||||
return basename($tmpfile."_".$fileBaseName);
|
||||
}
|
||||
});
|
||||
|
|
@ -253,26 +263,20 @@ $f3->route('POST /share',
|
|||
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));
|
||||
}
|
||||
|
||||
);
|
||||
|
|
@ -320,7 +324,7 @@ $f3->route('POST /signature/@hash/save',
|
|||
}
|
||||
|
||||
$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();
|
||||
|
|
@ -339,7 +343,7 @@ $f3->route('GET /signature/@hash/nblayers',
|
|||
$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++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
lib/NSSCryptography.class.php
Normal file
113
lib/NSSCryptography.class.php
Normal file
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -15,10 +15,24 @@ class PDFSignature
|
|||
$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;
|
||||
|
|
@ -44,7 +58,7 @@ class PDFSignature
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +73,7 @@ class PDFSignature
|
|||
$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();
|
||||
|
|
@ -67,6 +81,17 @@ class PDFSignature
|
|||
$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);
|
||||
|
|
|
|||
46
tools/create_nss_certs.sh
Normal file
46
tools/create_nss_certs.sh
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
|
||||
nss_dir=$1
|
||||
nss_pass=$2
|
||||
nss_nick=$3
|
||||
signaturepdf_url=$4
|
||||
|
||||
if ! test "$signaturepdf_url"; then
|
||||
echo "Usage:" 1>&2 ;
|
||||
echo " $0 <nss_dir> <nss_pass> <nss_nick> <signaturepdf_url>" 1>&2 ;
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
if ! test -d "$nss_dir"; then
|
||||
echo "ERROR: nss_dir \"$nss_dir\" should exist" 1>&2 ;
|
||||
exit 2;
|
||||
fi
|
||||
if echo "$nss_nick" | grep '\.' > /dev/null ; then
|
||||
echo "ERROR: $nss_nick should not contain . " 1>&2 ;
|
||||
exit 3;
|
||||
fi
|
||||
|
||||
signaturepdf_domain=$(echo $signaturepdf_url | sed 's/https*:\/\///' | sed 's/\/.*//')
|
||||
signaturepdf_path=$(echo $signaturepdf_url | sed 's/https*:\/\///' | sed 's/.*'$signaturepdf_domain'\/*//')
|
||||
signaturepdf_dc=$(echo $signaturepdf_domain | tr '.' '\n' | sed 's/^/DC=/' | tr '\n' ',' | sed 's/,$//')
|
||||
if test "$signaturepdf_path"; then
|
||||
signaturepdf_dc="DC=/"$signaturepdf_path','$signaturepdf_dc;
|
||||
fi
|
||||
|
||||
echo "$nss_pass" > /tmp/nss.$$.tmp
|
||||
certutil -N -f /tmp/nss.$$.tmp -d "$nss_dir"
|
||||
|
||||
echo $RANDOM" CACert "$(date)" $$ "$RANDOM | shasum > /tmp/nss_noise.$$.tmp
|
||||
certutil -S -s "CN=PDF Sign CA Cert,$signaturepdf_dc" -n "PDFSignCA" -z /tmp/nss_noise.$$.tmp -x -t "CT,CT,CT" -v 120 -m 1234 -d "$nss_dir" -f /tmp/nss.$$.tmp
|
||||
|
||||
echo $RANDOM" $signaturepdf_dc "$(date)" $$ "$RANDOM | shasum >> /tmp/nss_noise.$$.tmp
|
||||
certutil -S -s "CN=$nss_nick,$signaturepdf_dc" -n "$nss_nick" -c "PDFSignCA" -z /tmp/nss_noise.$$.tmp -t ",," -m 730 -d "$nss_dir" -f /tmp/nss.$$.tmp
|
||||
|
||||
echo "Certs created :"
|
||||
echo "==============="
|
||||
certutil -f /tmp/nss.$$.tmp -d $nss_dir -L
|
||||
echo "Private keys created :"
|
||||
echo "======================"
|
||||
certutil -f /tmp/nss.$$.tmp -d $nss_dir -K
|
||||
|
||||
rm /tmp/nss.$$.tmp /tmp/nss_noise.$$.tmp
|
||||
Loading…
Add table
Add a link
Reference in a new issue