SetaPDF Demos

There seems to be a problem loading the components. Please check your PHP error logs for details!

Common issues could be that you missed to install the trial license or that you are using a trial version on an unsupported PHP version.

Fortify Web-component

This demo shows an implementation with the web-component of Fortify.

PHP
<?php

if ($_SERVER['SERVER_NAME'] !== 'localhost' && (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off')) {
    throw new Exception('This demo must run on localhost or HTTPS.');
}

$path = substr($_SERVER['PHP_SELF'], 0, -strlen(basename(__FILE__)));
$controllerPath = 'https://' . $_SERVER['HTTP_HOST'] . $path . 'controller.php';

?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>SetaPDF-Signer meets Fortify</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pure-css-loader@3.3.3/dist/css-loader.css">
    <link rel="stylesheet" href="https://unpkg.com/@peculiar/fortify-webcomponents@^1.0.0/dist/peculiar/peculiar.css">
    <style>
        * {
            box-sizing: border-box;
        }

        body {
            font-family: "Open Sans", "Arial", sans-serif;
            font-size: 14px;
            height: 100vh;
            color: rgb(64, 72, 79);
            margin: 0;
            padding: 0;

            /* adjust some colors in the Fortify webcomponent */
            --peculiar-color-footer-rgb: 255, 255, 255;
            --peculiar-color-footer-text-rgb: 0, 0, 0;
        }

        #signatureControlsPanel, #previewContainer, #signButtonContainer, #fortifyContainer, #downloadButtonContainer {
            height:100%;
        }

        #previewContainer, #signButtonContainer, #fortifyContainer, #downloadButtonContainer {
            border: 0;
            float: left;
            width: 50%;
        }

        #signButtonContainer, #fortifyContainer, #downloadButtonContainer {
            border-right: 1px solid rgb(234, 237, 242);
            border-top: 1px solid rgb(234, 237, 242);
            border-bottom: 1px solid rgb(234, 237, 242);
        }

        #signButtonContainer, #downloadButtonContainer {
            padding: 34px 50px 46px;
        }

        h4 {
            font-size: 17px;
            margin-top: 0;
            padding-top: 0;
        }

        button.btnContinue, button.btnCancel {
            justify-content: center;
            border-radius: 3px;
            padding: 0 26px;
            height: 40px;
            float: right;
            cursor: pointer;
            transition: color 200ms;
        }

        button.btnContinue {
            color: #ffffff;
            border: 1px solid rgb(10, 190, 101);
            background-color: rgb(10, 190, 101);
        }

        button.btnContinue:hover {
            color: #9ddd97;
        }

        button.btnCancel {
            color: rgb(109, 125, 135);
            background-color: #ffffff;
            border: 1px solid rgb(182, 195, 204);
            float: none;
        }

        button.btnCancel:hover {
            color: rgb(182, 195, 204);
        }

        button.btnContinue:focus, button.btnCancel:focus {
            box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.1);
            outline:none;
        }

        label.checkbox {
            display: block;
            margin: 5px 0;
            margin-left: 20px;
        }

        label.checkbox input[type=checkbox] {
            position: absolute;
            margin-left: -20px;
        }

        .loader-default::after {
            border-color: rgb(13, 132, 255);
            border-left-color: transparent;
        }
    </style>
</head>
<body>
<div id="loader" class="loader loader-default is-active" data-text="Loading..."></div>
<div id="outdated" style="display:none;">Your browser is outdated.</div>

<div id="signatureControlsPanel" style="display:none;">
    <div id="previewContainer"></div>
    <div id="signButtonContainer">
        <h4>Signature Settings</h4>
        <label class="checkbox" for="useAIA">
            <input type="checkbox" name="useAIA" id="useAIA" checked="checked"/>
            Embedded certificates fetched from the <a href="http://www.pkiglobe.org/auth_info_access.html" target="_blank">AIA extension</a>.
        </label>

        <label class="checkbox" for="useTimestamp">
            <input type="checkbox" name="useTimestamp" id="useTimestamp" checked="checked" />
            Embedded timestamp if adobe timestamp extension is available in certificate.
        </label>

        <button id="signBtn" class="btnContinue">Start and choose certificate</button>
    </div>
    <div id="fortifyContainer" style="display: none;height:100%;"></div>
    <div id="downloadButtonContainer" style="display: none;">
        <p>The document was successfully signed.</p>
        <p id="extraCerts"></p>
        <p id="tsUrl"></p>
        <button id="resetBtn" class="btnCancel">Restart</button>
    </div>
</div>

<script type="text/javascript">
    var controllerPath = '<?=$controllerPath?>';
    document.addEventListener("DOMContentLoaded", function () {
        function loadScript(src, module) {
            return new Promise(function (resolve, reject) {
                var script = document.createElement('script');
                script.src = src;
                script.type = 'text/javascript';
                script.onload = resolve.bind(null, true);
                script.onerror = reject;
                if (typeof module !== 'undefined') {
                    if (module) {
                        script.type = 'module';
                    } else {
                        script.noModule = true;
                    }
                }
                document.head.appendChild(script);
            });
        }

        try {
            loadScript('https://unpkg.com/@peculiar/fortify-webcomponents@^1.0.0/dist/peculiar/peculiar.esm.js', true)
                .then(function () {return loadScript('https://verify.ink/webcomponent/index.js', true)})
                .then(function () {return loadScript('js/main.js')})
                .catch(function (e) {
                    document.getElementById('loader').style.display = 'none';
                    document.getElementById('outdated').style.display = '';
                    console.error(e);
                });
        } catch (e) {
            document.getElementById('loader').style.display = 'none';
            document.getElementById('outdated').style.display = '';
            console.error(e);
        }
    });
</script>
</body>
</html>
PHP
<?php
if (!isset($_GET['action'])) {
    die();
}

// load and register the autoload function
require_once __DIR__ . '/../../../../../../bootstrap.php';

$fileToSign = $assetsDirectory . '/pdfs/tektown/Laboratory-Report.pdf';

// for demonstration purpose we use a session for state handling
// in a production environment you may use a more reasonable solution
session_start();

try {
    // a simple "controller":
    switch ($_GET['action']) {
        case 'preview':
            $doc = file_get_contents($fileToSign);

            // Note: these lines are only required for the Verify.ink pdf viewer because of CORS
            header('Access-Control-Allow-Origin: https://verify.ink');
            header('Access-Control-Allow-Credentials: true');
            header('Access-Control-Expose-Headers: Content-Disposition');

            header('Content-Type: application/pdf');
            header('Content-Disposition: inline; filename="' . basename($fileToSign, '.pdf') . '.pdf"');
            header('Expires: 0');
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header('Pragma: public');
            header('Content-Length: ' . strlen($doc));
            echo $doc;
            flush();
            break;

        // This action expects the certificate of the signer.
        // It prepares the PDF document accordingly.
        case 'start':
            if (isset($_SESSION['tmpDocument'])) {
                @unlink($_SESSION['tmpDocument']->getWriter()->getPath());
            }

            $data = json_decode(file_get_contents('php://input'));
            if (!isset($data->certificate)) {
                throw new Exception('Missing certificate!');
            }

            // load the PDF document
            $document = \SetaPDF_Core_Document::loadByFilename($fileToSign);
            // create a signer instance
            $signer = new \SetaPDF_Signer($document);
            // create a module instance
            $module = new \SetaPDF_Signer_Signature_Module_Pades();

            // create a certificate instance
            $certificate = new \SetaPDF_Signer_X509_Certificate($data->certificate);

            // pass the user certificate to the module
            $module->setCertificate($certificate);

            // setup information resolver manager
            $informationResolverManager = new \SetaPDF_Signer_InformationResolver_Manager();
            $informationResolverManager->addResolver(new \SetaPDF_Signer_InformationResolver_HttpCurlResolver([
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_MAXREDIRS => 5
            ]));

            $extraCerts = new \SetaPDF_Signer_X509_Collection();

            // get issuer certificates
            if (isset($data->useAIA) && $data->useAIA) {
                $certificates = [$certificate];
                while (count($certificates) > 0) {
                    /** @var \SetaPDF_Signer_X509_Certificate $currentCertificate */
                    $currentCertificate = array_pop($certificates);
                    /** @var \SetaPDF_Signer_X509_Extension_AuthorityInformationAccess $aia */
                    $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 = 10000;
            foreach ($extraCerts->getAll() as $extraCert) {
                $signatureContentLength += (strlen($extraCert->get(\SetaPDF_Signer_X509_Format::DER)) * 2);
            }

            $signer->setSignatureContentLength($signatureContentLength);

            unset($_SESSION['tsUrl']);
            // get timestamp information and use it
            if (isset($data->useTimestamp) && $data->useTimestamp) {
                /** @var \SetaPDF_Signer_X509_Extension_TimeStamp $ts */
                $ts = $certificate->getExtensions()->get(\SetaPDF_Signer_X509_Extension_TimeStamp::OID);
                if ($ts && $ts->getVersion() === 1 && $ts->requiresAuth() === false) {
                    $_SESSION['tsUrl'] = $ts->getLocation();
                    $signer->setSignatureContentLength($signatureContentLength + 6000);
                }
            }

            // A simple example to add a visible signature.
            //        $field = $signer->addSignatureField(
            //            'Signature', 1, \SetaPDF_Signer_SignatureField::POSITION_LEFT_TOP, ['x' => 20, 'y' => -20], 180, 60
            //        );
            //        $signer->setSignatureFieldName($field->getQualifiedName());
            //
            //        $appearance = new \SetaPDF_Signer_Signature_Appearance_Dynamic($module);
            //        $signer->setAppearance($appearance);

            // you may use an own temporary file handler
            $tempPath = \SetaPDF_Core_Writer_TempFile::createTempPath();

            // prepare the PDF
            $tmpDocument = $signer->preSign(
                new \SetaPDF_Core_Writer_File($tempPath),
                $module
            );
            
            // prepare the response
            $response = [
                'dataToSign' => \SetaPDF_Core_Type_HexString::str2hex(
                    $module->getDataToSign($tmpDocument->getHashFile())
                ),
                'extraCerts' => array_map(function (\SetaPDF_Signer_X509_Certificate $cert) {
                    return $cert->get(\SetaPDF_Signer_X509_Format::PEM);
                }, $extraCerts->getAll()),
                'tsUrl' => isset($_SESSION['tsUrl']) ? $_SESSION['tsUrl'] : false
            ];

            $_SESSION['tmpDocument'] = $tmpDocument;
            $_SESSION['module'] = $module;

            // send it
            header('Content-Type: application/json; charset=utf-8');
            echo json_encode($response);
            break;

        // This action embeddeds the signature in the CMS container
        // and optionally requests and embeds the timestamp
        case 'complete':
            $data = json_decode(file_get_contents('php://input'));
            if (!isset($data->signature)) {
                die();
            }

            $data->signature = \SetaPDF_Core_Type_HexString::hex2str($data->signature);

            // create the document instance
            $writer = new \SetaPDF_Core_Writer_String();
            $document = \SetaPDF_Core_Document::loadByFilename($fileToSign, $writer);
            $signer = new \SetaPDF_Signer($document);

            // pass the signature to the signature modul
            $_SESSION['module']->setSignatureValue($data->signature);

            // get the CMS structur from the signature module
            $cms = (string)$_SESSION['module']->getCms();

            // verify that the received signature matches to the CMS package and document.
            $signedData = new \SetaPDF_Signer_Cms_SignedData($cms);
            $signedData->setDetachedSignedData($_SESSION['tmpDocument']->getHashFile());
            if (!$signedData->verify($signedData->getSigningCertificate())) {
                throw new Exception('Signature cannot be verified!');
            }

            // add the timestamp (if available)
            if (isset($_SESSION['tsUrl'])) {
                $tsModule = new \SetaPDF_Signer_Timestamp_Module_Rfc3161_Curl($_SESSION['tsUrl']);
                $signer->setTimestampModule($tsModule);
                $cms = $signer->addTimeStamp($cms, $_SESSION['tmpDocument']);
            }

            // save the signature to the temporary document
            $signer->saveSignature($_SESSION['tmpDocument'], $cms);
            // clean up temporary file
            unlink($_SESSION['tmpDocument']->getWriter()->getPath());

            if (!isset($_SESSION['pdfs']['currentId'])) {
                $_SESSION['pdfs'] = ['currentId' => 0, 'docs' => []];
            } else {
                // reduce the session data to 5 signed files only
                while (count($_SESSION['pdfs']['docs']) > 5) {
                    array_shift($_SESSION['pdfs']['docs']);
                }
            }

            $id = $_SESSION['pdfs']['currentId']++;
            $_SESSION['pdfs']['docs']['id-' . $id] = $writer;
            // send the response
            header('Content-Type: application/json; charset=utf-8');
            echo json_encode(['id' => $id]);
            break;

        // a download action
        case 'download':
            $key = 'id-' . (isset($_GET['id']) ? $_GET['id'] : '');
            if (!isset($_SESSION['pdfs']['docs'][$key])) {
                die();
            }

            $doc = $_SESSION['pdfs']['docs'][$key];

            // Note: these lines are only required for the Verify.ink pdf viewer because of CORS
            header('Access-Control-Allow-Origin: https://verify.ink');
            header('Access-Control-Allow-Credentials: true');
            header('Access-Control-Expose-Headers: Content-Disposition');

            header('Content-Type: application/pdf');
            header('Content-Disposition: attachment; filename="' . basename($fileToSign, '.pdf') . '-signed.pdf"');
            header('Expires: 0');
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header('Pragma: public');
            header('Content-Length: ' . strlen($doc));
            echo $doc;
            flush();
            break;
    }
} catch (\Exception $e) {
    header('Content-Type: application/json; charset=utf-8', true, 500);
    echo json_encode(['error' => $e->getMessage()]);
}
(function() {
    // Some helper functions:
    function show(id) {
        document.getElementById(id).style.display = '';
    }

    function hide(id) {
        document.getElementById(id).style.display = 'none';
    }

    // some helper functions to work with typed arrays
    function toHex(buffer) {
        let buf = new Uint8Array(buffer),
            splitter = "",
            res = [],
            len = buf.length;

        for (let i = 0; i < len; i++) {
            let char = buf[i].toString(16);
            res.push(char.length === 1 ? "0" + char : char);
        }
        return res.join(splitter);
    }

    function fromHex(hexString) {
        let res = new Uint8Array(hexString.length / 2);
        for (let i = 0; i < hexString.length; i = i + 2) {
            let c = hexString.slice(i, i + 2);
            res[i / 2] = parseInt(c, 16);
        }
        return res.buffer;
    }

    // we need some ajax
    function postRequest(url, params) {
        return new Promise(function(resolve, reject) {
            let xhr = new XMLHttpRequest();
            xhr.open("POST", url, true);
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        resolve(xhr.responseText);
                    } else {
                        reject(xhr, xhr.status);
                    }
                }
            };

            xhr.onerror = (() => reject(xhr, xhr.status));
            xhr.send(params);
        });
    }

    // the main function
    async function main() {
        let lastId = null,
            fortifyComp = null;

        function initVerify(url) {
            document.getElementById('previewContainer').innerHTML = '<verify-viewer'
                + ' url="' + url + '"'
                + ' show-signature-if-present="false"'
                + ' notify-if-not-signed="false"'
                + ' sign="false"'
                + ' search="false"'
                + ' download="true"'
                + ' style="height: 100%;"'
                + ' ></verify-viewer>';
        }

        function initFortify () {
            // https://fortifyapp.com/developers/examples/certificate-management
            fortifyComp = document.createElement('peculiar-fortify-certificates');
            fortifyComp.style.height = '100%';
            fortifyComp.language = 'en';
            fortifyComp.filters = {
                //   onlySmartcards: false,
                expired: false,
                //   subjectDNMatch: 'apple',
                //   subjectDNMatch: new RegExp(/apple/),
                //   issuerDNMatch: 'demo',
                //   issuerDNMatch: new RegExp(/demo/),
                // keyUsage: ['digitalSignature'],
                onlyWithPrivateKey: true,
                ca: true
            };

            fortifyComp.addEventListener('cancel', function () {
                hide('fortifyContainer');
                show('signButtonContainer');
            });
            fortifyComp.addEventListener('continue', async function (event) {
                try {
                    show('loader');
                    document.getElementById('loader').setAttribute('data-text', 'Signing document');
                    let provider = await event.detail.server.getCrypto(event.detail.providerId);

                    let cert = await provider.certStorage.getItem(event.detail.certificateId);
                    let certPem = await provider.certStorage.exportCert('pem', cert);
                    let privateKey = await provider.keyStorage.getItem(event.detail.privateKeyId);

                    let startResponseText = await postRequest(
                        controllerPath + '?action=start',
                        JSON.stringify({
                            certificate: certPem,
                            useAIA: document.getElementById('useAIA').checked,
                            useTimestamp: document.getElementById('useTimestamp').checked
                        })
                    );
                    let startJson = JSON.parse(startResponseText);

                    if (startJson.extraCerts.length > 0) {
                        document.getElementById('extraCerts').innerHTML = startJson.extraCerts.length
                            + ' extra certificate(s) resolved and embedded through the '
                            + '<a href="http://www.pkiglobe.org/auth_info_access.html" target="_blank">AIA extension</a>.';
                    } else {
                        document.getElementById('extraCerts').innerHTML = 'No extra certificates were resolved.';
                    }

                    if (startJson.tsUrl) {
                        document.getElementById('tsUrl').innerHTML = 'Timestamp server located at <i>' + startJson.tsUrl
                            + '</i> was used.';
                    } else {
                        document.getElementById('tsUrl').innerHTML = 'No timestamp server found.';
                    }

                    const message = fromHex(startJson.dataToSign);
                    const alg = {
                        name: privateKey.algorithm.name,
                        hash: "SHA-256",
                    };

                    let signature = await provider.subtle.sign(alg, privateKey, message);
                    let completeResponseText = await postRequest(
                        controllerPath + '?action=complete',
                        JSON.stringify({signature: toHex(signature)})
                    );
                    let completeJson = JSON.parse(completeResponseText);
                    lastId = completeJson.id;

                    initVerify(controllerPath + '?action=download&id=' + lastId);
                    hide('fortifyContainer');
                    hide('loader');
                    show('downloadButtonContainer');
                } catch (error) {
                    hide('loader');
                    console.info(error);
                    alert('An error occured: ' + error.responseText);
                }
            });

            document.getElementById('fortifyContainer').appendChild(fortifyComp);
        }

        document.getElementById('signBtn').addEventListener('click', () => {
            if (!fortifyComp) {
                initFortify();
            }

            hide('signButtonContainer');
            show('fortifyContainer');
        });

        document.getElementById('resetBtn').addEventListener('click', () => {
            hide('downloadButtonContainer');
            initVerify(controllerPath + '?action=preview');
            show('signButtonContainer');
        });

        initVerify(controllerPath + '?action=preview');
        show('signatureControlsPanel');
        hide('loader');
    }

    //noinspection JSIgnoredPromiseFromCall
    main();

})();