53 const XMLDSIGNS =
'http://www.w3.org/2000/09/xmldsig#';
54 const SHA1 =
'http://www.w3.org/2000/09/xmldsig#sha1';
55 const SHA256 =
'http://www.w3.org/2001/04/xmlenc#sha256';
56 const SHA384 =
'http://www.w3.org/2001/04/xmldsig-more#sha384';
57 const SHA512 =
'http://www.w3.org/2001/04/xmlenc#sha512';
58 const RIPEMD160 =
'http://www.w3.org/2001/04/xmlenc#ripemd160';
60 const C14N =
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
61 const C14N_COMMENTS =
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments';
62 const EXC_C14N =
'http://www.w3.org/2001/10/xml-exc-c14n#';
65 const template =
'<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> 67 <ds:SignatureMethod /> 71 const BASE_TEMPLATE =
'<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> 115 $search = array(
"<S",
"</S",
"xmlns=");
116 $replace = array(
"<$prefix:S",
"</$prefix:S",
"xmlns:$prefix=");
121 $this->sigNode = $sigdoc->documentElement;
129 $this->xPathCtx = null;
139 if (empty($this->xPathCtx) && ! empty($this->sigNode)) {
140 $xpath =
new DOMXPath($this->sigNode->ownerDocument);
141 $xpath->registerNamespace(
'secdsig', self::XMLDSIGNS);
142 $this->xPathCtx = $xpath;
156 $uuid = md5(uniqid(mt_rand(),
true));
157 $guid =
$prefix.substr($uuid, 0, 8).
"-".
158 substr($uuid, 8, 4).
"-".
159 substr($uuid, 12, 4).
"-".
160 substr($uuid, 16, 4).
"-".
161 substr($uuid, 20, 12);
176 return self::generateGUID(
$prefix);
189 $doc = $objDoc->ownerDocument;
193 $xpath->registerNamespace(
'secdsig', self::XMLDSIGNS);
194 $query =
".//secdsig:Signature";
195 $nodeset = $xpath->query(
$query, $objDoc);
196 $this->sigNode = $nodeset->item($pos);
197 $query =
"./secdsig:SignedInfo";
198 $nodeset = $xpath->query(
$query, $this->sigNode);
199 if ($nodeset->length > 1) {
200 throw new Exception(
"Invalid structure - Too many SignedInfo elements found");
214 $doc = $this->sigNode->ownerDocument;
215 if (! is_null($value)) {
216 $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name, $value);
218 $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name);
230 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
231 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
232 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
233 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
234 $this->canonicalMethod = $method;
237 throw new Exception(
'Invalid Canonical Method');
240 $query =
'./'.$this->searchpfx.
':SignedInfo';
241 $nodeset = $xpath->query(
$query, $this->sigNode);
242 if ($sinfo = $nodeset->item(0)) {
243 $query =
'./'.$this->searchpfx.
'CanonicalizationMethod';
244 $nodeset = $xpath->query(
$query, $sinfo);
245 if (! ($canonNode = $nodeset->item(0))) {
247 $sinfo->insertBefore($canonNode, $sinfo->firstChild);
249 $canonNode->setAttribute(
'Algorithm', $this->canonicalMethod);
261 private function canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefixList=null)
264 $withComments =
false;
265 switch ($canonicalmethod) {
266 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
268 $withComments =
false;
270 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
271 $withComments =
true;
273 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
276 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
278 $withComments =
true;
282 if (is_null($arXPath) && ($node instanceof
DOMNode) && ($node->ownerDocument !== null) && $node->isSameNode($node->ownerDocument->documentElement)) {
285 while ($refnode = $element->previousSibling) {
286 if ($refnode->nodeType == XML_PI_NODE || (($refnode->nodeType == XML_COMMENT_NODE) && $withComments)) {
291 if ($refnode == null) {
292 $node = $node->ownerDocument;
296 return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
305 $doc = $this->sigNode->ownerDocument;
306 $canonicalmethod = null;
309 $query =
"./secdsig:SignedInfo";
310 $nodeset = $xpath->query(
$query, $this->sigNode);
311 if ($nodeset->length > 1) {
312 throw new Exception(
"Invalid structure - Too many SignedInfo elements found");
314 if ($signInfoNode = $nodeset->item(0)) {
315 $query =
"./secdsig:CanonicalizationMethod";
316 $nodeset = $xpath->query(
$query, $signInfoNode);
317 if ($canonNode = $nodeset->item(0)) {
318 $canonicalmethod = $canonNode->getAttribute(
'Algorithm');
320 $this->signedInfo = $this->
canonicalizeData($signInfoNode, $canonicalmethod);
336 switch ($digestAlgorithm) {
349 case self::RIPEMD160:
353 throw new Exception(
"Cannot validate digest: Unsupported Algorithm <$digestAlgorithm>");
358 $digest = base64_encode($digest);
371 $xpath =
new DOMXPath($refNode->ownerDocument);
372 $xpath->registerNamespace(
'secdsig', self::XMLDSIGNS);
373 $query =
'string(./secdsig:DigestMethod/@Algorithm)';
374 $digestAlgorithm = $xpath->evaluate(
$query, $refNode);
376 $query =
'string(./secdsig:DigestValue)';
377 $digestValue = $xpath->evaluate(
$query, $refNode);
378 return ($digValue === base64_decode($digestValue));
390 $xpath =
new DOMXPath($refNode->ownerDocument);
391 $xpath->registerNamespace(
'secdsig', self::XMLDSIGNS);
392 $query =
'./secdsig:Transforms/secdsig:Transform';
393 $nodelist = $xpath->query(
$query, $refNode);
397 foreach ($nodelist AS $transform) {
398 $algorithm = $transform->getAttribute(
"Algorithm");
399 switch ($algorithm) {
400 case 'http://www.w3.org/2001/10/xml-exc-c14n#':
401 case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments':
403 if (!$includeCommentNodes) {
412 $node = $transform->firstChild;
414 if ($node->localName ==
'InclusiveNamespaces') {
415 if ($pfx = $node->getAttribute(
'PrefixList')) {
417 $pfxlist = explode(
" ", $pfx);
418 foreach ($pfxlist AS $pfx) {
424 if (count($arpfx) > 0) {
425 $prefixList = $arpfx;
430 $node = $node->nextSibling;
433 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315':
434 case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments':
435 if (!$includeCommentNodes) {
445 case 'http://www.w3.org/TR/1999/REC-xpath-19991116':
446 $node = $transform->firstChild;
448 if ($node->localName ==
'XPath') {
450 $arXPath[
'query'] =
'(.//. | .//@* | .//namespace::*)['.$node->nodeValue.
']';
451 $arXPath[
'namespaces'] = array();
452 $nslist = $xpath->query(
'./namespace::*', $node);
453 foreach ($nslist AS $nsnode) {
454 if ($nsnode->localName !=
"xml") {
455 $arXPath[
'namespaces'][$nsnode->localName] = $nsnode->nodeValue;
460 $node = $node->nextSibling;
483 $includeCommentNodes =
true;
485 if ($uri = $refNode->getAttribute(
"URI")) {
486 $arUrl = parse_url($uri);
487 if (empty($arUrl[
'path'])) {
488 if ($identifier = $arUrl[
'fragment']) {
493 $includeCommentNodes =
false;
495 $xPath =
new DOMXPath($refNode->ownerDocument);
496 if ($this->idNS && is_array($this->idNS)) {
497 foreach ($this->idNS as $nspf => $ns) {
498 $xPath->registerNamespace($nspf, $ns);
501 $iDlist =
'@Id="'.XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).
'"';
502 if (is_array($this->idKeys)) {
503 foreach ($this->idKeys as $idKey) {
504 $iDlist .=
" or @".XPath::filterAttrName($idKey).
'="'.
505 XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).
'"';
508 $query =
'//*['.$iDlist.
']';
509 $dataObject = $xPath->query(
$query)->item(0);
511 $dataObject = $refNode->ownerDocument;
518 $includeCommentNodes =
false;
520 $dataObject = $refNode->ownerDocument;
527 if ($dataObject instanceof
DOMNode) {
529 if (! empty($identifier)) {
530 $this->validatedNodes[$identifier] = $dataObject;
532 $this->validatedNodes[] = $dataObject;
545 if ($uri = $refNode->getAttribute(
"URI")) {
546 $arUrl = parse_url($uri);
547 if (empty($arUrl[
'path'])) {
548 if ($identifier = $arUrl[
'fragment']) {
565 $query =
"./secdsig:SignedInfo[1]/secdsig:Reference";
566 $nodeset = $xpath->query(
$query, $this->sigNode);
567 if ($nodeset->length == 0) {
568 throw new Exception(
"Reference nodes not found");
570 foreach ($nodeset AS $refNode) {
582 $docElem = $this->sigNode->ownerDocument->documentElement;
583 if (! $docElem->isSameNode($this->sigNode)) {
584 if ($this->sigNode->parentNode != null) {
585 $this->sigNode->parentNode->removeChild($this->sigNode);
589 $query =
"./secdsig:SignedInfo[1]/secdsig:Reference";
590 $nodeset = $xpath->query(
$query, $this->sigNode);
591 if ($nodeset->length == 0) {
592 throw new Exception(
"Reference nodes not found");
596 $this->validatedNodes = array();
598 foreach ($nodeset AS $refNode) {
601 $this->validatedNodes = null;
602 throw new Exception(
"Reference validation failed");
620 $overwrite_id =
true;
627 $overwrite_id = !isset(
$options[
'overwrite']) ? true : (bool)
$options[
'overwrite'];
628 $force_uri = !isset(
$options[
'force_uri']) ? false : (bool)
$options[
'force_uri'];
633 $attname =
$prefix.
':'.$attname;
637 $sinfoNode->appendChild($refNode);
641 if (! $overwrite_id) {
642 $uri = $prefix_ns ? $node->getAttributeNS($prefix_ns, $id_name) : $node->getAttribute($id_name);
645 $uri = self::generateGUID();
646 $node->setAttributeNS($prefix_ns, $attname, $uri);
648 $refNode->setAttribute(
"URI",
'#'.$uri);
649 } elseif ($force_uri) {
650 $refNode->setAttribute(
"URI",
'');
654 $refNode->appendChild($transNodes);
656 if (is_array($arTransforms)) {
657 foreach ($arTransforms AS $transform) {
659 $transNodes->appendChild($transNode);
660 if (is_array($transform) &&
661 (! empty($transform[
'http://www.w3.org/TR/1999/REC-xpath-19991116'])) &&
662 (! empty($transform[
'http://www.w3.org/TR/1999/REC-xpath-19991116'][
'query']))) {
663 $transNode->setAttribute(
'Algorithm',
'http://www.w3.org/TR/1999/REC-xpath-19991116');
664 $XPathNode = $this->
createNewSignNode(
'XPath', $transform[
'http://www.w3.org/TR/1999/REC-xpath-19991116'][
'query']);
665 $transNode->appendChild($XPathNode);
666 if (! empty($transform[
'http://www.w3.org/TR/1999/REC-xpath-19991116'][
'namespaces'])) {
667 foreach ($transform[
'http://www.w3.org/TR/1999/REC-xpath-19991116'][
'namespaces'] AS
$prefix =>
$namespace) {
668 $XPathNode->setAttributeNS(
"http://www.w3.org/2000/xmlns/",
"xmlns:$prefix",
$namespace);
672 $transNode->setAttribute(
'Algorithm', $transform);
675 } elseif (! empty($this->canonicalMethod)) {
677 $transNodes->appendChild($transNode);
678 $transNode->setAttribute(
'Algorithm', $this->canonicalMethod);
685 $refNode->appendChild($digestMethod);
686 $digestMethod->setAttribute(
'Algorithm', $algorithm);
689 $refNode->appendChild($digestValue);
701 $query =
"./secdsig:SignedInfo";
702 $nodeset = $xpath->query(
$query, $this->sigNode);
703 if ($sInfo = $nodeset->item(0)) {
718 $query =
"./secdsig:SignedInfo";
719 $nodeset = $xpath->query(
$query, $this->sigNode);
720 if ($sInfo = $nodeset->item(0)) {
721 foreach ($arNodes AS $node) {
737 $this->sigNode->appendChild($objNode);
738 if (! empty($mimetype)) {
739 $objNode->setAttribute(
'MimeType', $mimetype);
741 if (! empty($encoding)) {
742 $objNode->setAttribute(
'Encoding', $encoding);
746 $newData = $this->sigNode->ownerDocument->importNode(
$data,
true);
748 $newData = $this->sigNode->ownerDocument->createTextNode(
$data);
750 $objNode->appendChild($newData);
764 if (! $node instanceof
DOMNode) {
767 if ($doc = $node->ownerDocument) {
769 $xpath->registerNamespace(
'secdsig', self::XMLDSIGNS);
770 $query =
"string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)";
771 $algorithm = $xpath->evaluate(
$query, $node);
774 $objKey =
new XMLSecurityKey($algorithm, array(
'type' =>
'public'));
802 $doc = $this->sigNode->ownerDocument;
804 $xpath->registerNamespace(
'secdsig', self::XMLDSIGNS);
805 $query =
"string(./secdsig:SignatureValue)";
806 $sigValue = $xpath->evaluate(
$query, $this->sigNode);
807 if (empty($sigValue)) {
808 throw new Exception(
"Unable to locate SignatureValue");
810 return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue));
820 return $objKey->signData(
$data);
827 public function sign($objKey, $appendToNode = null)
830 if ($appendToNode != null) {
833 $this->sigNode = $appendToNode->lastChild;
836 $query =
"./secdsig:SignedInfo";
837 $nodeset = $xpath->query(
$query, $this->sigNode);
838 if ($sInfo = $nodeset->item(0)) {
839 $query =
"./secdsig:SignatureMethod";
840 $nodeset = $xpath->query(
$query, $sInfo);
841 $sMethod = $nodeset->item(0);
842 $sMethod->setAttribute(
'Algorithm', $objKey->type);
846 if ($infoSibling = $sInfo->nextSibling) {
847 $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling);
849 $this->sigNode->appendChild($sigValueNode);
866 $objKey->serializeKey($parent);
884 $document = $node->ownerDocument;
885 $signatureElement = $document->importNode($this->sigNode,
true);
887 if ($beforeNode == null) {
888 return $node->insertBefore($signatureElement);
890 return $node->insertBefore($signatureElement, $beforeNode);
901 $beforeNode = $insertBefore ? $parentNode->firstChild : null;
912 $certs = self::staticGet509XCerts($cert, $isPEMFormat);
913 if (! empty($certs)) {
929 $arCert = explode(
"\n", $certs);
931 foreach ($arCert AS $curData) {
933 if (strncmp($curData,
'-----BEGIN CERTIFICATE', 22) == 0) {
937 if (strncmp($curData,
'-----END CERTIFICATE', 20) == 0) {
943 $data .= trim($curData);
948 return array($certs);
964 $cert = file_get_contents($cert);
967 throw new Exception(
'Invalid parent Node parameter');
969 $baseDoc = $parentRef->ownerDocument;
972 $xpath =
new DOMXPath($parentRef->ownerDocument);
973 $xpath->registerNamespace(
'secdsig', self::XMLDSIGNS);
976 $query =
"./secdsig:KeyInfo";
977 $nodeset = $xpath->query(
$query, $parentRef);
978 $keyInfo = $nodeset->item(0);
981 $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS);
983 $dsig_pfx = $pfx.
":";
986 $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.
'KeyInfo');
988 $query =
"./secdsig:Object";
989 $nodeset = $xpath->query(
$query, $parentRef);
990 if ($sObject = $nodeset->item(0)) {
991 $sObject->parentNode->insertBefore($keyInfo, $sObject);
996 $parentRef->appendChild($keyInfo);
999 $pfx = $keyInfo->lookupPrefix(self::XMLDSIGNS);
1000 if (! empty($pfx)) {
1001 $dsig_pfx = $pfx.
":";
1006 $certs = self::staticGet509XCerts($cert, $isPEMFormat);
1009 $x509DataNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.
'X509Data');
1010 $keyInfo->appendChild($x509DataNode);
1012 $issuerSerial =
false;
1013 $subjectName =
false;
1015 if (! empty(
$options[
'issuerSerial'])) {
1016 $issuerSerial =
true;
1018 if (! empty(
$options[
'subjectName'])) {
1019 $subjectName =
true;
1024 foreach ($certs as $X509Cert) {
1025 if ($issuerSerial || $subjectName) {
1026 if ($certData = openssl_x509_parse(
"-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64,
"\n").
"-----END CERTIFICATE-----\n")) {
1027 if ($subjectName && ! empty($certData[
'subject'])) {
1028 if (is_array($certData[
'subject'])) {
1030 foreach ($certData[
'subject'] AS
$key => $value) {
1031 if (is_array($value)) {
1032 foreach ($value as $valueElement) {
1033 array_unshift($parts,
"$key=$valueElement");
1036 array_unshift($parts,
"$key=$value");
1039 $subjectNameValue = implode(
',', $parts);
1041 $subjectNameValue = $certData[
'issuer'];
1043 $x509SubjectNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.
'X509SubjectName', $subjectNameValue);
1044 $x509DataNode->appendChild($x509SubjectNode);
1046 if ($issuerSerial && ! empty($certData[
'issuer']) && ! empty($certData[
'serialNumber'])) {
1047 if (is_array($certData[
'issuer'])) {
1049 foreach ($certData[
'issuer'] AS
$key => $value) {
1050 array_unshift($parts,
"$key=$value");
1052 $issuerName = implode(
',', $parts);
1054 $issuerName = $certData[
'issuer'];
1057 $x509IssuerNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.
'X509IssuerSerial');
1058 $x509DataNode->appendChild($x509IssuerNode);
1060 $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.
'X509IssuerName', $issuerName);
1061 $x509IssuerNode->appendChild($x509Node);
1062 $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.
'X509SerialNumber', $certData[
'serialNumber']);
1063 $x509IssuerNode->appendChild($x509Node);
1068 $x509CertNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.
'X509Certificate', $X509Cert);
1069 $x509DataNode->appendChild($x509CertNode);
1082 self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath,
$options);
1098 $baseDoc = $parentRef->ownerDocument;
1101 if (empty($xpath)) {
1102 $xpath =
new DOMXPath($parentRef->ownerDocument);
1103 $xpath->registerNamespace(
'secdsig', self::XMLDSIGNS);
1106 $query =
"./secdsig:KeyInfo";
1107 $nodeset = $xpath->query(
$query, $parentRef);
1108 $keyInfo = $nodeset->item(0);
1111 $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS);
1112 if (! empty($pfx)) {
1113 $dsig_pfx = $pfx.
":";
1116 $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.
'KeyInfo');
1118 $query =
"./secdsig:Object";
1119 $nodeset = $xpath->query(
$query, $parentRef);
1120 if ($sObject = $nodeset->item(0)) {
1121 $sObject->parentNode->insertBefore($keyInfo, $sObject);
1126 $parentRef->appendChild($keyInfo);
1130 $keyInfo->appendChild($node);
calculateDigest($digestAlgorithm, $data, $encode=true)
if($err=$client->getError()) $namespace
addReferenceList($arNodes, $algorithm, $arTransforms=null, $options=null)
canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefixList=null)
addReference($node, $algorithm, $arTransforms=null, $options=null)
appendKey($objKey, $parent=null)
static staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=false, $xpath=null, $options=null)
add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null)
validateDigest($refNode, $data)
static generate_GUID($prefix='pfx')
Generate guid.
locateSignature($objDoc, $pos=0)
createNewSignNode($name, $value=null)
appendSignature($parentNode, $insertBefore=false)
insertSignature($node, $beforeNode=null)
This function inserts the signature element.
static get509XCert($cert, $isPEMFormat=true)
verify($objKey)
Returns: Bool when verifying HMAC_SHA1; Int otherwise, with following meanings: 1 on succesful signat...
appendToKeyInfo($node)
This function appends a node to the KeyInfo.
getXPathObj()
Returns the XPathObj or null if xPathCtx is set and sigNode is empty.
static staticGet509XCerts($certs, $isPEMFormat=true)
__construct($prefix='ds')
sign($objKey, $appendToNode=null)
processTransforms($refNode, $objData, $includeCommentNodes=true)
addObject($data, $mimetype=null, $encoding=null)
static generateGUID($prefix='pfx')
Generate guid.
getValidatedNodes()
This function retrieves an associative array of the validated nodes.
setCanonicalMethod($method)
resetXPathObj()
Reset the XPathObj to null.
addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=null, $options=null)
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.