Skip to content

Commit

Permalink
[#2] Move XML to views; simplify envelope
Browse files Browse the repository at this point in the history
Rather than the envelope trying to do anything, it will just maintain a
list of files and signatures. Also, stamps don't contain XML directly -
instead they use differet views based on the required ddoc format.
  • Loading branch information
kgilden committed Oct 25, 2014
1 parent f7fefae commit 69fafb0
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 190 deletions.
120 changes: 120 additions & 0 deletions src/Native/BDocView.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

/*
* This file is part of the DigiDoc package.
*
* (c) Kristen Gilden <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace KG\DigiDoc\Native;

use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;

class BDocView
{
const XPATH_FILE_REFS = '/asic:XAdESSignatures/ds:Signature/ds:SignedInfo';
const XPATH_FILE_FORMATS = '/asic:XAdESSignatures/ds:Signature/xades:SignedDataObjectProperties';
const XPATH_SIGNATURE = '/asic:XAdESSignatures/ds:Signature/ds:SignatureValue';
const XPATH_DATA_TO_SIGN = '/asic:XAdESSignatures/ds:Signature/ds:SignedProperties';

private $dom;

private $xpath;

public function __construct()
{
$this->dom = new \DomDocument();
$this->dom->load(__DIR__ . '/stamp.xml');

$this->xpath = new \DOMXPath($this->dom);
}

public function getDataToSign()
{
// @todo what if none or too many nodes found?
return $this->xpath->query(self::XPATH_DATA_TO_SIGN)->item(0)->c14n();
}

public function addSignature($signature)
{
// @todo what if none or too many nodes found?
// @todo algorithm is hard-coded
$element = $this->xpath->query(self::XPATH_SIGNATURE)->item(0);
$element->nodeValue = chunk_split(base64_encode(hash('sha256', $signature, true)), 64, "\n");
$element->setAttribute('Id', $signatureId = uniqid());

return $signatureId;
}

public function addFileDigests($files)
{
foreach ($files as $pathInEnvelope => $pathToFile) {
$this->addFileDigest($pathToFile, $pathInEnvelope);
}
}

public function addFileDigest($pathToFile, $pathInEnvelope)
{
$refId = $this->addFileReference($pathToFile, $pathInEnvelope);

$this->addMimeType($pathToFile, $refId);
}

public function __toString()
{
throw new \Exception('Implement me!');
}

private function addFileReference($pathToFile, $pathInEnvelope)
{
// @todo what if none or too many nodes found?
$refParent = $this->xpath->query(self::XPATH_FILE_REFS)->item(0);

// @todo algorithm is hard-coded
$digestMethod = $this->dom
->createElementNS($refParent->namespaceURI, $refParent->prefix . ':DigestMethod')
->setAttribute('Algorithm', 'http://www.w3.org/2001/04/xmlenc#sha256')
;

$digestValue = $this->dom->createElementNS(
$refParent->namespaceURI,
$refParent->prefix . ':DigestValue',
chunk_split(base64_encode(hash_file('sha256', $pathToFile, true)), 64, "\n")
);

$ref = $this->dom
->createElementNS($refParent->namespaceURI, $refParent->prefix . ':Reference')
->setAttribute('Id', $refId = uniqid())
->setAttribute('URI', $pathInEnvelope)
->appendChild($digestMethod)
->appendChild($digestValue)
;

$refParent->appendChild($ref);

return $refId;
}

private function addMimeType($pathToFile, $refId)
{
// @todo what if none or too many nodes found?
$parent = $this->xpath->query(self::XPATH_FILE_FORMATS)->item(0);

$mimeType = $this->dom->createElementNS(
$parent->namespaceURI,
$parent->prefix . ':MimeType',
MimeTypeGuesser::getInstance()->guess($pathToFile) ?: 'application/octet-stream'
);

$format = $this->dom
->createElementNS($parent->namespaceURI, $parent->prefix . ':DataObjectFormat')
->setAttribute('ObjectReference', '#' . $refId)
->appendChild($mimeType)
;

$parent->appendChild($format);
}
}
127 changes: 25 additions & 102 deletions src/Native/Envelope.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,125 +15,48 @@

class Envelope
{
/**
* @var \ZipArchive
*/
private $archive;
const BDOC21 = 0;
const SHA256 = 'sha256';

/**
* @var string
*/
private $path;
private $files;

/**
* @param string $path
*/
public function __construct($path)
{
$this->path = $path;
private $stamps;

$this->archive = new \ZipArchive();
if (true !== ($error = $this->archive->open($this->path))) {
// @todo better exception?
throw new \RuntimeException(sprintf('Failed to open archive "%s", ZipArchive code %d', $this->path, $error));
}
}
private $options;

/**
* {@inheritDoc}
*/
public function getFiles()
public function __construct(array $files, array $options)
{
$nonMetaNames = array();

foreach ($this->getAllFileNames() as $fileName) {
// Skip metadata files.
if (0 === strpos($fileName, 'META-INF/') || 'mimetype' === $fileName) {
continue;
}

$nonMetaNames[] = $fileName;
}

return new \ArrayIterator($this->convertNamesToFullPaths($nonMetaNames));
$this->files = $files;
$this->stamps = array();
$this->options = array_merge(array(
'format' => self::BDOC21,
'algo' => self::SHA256,
), $options);
}

/**
* {@inheritDoc}
*/
public function getStamps()
public function signBy(Signer $signer)
{
$stamps = array();

foreach ($this->getAllFileNames() as $fileName) {
if (preg_match('/^META-INF\/signatures\d+\.xml$/', $fileName)) {
$stamps[] = $this->createStamp($this->convertNameToFullPath($fileName));
}
}

return new \ArrayIterator($stamps);
// @todo what other stuff should be added to the view and where
// should they be added?
return $this->stamps[] = new Stamp($this->createView(), $signer);
}

public function __destruct()
public function write($path)
{
$this->archive->close();
throw new \Exception('Implement me!');
}

/**
* @param array $names
*
* @return array
*/
private function convertNamesToFullPaths($names)
private function createView()
{
$paths = array();
$version = $this->options['format'];

foreach ($names as $name) {
$paths[] = $this->convertNameToFullPath($name);
if ($version !== self::BDOC21) {
throw new \Exception('Unsupported format');
}

return $paths;
}

/**
* @param string $name
*
* @return string
*/
private function convertNameToFullPath($name)
{
return sprintf('zip://%s#%s', $this->path, $name);
}

/**
* @return array
*/
private function getAllFileNames()
{
$fileNames = array();

for ($i = 0; $i < $this->archive->numFiles; $i++) {
$fileNames[] = $this->archive->getNameIndex($i);
}

return $fileNames;
}

/**
* Creates a new Stamp from the given file.
*
* @param string $path
*
* @return Stamp A new Stamp object
*
* @throws \RuntimeException If the path is not readable
*/
private function createStamp($path)
{
if (!$signatureContents = @file_get_contents($path)) {
throw new \RuntimeException(sprintf('Failed to open signature "%s" for reading.', $path));
}
$view = new BDocView();
$view->addFileDigests($this->files);

return new Stamp(new DOMDocument($signatureContents));
return $view;
}
}
105 changes: 17 additions & 88 deletions src/Native/Stamp.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,106 +13,35 @@

class Stamp
{
/**
* @var DOMDocument
*/
private $dom;

/**
* @param DomDocument $dom DOM of the stamp's XML representation
*/
public function __construct(\DOMDocument $dom = null)
{
$this->dom = $dom ?: $this->createDom();
}
private $view;

/**
* Adds a file reference to the stamp.
*
* @todo Don't allow adding files, if the stamp is signed
*
* @param string $pathInEnvelope File's relative path in the envelope
* @param string $pathToFile File's current path
*
* @return Stamp
*/
public function addFile($pathInEnvelope, $pathToFile)
{
$xpath = new \DOMXpath($this->dom);
$nodes = $xpath->query('/asic:XAdESSignatures/ds:Signature/ds:SignedInfo');

foreach ($nodes as $node) {
$this->appendRef($node, $pathInEnvelope, $pathToFile);
}

return $this;
}
private $signer;

public function getFileDigest($pathInEnvelope)
public function __construct(BDocView $view, Signer $signer)
{
$xpath = new \DOMXpath($this->dom);
$nodes = $xpath->query(sprintf('.//ds:Reference[@URI="%s"]', $pathInEnvelope));

if ($nodes->length !== 1) {
throw new \RuntimeException(sprintf('Expected to find 1 reference node with uri "%s", found %d.', $pathInEnvelope, $nodes->length));
}

return base64_decode(str_replace("\n", '', $nodes->item(0)->textContent));
$this->view = $view;
$this->signer = $signer;
}

/**
* Appends a file reference to the stamp. Its structure is the following
*
* <ds:Reference Id="__id__" URI="__pathInEnvelope__">
* <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
* <ds:DigestValue>__b64enc_digest__</ds:DigestValue>
* </ds:Reference>
*
* @param \DOMNode $parent
* @param string $pathInEnvelope
* @param string $pathToFile
*/
private function appendRef(\DOMNode $parent, $pathInEnvelope, $pathToFile)
public function getChallenge()
{
$prefix = $parent->prefix;
$nsUri = $parent->namespaceURI;

$ref = $this->dom->createElementNS($nsUri, $prefix . ':Reference');
$ref->setAttribute('Id', uniqid());
$ref->setAttribute('URI', $pathInEnvelope);

$digestMethod = $this->dom->createElementNS($nsUri, $prefix . ':DigestMethod');
$digestMethod->setAttribute('Algorithm', 'http://www.w3.org/2001/04/xmlenc#sha256');

$digestValue = $this->dom->createElementNS($nsUri, $prefix . ':DigestValue', $this->digestFile('sha256', $pathToFile));
$dataToSign = $this->view->getDataToSign();

$ref
->appendChild($digestMethod)
->appendChild($digestValue)
;

$parent->appendChild($ref);
// @todo get rid of hardcoded hash - grab from SignatureMethod?
// @todo document that if the challenge will be signed via an in-browser
// plugin, it must first be hex-encoded and uppercased.
return hash('sha256', $dataToSign, true);
}

/**
* @return \DOMDocument
* @param string $signature raw binary signature
*/
private function createDom()
public function sign($signature)
{
$dom = new \DOMDocument();
$dom->load(__DIR__ . '/stamp.xml');

return $dom;
}
// @todo verify before happily adding it?
$this->view->addSignature(base64_encode($signature));

/**
* @param string $algo
* @param string $path
*
* @return string
*/
private function digestFile($algo, $path)
{
return chunk_split(base64_encode(hash_file($algo, $path, true)), 64, "\n");
// @todo make OCSP request
$bytesToSend;
}
}

0 comments on commit 69fafb0

Please sign in to comment.