Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cs.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
'array_syntax' => ['syntax' => 'short'],
'cast_spaces' => ['space' => 'none'],
'concat_space' => ['spacing' => 'one'],
'compact_nullable_typehint' => true,
'compact_nullable_type_declaration' => true,
'declare_equal_normalize' => ['space' => 'single'],
'general_phpdoc_annotation_remove' => [
'annotations' => [
Expand Down
2 changes: 1 addition & 1 deletion src/Algorithm.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ final class Algorithm
* @param string $signatureMethodAlgorithm
* @param string|null $digestMethodAlgorithm
*/
public function __construct(string $signatureMethodAlgorithm, string $digestMethodAlgorithm = null)
public function __construct(string $signatureMethodAlgorithm, ?string $digestMethodAlgorithm = null)
{
$this->setSignatureMethodAlgorithm($signatureMethodAlgorithm);
$this->setDigestMethodAlgorithm($digestMethodAlgorithm ?? $signatureMethodAlgorithm);
Expand Down
2 changes: 1 addition & 1 deletion src/X509Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,6 @@ public function toRawBase64(OpenSSLCertificate $certificate): string

preg_match(self::PEM_REGEX_PATTERN, $exportedCertificate, $matches);

return str_replace(["\r\n", "\n"], '', trim($matches[1]));
return str_replace(["\r\n", "\n"], '', trim($matches[1] ?? ''));
}
}
19 changes: 13 additions & 6 deletions src/XmlSignatureVerifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,23 @@ final class XmlSignatureVerifier

private bool $preserveWhiteSpace;

private bool $exclusive;

/**
* The constructor.
*
* @param CryptoVerifierInterface $cryptoVerifier
* @param bool $preserveWhiteSpace To remove redundant white spaces
* @param bool $exclusive Exclusive canonicalization. @see https://www.php.net/manual/en/domnode.c14n.php
*/
public function __construct(CryptoVerifierInterface $cryptoVerifier, bool $preserveWhiteSpace = true)
{
public function __construct(
CryptoVerifierInterface $cryptoVerifier,
bool $preserveWhiteSpace = true,
bool $exclusive = true,
) {
$this->cryptoVerifier = $cryptoVerifier;
$this->preserveWhiteSpace = $preserveWhiteSpace;
$this->exclusive = $exclusive;
$this->xmlReader = new XmlReader();
}

Expand Down Expand Up @@ -82,13 +89,13 @@ public function verifyDocument(DOMDocument $xml): bool
$signatureValueElement = $this->xmlReader->queryDomNode($xpath, '//xmlns:SignatureValue', $signedInfoNode);
$signatureValueElement->nodeValue = '';

$canonicalData = $signedInfoNode->C14N(true, false);
$canonicalData = $signedInfoNode->C14N($this->exclusive, false);

$xml2 = new DOMDocument();
$xml2->preserveWhiteSpace = true;
$xml2->formatOutput = true;
$xml2->loadXML($canonicalData);
$canonicalData = $xml2->C14N(true, false);
$canonicalData = $xml2->C14N($this->exclusive, false);

$isValidSignature = $this->cryptoVerifier->verify($canonicalData, $signatureValue, $signatureAlgorithm);

Expand Down Expand Up @@ -124,8 +131,8 @@ private function checkDigest(DOMDocument $xml, DOMXPath $xpath, string $algorith
$signatureNode->parentNode->removeChild($signatureNode);
}

// Canonicalize the content, exclusive and without comments
$canonicalData = $xml->C14N(true, false);
// Canonicalize the content without comments
$canonicalData = $xml->C14N($this->exclusive, false);

$digestValue2 = $this->cryptoVerifier->computeDigest($canonicalData, $algorithm);

Expand Down
23 changes: 16 additions & 7 deletions src/XmlSigner.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,18 @@ final class XmlSigner

private CryptoSignerInterface $cryptoSigner;

public function __construct(CryptoSignerInterface $cryptoSigner)
{
private bool $preserveWhiteSpace;

private bool $exclusive;

public function __construct(
CryptoSignerInterface $cryptoSigner,
bool $preserveWhiteSpace = true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer having immutable with* Methods for these new parameters.
e.g. withPreserveWhiteSpace() and withExclusive()

bool $exclusive = true,
) {
$this->xmlReader = new XmlReader();
$this->preserveWhiteSpace = $preserveWhiteSpace;
$this->exclusive = $exclusive;
$this->cryptoSigner = $cryptoSigner;
}

Expand All @@ -42,7 +51,7 @@ public function signXml(string $data): string
$xml = new DOMDocument();

// Whitespaces must be preserved
$xml->preserveWhiteSpace = true;
$xml->preserveWhiteSpace = $this->preserveWhiteSpace;
$xml->formatOutput = false;

$xml->loadXML($data);
Expand All @@ -63,15 +72,15 @@ public function signXml(string $data): string
*
* @return string The signed XML as string
*/
public function signDocument(DOMDocument $document, DOMElement $element = null): string
public function signDocument(DOMDocument $document, ?DOMElement $element = null): string
{
$element = $element ?? $document->documentElement;

if ($element === null) {
throw new XmlSignerException('Invalid XML document element');
}

$canonicalData = $element->C14N(true, false);
$canonicalData = $element->C14N($this->exclusive, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the use case for this?


// Calculate and encode digest value
$digestValue = $this->cryptoSigner->computeDigest($canonicalData);
Expand Down Expand Up @@ -176,11 +185,11 @@ private function appendSignature(DOMDocument $xml, string $digestValue): void
}

// http://www.soapclient.com/XMLCanon.html
$c14nSignedInfo = $signedInfoElement->C14N(true, false);
$c14nSignedInfo = $signedInfoElement->C14N($this->exclusive, false);

$signatureValue = $this->cryptoSigner->computeSignature($c14nSignedInfo);

$xpath = new DOMXpath($xml);
$xpath = new DOMXPath($xml);
$signatureValueElement = $this->xmlReader->queryDomNode($xpath, '//SignatureValue', $signatureElement);
$signatureValueElement->nodeValue = base64_encode($signatureValue);
}
Expand Down
53 changes: 29 additions & 24 deletions tests/XmlSignatureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,38 +48,43 @@ public function testSignAndVerify(string $privateKeyFile, string $publicKeyFile,

foreach ($files as $filename) {
foreach ($algos as $algo) {
$privateKeyStore = new PrivateKeyStore();
$this->testFileAndAlgo($filename, $algo, $privateKeyFile, $publicKeyFile, $password);
}
}
}

if (pathinfo($privateKeyFile, PATHINFO_EXTENSION) === 'p12') {
$privateKeyStore->loadFromPkcs12(file_get_contents($privateKeyFile), $password);
} else {
$privateKeyStore->loadFromPem(file_get_contents($privateKeyFile), $password);
}
private function testFileAndAlgo(string $filename, string $algo, string $privateKeyFile, string $publicKeyFile, string $password)
{
$privateKeyStore = new PrivateKeyStore();

$algorithm = new Algorithm($algo, $algo);
$cryptoSigner = new CryptoSigner($privateKeyStore, $algorithm);
if (pathinfo($privateKeyFile, PATHINFO_EXTENSION) === 'p12') {
$privateKeyStore->loadFromPkcs12(file_get_contents($privateKeyFile), $password);
} else {
$privateKeyStore->loadFromPem(file_get_contents($privateKeyFile), $password);
}

$xmlSigner = new XmlSigner($cryptoSigner);
$xmlSigner->setReferenceUri('');
$algorithm = new Algorithm($algo, $algo);
$cryptoSigner = new CryptoSigner($privateKeyStore, $algorithm);

$signedXml = $xmlSigner->signXml(file_get_contents($filename));
$xmlSigner = new XmlSigner($cryptoSigner);
$xmlSigner->setReferenceUri('');

// verify
$publicKeyStore = new PublicKeyStore();
if (pathinfo($publicKeyFile, PATHINFO_EXTENSION) === 'p12') {
$publicKeyStore->loadFromPkcs12(file_get_contents($publicKeyFile), $password);
} else {
$publicKeyStore->loadFromPem(file_get_contents($publicKeyFile));
}
$signedXml = $xmlSigner->signXml(file_get_contents($filename));

$cryptoVerifier = new CryptoVerifier($publicKeyStore);
$xmlSignatureVerifier = new XmlSignatureVerifier($cryptoVerifier);
// verify
$publicKeyStore = new PublicKeyStore();
if (pathinfo($publicKeyFile, PATHINFO_EXTENSION) === 'p12') {
$publicKeyStore->loadFromPkcs12(file_get_contents($publicKeyFile), $password);
} else {
$publicKeyStore->loadFromPem(file_get_contents($publicKeyFile));
}

$isValid = $xmlSignatureVerifier->verifyXml($signedXml);
$cryptoVerifier = new CryptoVerifier($publicKeyStore);
$xmlSignatureVerifier = new XmlSignatureVerifier($cryptoVerifier);

$this->assertTrue($isValid);
}
}
$isValid = $xmlSignatureVerifier->verifyXml($signedXml);

$this->assertTrue($isValid);
}

/**
Expand Down
Loading