Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like XMLSecurityDSig often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use XMLSecurityDSig, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 658 | class XMLSecurityDSig {
|
||
| 659 | const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#'; |
||
| 660 | const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'; |
||
| 661 | const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'; |
||
| 662 | const SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'; |
||
| 663 | const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'; |
||
| 664 | const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160'; |
||
| 665 | |||
| 666 | const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; |
||
| 667 | const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'; |
||
| 668 | const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#'; |
||
| 669 | const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments'; |
||
| 670 | |||
| 671 | const template = '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> |
||
| 672 | <ds:SignedInfo> |
||
| 673 | <ds:SignatureMethod /> |
||
| 674 | </ds:SignedInfo> |
||
| 675 | </ds:Signature>'; |
||
| 676 | |||
| 677 | public $sigNode = NULL; |
||
| 678 | public $idKeys = array(); |
||
| 679 | public $idNS = array(); |
||
| 680 | private $signedInfo = NULL; |
||
| 681 | private $xPathCtx = NULL; |
||
| 682 | private $canonicalMethod = NULL; |
||
| 683 | private $prefix = 'ds'; |
||
| 684 | private $searchpfx = 'secdsig'; |
||
| 685 | |||
| 686 | /* This variable contains an associative array of validated nodes. */ |
||
| 687 | private $validatedNodes = NULL; |
||
| 688 | |||
| 689 | public function __construct() {
|
||
| 690 | $sigdoc = new DOMDocument(); |
||
| 691 | $sigdoc->loadXML(self::template); |
||
| 692 | $this->sigNode = $sigdoc->documentElement; |
||
| 693 | } |
||
| 694 | |||
| 695 | private function resetXPathObj() {
|
||
| 696 | $this->xPathCtx = NULL; |
||
| 697 | } |
||
| 698 | |||
| 699 | private function getXPathObj() {
|
||
| 700 | if (empty($this->xPathCtx) && ! empty($this->sigNode)) {
|
||
| 701 | $xpath = new DOMXPath($this->sigNode->ownerDocument); |
||
| 702 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
|
||
| 703 | $this->xPathCtx = $xpath; |
||
| 704 | } |
||
| 705 | return $this->xPathCtx; |
||
| 706 | } |
||
| 707 | |||
| 708 | static function generate_GUID($prefix='pfx') {
|
||
| 709 | $uuid = md5(uniqid(rand(), true)); |
||
| 710 | $guid = $prefix.substr($uuid,0,8)."-". |
||
| 711 | substr($uuid,8,4)."-". |
||
| 712 | substr($uuid,12,4)."-". |
||
| 713 | substr($uuid,16,4)."-". |
||
| 714 | substr($uuid,20,12); |
||
| 715 | return $guid; |
||
| 716 | } |
||
| 717 | |||
| 718 | public function locateSignature($objDoc) {
|
||
| 719 | if ($objDoc instanceof DOMDocument) {
|
||
| 720 | $doc = $objDoc; |
||
| 721 | } else {
|
||
| 722 | $doc = $objDoc->ownerDocument; |
||
| 723 | } |
||
| 724 | if ($doc) {
|
||
| 725 | $xpath = new DOMXPath($doc); |
||
| 726 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
|
||
| 727 | $query = ".//secdsig:Signature"; |
||
| 728 | $nodeset = $xpath->query($query, $objDoc); |
||
| 729 | $this->sigNode = $nodeset->item(0); |
||
| 730 | return $this->sigNode; |
||
| 731 | } |
||
| 732 | return NULL; |
||
| 733 | } |
||
| 734 | |||
| 735 | public function createNewSignNode($name, $value=NULL) {
|
||
| 736 | $doc = $this->sigNode->ownerDocument; |
||
| 737 | if (! is_null($value)) {
|
||
| 738 | $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.':'.$name, $value); |
||
| 739 | } else {
|
||
| 740 | $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.':'.$name); |
||
| 741 | } |
||
| 742 | return $node; |
||
| 743 | } |
||
| 744 | |||
| 745 | public function setCanonicalMethod($method) {
|
||
| 746 | switch ($method) {
|
||
| 747 | case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': |
||
| 748 | case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': |
||
| 749 | case 'http://www.w3.org/2001/10/xml-exc-c14n#': |
||
| 750 | case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': |
||
| 751 | $this->canonicalMethod = $method; |
||
| 752 | break; |
||
| 753 | default: |
||
| 754 | throw new Exception('Invalid Canonical Method');
|
||
| 755 | } |
||
| 756 | if ($xpath = $this->getXPathObj()) {
|
||
| 757 | $query = './'.$this->searchpfx.':SignedInfo'; |
||
| 758 | $nodeset = $xpath->query($query, $this->sigNode); |
||
| 759 | if ($sinfo = $nodeset->item(0)) {
|
||
| 760 | $query = './'.$this->searchpfx.'CanonicalizationMethod'; |
||
| 761 | $nodeset = $xpath->query($query, $sinfo); |
||
| 762 | if (! ($canonNode = $nodeset->item(0))) {
|
||
| 763 | $canonNode = $this->createNewSignNode('CanonicalizationMethod');
|
||
| 764 | $sinfo->insertBefore($canonNode, $sinfo->firstChild); |
||
| 765 | } |
||
| 766 | $canonNode->setAttribute('Algorithm', $this->canonicalMethod);
|
||
| 767 | } |
||
| 768 | } |
||
| 769 | } |
||
| 770 | |||
| 771 | private function canonicalizeData($node, $canonicalmethod, $arXPath=NULL, $prefixList=NULL) {
|
||
| 772 | $exclusive = FALSE; |
||
| 773 | $withComments = FALSE; |
||
| 774 | switch ($canonicalmethod) {
|
||
| 775 | case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': |
||
| 776 | $exclusive = FALSE; |
||
| 777 | $withComments = FALSE; |
||
| 778 | break; |
||
| 779 | case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': |
||
| 780 | $withComments = TRUE; |
||
| 781 | break; |
||
| 782 | case 'http://www.w3.org/2001/10/xml-exc-c14n#': |
||
| 783 | $exclusive = TRUE; |
||
| 784 | break; |
||
| 785 | case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': |
||
| 786 | $exclusive = TRUE; |
||
| 787 | $withComments = TRUE; |
||
| 788 | break; |
||
| 789 | } |
||
| 790 | /* Support PHP versions < 5.2 not containing C14N methods in DOM extension */ |
||
| 791 | $php_version = explode('.', PHP_VERSION);
|
||
| 792 | if (($php_version[0] < 5) || ($php_version[0] == 5 && $php_version[1] < 2) ) {
|
||
| 793 | if (! is_null($arXPath)) {
|
||
| 794 | throw new Exception("PHP 5.2.0 or higher is required to perform XPath Transformations");
|
||
| 795 | } |
||
| 796 | return C14NGeneral($node, $exclusive, $withComments); |
||
| 797 | } |
||
| 798 | return $node->C14N($exclusive, $withComments, $arXPath, $prefixList); |
||
| 799 | } |
||
| 800 | |||
| 801 | public function canonicalizeSignedInfo() {
|
||
| 802 | |||
| 803 | $doc = $this->sigNode->ownerDocument; |
||
| 804 | $canonicalmethod = NULL; |
||
| 805 | if ($doc) {
|
||
| 806 | $xpath = $this->getXPathObj(); |
||
| 807 | $query = "./secdsig:SignedInfo"; |
||
| 808 | $nodeset = $xpath->query($query, $this->sigNode); |
||
| 809 | if ($signInfoNode = $nodeset->item(0)) {
|
||
| 810 | $query = "./secdsig:CanonicalizationMethod"; |
||
| 811 | $nodeset = $xpath->query($query, $signInfoNode); |
||
| 812 | if ($canonNode = $nodeset->item(0)) {
|
||
| 813 | $canonicalmethod = $canonNode->getAttribute('Algorithm');
|
||
| 814 | } |
||
| 815 | $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod); |
||
| 816 | return $this->signedInfo; |
||
| 817 | } |
||
| 818 | } |
||
| 819 | return NULL; |
||
| 820 | } |
||
| 821 | |||
| 822 | public function calculateDigest ($digestAlgorithm, $data) {
|
||
| 823 | switch ($digestAlgorithm) {
|
||
| 824 | case self::SHA1: |
||
| 825 | $alg = 'sha1'; |
||
| 826 | break; |
||
| 827 | case self::SHA256: |
||
| 828 | $alg = 'sha256'; |
||
| 829 | break; |
||
| 830 | case self::SHA384: |
||
| 831 | $alg = 'sha384'; |
||
| 832 | break; |
||
| 833 | case self::SHA512: |
||
| 834 | $alg = 'sha512'; |
||
| 835 | break; |
||
| 836 | case self::RIPEMD160: |
||
| 837 | $alg = 'ripemd160'; |
||
| 838 | break; |
||
| 839 | default: |
||
| 840 | throw new Exception("Cannot validate digest: Unsupported Algorith <$digestAlgorithm>");
|
||
| 841 | } |
||
| 842 | if (function_exists('hash')) {
|
||
| 843 | return base64_encode(hash($alg, $data, TRUE)); |
||
| 844 | } elseif (function_exists('mhash')) {
|
||
| 845 | $alg = "MHASH_" . strtoupper($alg); |
||
| 846 | return base64_encode(mhash(constant($alg), $data)); |
||
| 847 | } elseif ($alg === 'sha1') {
|
||
| 848 | return base64_encode(sha1($data, TRUE)); |
||
| 849 | } else {
|
||
| 850 | throw new Exception('xmlseclibs is unable to calculate a digest. Maybe you need the mhash library?');
|
||
| 851 | } |
||
| 852 | } |
||
| 853 | |||
| 854 | public function validateDigest($refNode, $data) {
|
||
| 855 | $xpath = new DOMXPath($refNode->ownerDocument); |
||
| 856 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
|
||
| 857 | $query = 'string(./secdsig:DigestMethod/@Algorithm)'; |
||
| 858 | $digestAlgorithm = $xpath->evaluate($query, $refNode); |
||
| 859 | $digValue = $this->calculateDigest($digestAlgorithm, $data); |
||
| 860 | $query = 'string(./secdsig:DigestValue)'; |
||
| 861 | $digestValue = $xpath->evaluate($query, $refNode); |
||
| 862 | return ($digValue == $digestValue); |
||
| 863 | } |
||
| 864 | |||
| 865 | public function processTransforms($refNode, $objData, $includeCommentNodes = TRUE) {
|
||
| 866 | $data = $objData; |
||
| 867 | $xpath = new DOMXPath($refNode->ownerDocument); |
||
| 868 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
|
||
| 869 | $query = './secdsig:Transforms/secdsig:Transform'; |
||
| 870 | $nodelist = $xpath->query($query, $refNode); |
||
| 871 | $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; |
||
| 872 | $arXPath = NULL; |
||
| 873 | $prefixList = NULL; |
||
| 874 | foreach ($nodelist AS $transform) {
|
||
| 875 | $algorithm = $transform->getAttribute("Algorithm");
|
||
| 876 | switch ($algorithm) {
|
||
| 877 | case 'http://www.w3.org/2001/10/xml-exc-c14n#': |
||
| 878 | // no break |
||
| 879 | case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': |
||
| 880 | if(!$includeCommentNodes) {
|
||
| 881 | /* We remove comment nodes by forcing it to use a canonicalization |
||
| 882 | * without comments. |
||
| 883 | */ |
||
| 884 | $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#'; |
||
| 885 | } else {
|
||
| 886 | $canonicalMethod = $algorithm; |
||
| 887 | } |
||
| 888 | |||
| 889 | $node = $transform->firstChild; |
||
| 890 | while ($node) {
|
||
| 891 | if ($node->localName == 'InclusiveNamespaces') {
|
||
| 892 | if ($pfx = $node->getAttribute('PrefixList')) {
|
||
| 893 | $arpfx = array(); |
||
| 894 | $pfxlist = explode(" ", $pfx);
|
||
| 895 | foreach ($pfxlist AS $pfx) {
|
||
| 896 | $val = trim($pfx); |
||
| 897 | if (! empty($val)) {
|
||
| 898 | $arpfx[] = $val; |
||
| 899 | } |
||
| 900 | } |
||
| 901 | if (count($arpfx) > 0) {
|
||
| 902 | $prefixList = $arpfx; |
||
| 903 | } |
||
| 904 | } |
||
| 905 | break; |
||
| 906 | } |
||
| 907 | $node = $node->nextSibling; |
||
| 908 | } |
||
| 909 | break; |
||
| 910 | case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': |
||
| 911 | case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': |
||
| 912 | if(!$includeCommentNodes) {
|
||
| 913 | /* We remove comment nodes by forcing it to use a canonicalization |
||
| 914 | * without comments. |
||
| 915 | */ |
||
| 916 | $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; |
||
| 917 | } else {
|
||
| 918 | $canonicalMethod = $algorithm; |
||
| 919 | } |
||
| 920 | |||
| 921 | break; |
||
| 922 | case 'http://www.w3.org/TR/1999/REC-xpath-19991116': |
||
| 923 | $node = $transform->firstChild; |
||
| 924 | while ($node) {
|
||
| 925 | if ($node->localName == 'XPath') {
|
||
| 926 | $arXPath = array(); |
||
| 927 | $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']'; |
||
| 928 | $arXpath['namespaces'] = array(); |
||
| 929 | $nslist = $xpath->query('./namespace::*', $node);
|
||
| 930 | foreach ($nslist AS $nsnode) {
|
||
| 931 | if ($nsnode->localName != "xml") {
|
||
| 932 | $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue; |
||
| 933 | } |
||
| 934 | } |
||
| 935 | break; |
||
| 936 | } |
||
| 937 | $node = $node->nextSibling; |
||
| 938 | } |
||
| 939 | break; |
||
| 940 | } |
||
| 941 | } |
||
| 942 | if ($data instanceof DOMNode) {
|
||
| 943 | $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList); |
||
| 944 | } |
||
| 945 | return $data; |
||
| 946 | } |
||
| 947 | |||
| 948 | public function processRefNode($refNode) {
|
||
| 949 | $dataObject = NULL; |
||
| 950 | |||
| 951 | /* |
||
| 952 | * Depending on the URI, we may not want to include comments in the result |
||
| 953 | * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel |
||
| 954 | */ |
||
| 955 | $includeCommentNodes = TRUE; |
||
| 956 | |||
| 957 | if ($uri = $refNode->getAttribute("URI")) {
|
||
| 958 | $arUrl = parse_url($uri); |
||
| 959 | if (empty($arUrl['path'])) {
|
||
| 960 | if ($identifier = $arUrl['fragment']) {
|
||
| 961 | |||
| 962 | /* This reference identifies a node with the given id by using |
||
| 963 | * a URI on the form "#identifier". This should not include comments. |
||
| 964 | */ |
||
| 965 | $includeCommentNodes = FALSE; |
||
| 966 | |||
| 967 | $xPath = new DOMXPath($refNode->ownerDocument); |
||
| 968 | if ($this->idNS && is_array($this->idNS)) {
|
||
| 969 | foreach ($this->idNS AS $nspf=>$ns) {
|
||
| 970 | $xPath->registerNamespace($nspf, $ns); |
||
| 971 | } |
||
| 972 | } |
||
| 973 | $iDlist = '@Id="'.$identifier.'"'; |
||
| 974 | if (is_array($this->idKeys)) {
|
||
| 975 | foreach ($this->idKeys AS $idKey) {
|
||
| 976 | $iDlist .= " or @$idKey='$identifier'"; |
||
| 977 | } |
||
| 978 | } |
||
| 979 | $query = '//*['.$iDlist.']'; |
||
| 980 | $dataObject = $xPath->query($query)->item(0); |
||
| 981 | } else {
|
||
| 982 | $dataObject = $refNode->ownerDocument; |
||
| 983 | } |
||
| 984 | } else {
|
||
| 985 | $dataObject = file_get_contents($arUrl); |
||
| 986 | } |
||
| 987 | } else {
|
||
| 988 | /* This reference identifies the root node with an empty URI. This should |
||
| 989 | * not include comments. |
||
| 990 | */ |
||
| 991 | $includeCommentNodes = FALSE; |
||
| 992 | |||
| 993 | $dataObject = $refNode->ownerDocument; |
||
| 994 | } |
||
| 995 | $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes); |
||
| 996 | if (!$this->validateDigest($refNode, $data)) {
|
||
| 997 | return FALSE; |
||
| 998 | } |
||
| 999 | |||
| 1000 | if ($dataObject instanceof DOMNode) {
|
||
| 1001 | /* Add this node to the list of validated nodes. */ |
||
| 1002 | if(! empty($identifier)) {
|
||
| 1003 | $this->validatedNodes[$identifier] = $dataObject; |
||
| 1004 | } else {
|
||
| 1005 | $this->validatedNodes[] = $dataObject; |
||
| 1006 | } |
||
| 1007 | } |
||
| 1008 | |||
| 1009 | return TRUE; |
||
| 1010 | } |
||
| 1011 | |||
| 1012 | public function getRefNodeID($refNode) {
|
||
| 1013 | if ($uri = $refNode->getAttribute("URI")) {
|
||
| 1014 | $arUrl = parse_url($uri); |
||
| 1015 | if (empty($arUrl['path'])) {
|
||
| 1016 | if ($identifier = $arUrl['fragment']) {
|
||
| 1017 | return $identifier; |
||
| 1018 | } |
||
| 1019 | } |
||
| 1020 | } |
||
| 1021 | return null; |
||
| 1022 | } |
||
| 1023 | |||
| 1024 | public function getRefIDs() {
|
||
| 1025 | $refids = array(); |
||
| 1026 | $doc = $this->sigNode->ownerDocument; |
||
| 1027 | |||
| 1028 | $xpath = $this->getXPathObj(); |
||
| 1029 | $query = "./secdsig:SignedInfo/secdsig:Reference"; |
||
| 1030 | $nodeset = $xpath->query($query, $this->sigNode); |
||
| 1031 | if ($nodeset->length == 0) {
|
||
| 1032 | throw new Exception("Reference nodes not found");
|
||
| 1033 | } |
||
| 1034 | foreach ($nodeset AS $refNode) {
|
||
| 1035 | $refids[] = $this->getRefNodeID($refNode); |
||
| 1036 | } |
||
| 1037 | return $refids; |
||
| 1038 | } |
||
| 1039 | |||
| 1040 | public function validateReference() {
|
||
| 1041 | $doc = $this->sigNode->ownerDocument; |
||
| 1042 | if (! $doc->isSameNode($this->sigNode)) {
|
||
| 1043 | $this->sigNode->parentNode->removeChild($this->sigNode); |
||
| 1044 | } |
||
| 1045 | $xpath = $this->getXPathObj(); |
||
| 1046 | $query = "./secdsig:SignedInfo/secdsig:Reference"; |
||
| 1047 | $nodeset = $xpath->query($query, $this->sigNode); |
||
| 1048 | if ($nodeset->length == 0) {
|
||
| 1049 | throw new Exception("Reference nodes not found");
|
||
| 1050 | } |
||
| 1051 | |||
| 1052 | /* Initialize/reset the list of validated nodes. */ |
||
| 1053 | $this->validatedNodes = array(); |
||
| 1054 | |||
| 1055 | foreach ($nodeset AS $refNode) {
|
||
| 1056 | if (! $this->processRefNode($refNode)) {
|
||
| 1057 | /* Clear the list of validated nodes. */ |
||
| 1058 | $this->validatedNodes = NULL; |
||
| 1059 | throw new Exception("Reference validation failed");
|
||
| 1060 | } |
||
| 1061 | } |
||
| 1062 | return TRUE; |
||
| 1063 | } |
||
| 1064 | |||
| 1065 | private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=NULL, $options=NULL) {
|
||
| 1066 | $prefix = NULL; |
||
| 1067 | $prefix_ns = NULL; |
||
| 1068 | $id_name = 'Id'; |
||
| 1069 | $overwrite_id = TRUE; |
||
| 1070 | $force_uri = FALSE; |
||
| 1071 | |||
| 1072 | if (is_array($options)) {
|
||
| 1073 | $prefix = empty($options['prefix'])?NULL:$options['prefix']; |
||
| 1074 | $prefix_ns = empty($options['prefix_ns'])?NULL:$options['prefix_ns']; |
||
| 1075 | $id_name = empty($options['id_name'])?'Id':$options['id_name']; |
||
| 1076 | $overwrite_id = !isset($options['overwrite'])?TRUE:(bool)$options['overwrite']; |
||
| 1077 | $force_uri = !isset($options['force_uri'])?FALSE:(bool)$options['force_uri']; |
||
| 1078 | } |
||
| 1079 | |||
| 1080 | $attname = $id_name; |
||
| 1081 | if (! empty($prefix)) {
|
||
| 1082 | $attname = $prefix.':'.$attname; |
||
| 1083 | } |
||
| 1084 | |||
| 1085 | $refNode = $this->createNewSignNode('Reference');
|
||
| 1086 | $sinfoNode->appendChild($refNode); |
||
| 1087 | |||
| 1088 | if (! $node instanceof DOMDocument) {
|
||
| 1089 | $uri = NULL; |
||
| 1090 | if (! $overwrite_id) {
|
||
| 1091 | $uri = $node->getAttributeNS($prefix_ns, $id_name); |
||
| 1092 | } |
||
| 1093 | if (empty($uri)) {
|
||
| 1094 | $uri = self::generate_GUID(); |
||
| 1095 | $node->setAttributeNS($prefix_ns, $attname, $uri); |
||
| 1096 | } |
||
| 1097 | $refNode->setAttribute("URI", '#'.$uri);
|
||
| 1098 | } elseif ($force_uri) {
|
||
| 1099 | $refNode->setAttribute("URI", '');
|
||
| 1100 | } |
||
| 1101 | |||
| 1102 | $transNodes = $this->createNewSignNode('Transforms');
|
||
| 1103 | $refNode->appendChild($transNodes); |
||
| 1104 | |||
| 1105 | if (is_array($arTransforms)) {
|
||
| 1106 | foreach ($arTransforms AS $transform) {
|
||
| 1107 | $transNode = $this->createNewSignNode('Transform');
|
||
| 1108 | $transNodes->appendChild($transNode); |
||
| 1109 | if (is_array($transform) && |
||
| 1110 | (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) && |
||
| 1111 | (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) {
|
||
| 1112 | $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
|
||
| 1113 | $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']);
|
||
| 1114 | $transNode->appendChild($XPathNode); |
||
| 1115 | if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) {
|
||
| 1116 | foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) {
|
||
| 1117 | $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace);
|
||
| 1118 | } |
||
| 1119 | } |
||
| 1120 | } else {
|
||
| 1121 | $transNode->setAttribute('Algorithm', $transform);
|
||
| 1122 | } |
||
| 1123 | } |
||
| 1124 | } elseif (! empty($this->canonicalMethod)) {
|
||
| 1125 | $transNode = $this->createNewSignNode('Transform');
|
||
| 1126 | $transNodes->appendChild($transNode); |
||
| 1127 | $transNode->setAttribute('Algorithm', $this->canonicalMethod);
|
||
| 1128 | } |
||
| 1129 | |||
| 1130 | $canonicalData = $this->processTransforms($refNode, $node); |
||
| 1131 | $digValue = $this->calculateDigest($algorithm, $canonicalData); |
||
| 1132 | |||
| 1133 | $digestMethod = $this->createNewSignNode('DigestMethod');
|
||
| 1134 | $refNode->appendChild($digestMethod); |
||
| 1135 | $digestMethod->setAttribute('Algorithm', $algorithm);
|
||
| 1136 | |||
| 1137 | $digestValue = $this->createNewSignNode('DigestValue', $digValue);
|
||
| 1138 | $refNode->appendChild($digestValue); |
||
| 1139 | } |
||
| 1140 | |||
| 1141 | public function addReference($node, $algorithm, $arTransforms=NULL, $options=NULL) {
|
||
| 1142 | if ($xpath = $this->getXPathObj()) {
|
||
| 1143 | $query = "./secdsig:SignedInfo"; |
||
| 1144 | $nodeset = $xpath->query($query, $this->sigNode); |
||
| 1145 | if ($sInfo = $nodeset->item(0)) {
|
||
| 1146 | $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); |
||
| 1147 | } |
||
| 1148 | } |
||
| 1149 | } |
||
| 1150 | |||
| 1151 | public function addReferenceList($arNodes, $algorithm, $arTransforms=NULL, $options=NULL) {
|
||
| 1152 | if ($xpath = $this->getXPathObj()) {
|
||
| 1153 | $query = "./secdsig:SignedInfo"; |
||
| 1154 | $nodeset = $xpath->query($query, $this->sigNode); |
||
| 1155 | if ($sInfo = $nodeset->item(0)) {
|
||
| 1156 | foreach ($arNodes AS $node) {
|
||
| 1157 | $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); |
||
| 1158 | } |
||
| 1159 | } |
||
| 1160 | } |
||
| 1161 | } |
||
| 1162 | |||
| 1163 | public function addObject($data, $mimetype=NULL, $encoding=NULL) {
|
||
| 1164 | $objNode = $this->createNewSignNode('Object');
|
||
| 1165 | $this->sigNode->appendChild($objNode); |
||
| 1166 | if (! empty($mimetype)) {
|
||
| 1167 | $objNode->setAtribute('MimeType', $mimetype);
|
||
| 1168 | } |
||
| 1169 | if (! empty($encoding)) {
|
||
| 1170 | $objNode->setAttribute('Encoding', $encoding);
|
||
| 1171 | } |
||
| 1172 | |||
| 1173 | if ($data instanceof DOMElement) {
|
||
| 1174 | $newData = $this->sigNode->ownerDocument->importNode($data, TRUE); |
||
| 1175 | } else {
|
||
| 1176 | $newData = $this->sigNode->ownerDocument->createTextNode($data); |
||
| 1177 | } |
||
| 1178 | $objNode->appendChild($newData); |
||
| 1179 | |||
| 1180 | return $objNode; |
||
| 1181 | } |
||
| 1182 | |||
| 1183 | public function locateKey($node=NULL) {
|
||
| 1184 | if (empty($node)) {
|
||
| 1185 | $node = $this->sigNode; |
||
| 1186 | } |
||
| 1187 | if (! $node instanceof DOMNode) {
|
||
| 1188 | return NULL; |
||
| 1189 | } |
||
| 1190 | if ($doc = $node->ownerDocument) {
|
||
| 1191 | $xpath = new DOMXPath($doc); |
||
| 1192 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
|
||
| 1193 | $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)"; |
||
| 1194 | $algorithm = $xpath->evaluate($query, $node); |
||
| 1195 | if ($algorithm) {
|
||
| 1196 | try {
|
||
| 1197 | $objKey = new XMLSecurityKey($algorithm, array('type'=>'public'));
|
||
| 1198 | } catch (Exception $e) {
|
||
| 1199 | return NULL; |
||
| 1200 | } |
||
| 1201 | return $objKey; |
||
| 1202 | } |
||
| 1203 | } |
||
| 1204 | return NULL; |
||
| 1205 | } |
||
| 1206 | |||
| 1207 | public function verify($objKey) {
|
||
| 1208 | $doc = $this->sigNode->ownerDocument; |
||
| 1209 | $xpath = new DOMXPath($doc); |
||
| 1210 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
|
||
| 1211 | $query = "string(./secdsig:SignatureValue)"; |
||
| 1212 | $sigValue = $xpath->evaluate($query, $this->sigNode); |
||
| 1213 | |||
| 1214 | if (empty($sigValue)) {
|
||
| 1215 | throw new Exception("Unable to locate SignatureValue");
|
||
| 1216 | } |
||
| 1217 | error_log('-->>>>');
|
||
| 1218 | error_log(print_r($this->signedInfo,1)); |
||
| 1219 | error_log(print_r($sigValue,1)); |
||
| 1220 | error_log(print_r($objKey->verifySignature($this->signedInfo, base64_decode($sigValue)), 1)); |
||
| 1221 | |||
| 1222 | return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue)); |
||
| 1223 | } |
||
| 1224 | |||
| 1225 | public function signData($objKey, $data) {
|
||
| 1226 | return $objKey->signData($data); |
||
| 1227 | } |
||
| 1228 | |||
| 1229 | public function sign($objKey, $appendToNode = NULL) {
|
||
| 1230 | // If we have a parent node append it now so C14N properly works |
||
| 1231 | if ($appendToNode != NULL) {
|
||
| 1232 | $this->resetXPathObj(); |
||
| 1233 | $this->appendSignature($appendToNode); |
||
| 1234 | $this->sigNode = $appendToNode->lastChild; |
||
| 1235 | } |
||
| 1236 | if ($xpath = $this->getXPathObj()) {
|
||
| 1237 | $query = "./secdsig:SignedInfo"; |
||
| 1238 | $nodeset = $xpath->query($query, $this->sigNode); |
||
| 1239 | if ($sInfo = $nodeset->item(0)) {
|
||
| 1240 | $query = "./secdsig:SignatureMethod"; |
||
| 1241 | $nodeset = $xpath->query($query, $sInfo); |
||
| 1242 | $sMethod = $nodeset->item(0); |
||
| 1243 | $sMethod->setAttribute('Algorithm', $objKey->type);
|
||
| 1244 | $data = $this->canonicalizeData($sInfo, $this->canonicalMethod); |
||
| 1245 | $sigValue = base64_encode($this->signData($objKey, $data)); |
||
| 1246 | $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue);
|
||
| 1247 | if ($infoSibling = $sInfo->nextSibling) {
|
||
| 1248 | $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling); |
||
| 1249 | } else {
|
||
| 1250 | $this->sigNode->appendChild($sigValueNode); |
||
| 1251 | } |
||
| 1252 | } |
||
| 1253 | } |
||
| 1254 | } |
||
| 1255 | |||
| 1256 | public function appendCert() {
|
||
| 1257 | |||
| 1258 | } |
||
| 1259 | |||
| 1260 | public function appendKey($objKey, $parent=NULL) {
|
||
| 1261 | $objKey->serializeKey($parent); |
||
| 1262 | } |
||
| 1263 | |||
| 1264 | |||
| 1265 | /** |
||
| 1266 | * This function inserts the signature element. |
||
| 1267 | * |
||
| 1268 | * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode |
||
| 1269 | * is specified, the signature element will be inserted as the last element before $beforeNode. |
||
| 1270 | * |
||
| 1271 | * @param $node The node the signature element should be inserted into. |
||
| 1272 | * @param $beforeNode The node the signature element should be located before. |
||
| 1273 | * |
||
| 1274 | * @return DOMNode The signature element node |
||
| 1275 | */ |
||
| 1276 | public function insertSignature($node, $beforeNode = NULL) {
|
||
| 1277 | |||
| 1278 | $document = $node->ownerDocument; |
||
| 1279 | $signatureElement = $document->importNode($this->sigNode, TRUE); |
||
| 1280 | |||
| 1281 | if($beforeNode == NULL) {
|
||
| 1282 | return $node->insertBefore($signatureElement); |
||
| 1283 | } else {
|
||
| 1284 | return $node->insertBefore($signatureElement, $beforeNode); |
||
| 1285 | } |
||
| 1286 | } |
||
| 1287 | |||
| 1288 | public function appendSignature($parentNode, $insertBefore = FALSE) {
|
||
| 1289 | $beforeNode = $insertBefore ? $parentNode->firstChild : NULL; |
||
| 1290 | return $this->insertSignature($parentNode, $beforeNode); |
||
| 1291 | } |
||
| 1292 | |||
| 1293 | static function get509XCert($cert, $isPEMFormat=TRUE) {
|
||
| 1294 | $certs = self::staticGet509XCerts($cert, $isPEMFormat); |
||
| 1295 | if (! empty($certs)) {
|
||
| 1296 | return $certs[0]; |
||
| 1297 | } |
||
| 1298 | return ''; |
||
| 1299 | } |
||
| 1300 | |||
| 1301 | static function staticGet509XCerts($certs, $isPEMFormat=TRUE) {
|
||
| 1302 | if ($isPEMFormat) {
|
||
| 1303 | $data = ''; |
||
| 1304 | $certlist = array(); |
||
| 1305 | $arCert = explode("\n", $certs);
|
||
| 1306 | $inData = FALSE; |
||
| 1307 | foreach ($arCert AS $curData) {
|
||
| 1308 | if (! $inData) {
|
||
| 1309 | if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
|
||
| 1310 | $inData = TRUE; |
||
| 1311 | } |
||
| 1312 | } else {
|
||
| 1313 | if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
|
||
| 1314 | $inData = FALSE; |
||
| 1315 | $certlist[] = $data; |
||
| 1316 | $data = ''; |
||
| 1317 | continue; |
||
| 1318 | } |
||
| 1319 | $data .= trim($curData); |
||
| 1320 | } |
||
| 1321 | } |
||
| 1322 | return $certlist; |
||
| 1323 | } else {
|
||
| 1324 | return array($certs); |
||
| 1325 | } |
||
| 1326 | } |
||
| 1327 | |||
| 1328 | static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=TRUE, $isURL=False, $xpath=NULL, $options=NULL) {
|
||
| 1329 | if ($isURL) {
|
||
| 1330 | $cert = file_get_contents($cert); |
||
| 1331 | } |
||
| 1332 | if (! $parentRef instanceof DOMElement) {
|
||
| 1333 | throw new Exception('Invalid parent Node parameter');
|
||
| 1334 | } |
||
| 1335 | $baseDoc = $parentRef->ownerDocument; |
||
| 1336 | |||
| 1337 | if (empty($xpath)) {
|
||
| 1338 | $xpath = new DOMXPath($parentRef->ownerDocument); |
||
| 1339 | $xpath->registerNamespace('secdsig', self::XMLDSIGNS);
|
||
| 1340 | } |
||
| 1341 | |||
| 1342 | $query = "./secdsig:KeyInfo"; |
||
| 1343 | $nodeset = $xpath->query($query, $parentRef); |
||
| 1344 | $keyInfo = $nodeset->item(0); |
||
| 1345 | if (! $keyInfo) {
|
||
| 1346 | $inserted = FALSE; |
||
| 1347 | $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:KeyInfo'); |
||
| 1348 | |||
| 1349 | $query = "./secdsig:Object"; |
||
| 1350 | $nodeset = $xpath->query($query, $parentRef); |
||
| 1351 | if ($sObject = $nodeset->item(0)) {
|
||
| 1352 | $sObject->parentNode->insertBefore($keyInfo, $sObject); |
||
| 1353 | $inserted = TRUE; |
||
| 1354 | } |
||
| 1355 | |||
| 1356 | if (! $inserted) {
|
||
| 1357 | $parentRef->appendChild($keyInfo); |
||
| 1358 | } |
||
| 1359 | } |
||
| 1360 | |||
| 1361 | // Add all certs if there are more than one |
||
| 1362 | $certs = self::staticGet509XCerts($cert, $isPEMFormat); |
||
| 1363 | |||
| 1364 | // Attach X509 data node |
||
| 1365 | $x509DataNode = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509Data'); |
||
| 1366 | $keyInfo->appendChild($x509DataNode); |
||
| 1367 | |||
| 1368 | $issuerSerial = FALSE; |
||
| 1369 | $subjectName = FALSE; |
||
| 1370 | if (is_array($options)) {
|
||
| 1371 | if (! empty($options['issuerSerial'])) {
|
||
| 1372 | $issuerSerial = TRUE; |
||
| 1373 | } |
||
| 1374 | } |
||
| 1375 | |||
| 1376 | // Attach all certificate nodes and any additional data |
||
| 1377 | foreach ($certs as $X509Cert){
|
||
| 1378 | if ($issuerSerial) {
|
||
| 1379 | if ($certData = openssl_x509_parse("-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64, "\n")."-----END CERTIFICATE-----\n")) {
|
||
| 1380 | if ($issuerSerial && ! empty($certData['issuer']) && ! empty($certData['serialNumber'])) {
|
||
| 1381 | if (is_array($certData['issuer'])) {
|
||
| 1382 | $parts = array(); |
||
| 1383 | foreach ($certData['issuer'] AS $key => $value) {
|
||
| 1384 | array_unshift($parts, "$key=$value"); |
||
| 1385 | } |
||
| 1386 | $issuerName = implode(',', $parts);
|
||
| 1387 | } else {
|
||
| 1388 | $issuerName = $certData['issuer']; |
||
| 1389 | } |
||
| 1390 | |||
| 1391 | $x509IssuerNode = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509IssuerSerial'); |
||
| 1392 | $x509DataNode->appendChild($x509IssuerNode); |
||
| 1393 | |||
| 1394 | $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509IssuerName', $issuerName); |
||
| 1395 | $x509IssuerNode->appendChild($x509Node); |
||
| 1396 | $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509SerialNumber', $certData['serialNumber']); |
||
| 1397 | $x509IssuerNode->appendChild($x509Node); |
||
| 1398 | } |
||
| 1399 | } |
||
| 1400 | |||
| 1401 | } |
||
| 1402 | $x509CertNode = $baseDoc->createElementNS(self::XMLDSIGNS, 'ds:X509Certificate', $X509Cert); |
||
| 1403 | $x509DataNode->appendChild($x509CertNode); |
||
| 1404 | } |
||
| 1405 | } |
||
| 1406 | |||
| 1407 | public function add509Cert($cert, $isPEMFormat=TRUE, $isURL=False, $options=NULL) {
|
||
| 1408 | if ($xpath = $this->getXPathObj()) {
|
||
| 1409 | self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath, $options); |
||
| 1410 | } |
||
| 1411 | } |
||
| 1412 | |||
| 1413 | /* This function retrieves an associative array of the validated nodes. |
||
| 1414 | * |
||
| 1415 | * The array will contain the id of the referenced node as the key and the node itself |
||
| 1416 | * as the value. |
||
| 1417 | * |
||
| 1418 | * Returns: |
||
| 1419 | * An associative array of validated nodes or NULL if no nodes have been validated. |
||
| 1420 | */ |
||
| 1421 | public function getValidatedNodes() {
|
||
| 1422 | return $this->validatedNodes; |
||
| 1423 | } |
||
| 1424 | } |
||
| 1425 | |||
| 1821 |