Completed
Push — master ( abae97...fc5d0c )
by Breno
03:37 queued 01:48
created

Signer::xml()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 31
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 4.002

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 19
cts 20
cp 0.95
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 25
nc 4
nop 5
crap 4.002
1
<?php
2
3
namespace XmlSigner;
4
5
use XmlSigner\Exception\SignerException;
6
use XmlSigner\Validator\XmlValidator;
7
use DOMDocument;
8
use DOMNode;
9
10
class Signer
11
{
12
    const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#';
13
    const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
14
    const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
15
    const SHA1_SIG = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
16
    const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256';
17
    const SHA256_SIG = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
18
19
    private $algorithm;
20
    private $canonical;
21
    private $certificate;
22
23 3
    public function __construct(Certificate $certificate)
24
    {
25 3
        $this->certificate = $certificate;
26 3
    }
27
28 3
    public function xml(
29
        $content,
30
        $tagName,
31
        $rootName = '',
32
        $algorithm = OPENSSL_ALGO_SHA1,
33
        $canonical = [true, false, null, null]
34
    ) {
35 3
        $this->assertValidContent($content);
36 2
        $this->algorithm = $algorithm;
37 2
        $this->canonical = $canonical;
38
39 2
        $dom = new DOMDocument('1.0', 'UTF-8');
40 2
        $dom->loadXML($content);
41 2
        $dom->preserveWhiteSpace = false;
42 2
        $dom->formatOutput = false;
43 2
        $root = $dom->documentElement;
44 2
        if (!empty($rootName)) {
45
            $root = $dom->getElementsByTagName($rootName)->item(0);
46
        }
47 2
        $node = $dom->getElementsByTagName($tagName)->item(0);
48 2
        if (empty($node) || empty($root)) {
49 1
            throw SignerException::tagNotFound($tagName);
50
        }
51 1
        $dom = $this->createSignature(
52 1
            $dom,
53 1
            $root,
54 1
            $node
55
        );
56 1
        return (string) '<?xml version="1.0" encoding="UTF-8"?>'
57 1
           . $dom->saveXML($dom->documentElement, LIBXML_NOXMLDECL);
58
    }
59
60 3
    private function assertValidContent($content)
61
    {
62 3
        if (!XmlValidator::valid($content)) {
63 1
            throw SignerException::invalidContent();
64
        }
65 2
    }
66
67 1
    private function createSignature(
68
        DOMDocument $dom,
69
        DOMNode $root,
70
        DOMNode $node
71
    ) {
72 1
        $algorithmData = $this->algorithmData();
73 1
        $nsSignatureMethod = $algorithmData['nsSignatureMethod'];
74 1
        $nsDigestMethod = $algorithmData['nsDigestMethod'];
75 1
        $digestAlgorithm = $algorithmData['digestAlgorithm'];
76
77 1
        $digestValue = $this->makeDigest($node, $digestAlgorithm);
78 1
        $signatureNode = $dom->createElementNS(self::XMLDSIGNS, 'Signature');
79 1
        $root->appendChild($signatureNode);
80
81 1
        $signedInfoNode = $dom->createElement('SignedInfo');
82 1
        $signatureNode->appendChild($signedInfoNode);
83 1
        $canonicalNode = $dom->createElement('CanonicalizationMethod');
84 1
        $signedInfoNode->appendChild($canonicalNode);
85 1
        $canonicalNode->setAttribute('Algorithm', self::C14N);
86 1
        $signatureMethodNode = $dom->createElement('SignatureMethod');
87 1
        $signedInfoNode->appendChild($signatureMethodNode);
88 1
        $signatureMethodNode->setAttribute('Algorithm', $nsSignatureMethod);
89 1
        $referenceNode = $dom->createElement('Reference');
90 1
        $signedInfoNode->appendChild($referenceNode);
91 1
        $transformsNode = $dom->createElement('Transforms');
92 1
        $referenceNode->appendChild($transformsNode);
93 1
        $transfNode1 = $dom->createElement('Transform');
94 1
        $transformsNode->appendChild($transfNode1);
95 1
        $transfNode1->setAttribute('Algorithm', self::XMLDSIGNS.'enveloped-signature');
96 1
        $transfNode2 = $dom->createElement('Transform');
97 1
        $transformsNode->appendChild($transfNode2);
98 1
        $transfNode2->setAttribute('Algorithm', self::C14N);
99 1
        $digestMethodNode = $dom->createElement('DigestMethod');
100 1
        $referenceNode->appendChild($digestMethodNode);
101 1
        $digestMethodNode->setAttribute('Algorithm', $nsDigestMethod);
102 1
        $digestValueNode = $dom->createElement('DigestValue', $digestValue);
103 1
        $referenceNode->appendChild($digestValueNode);
104
105 1
        $c14n = $this->canonize($signedInfoNode);
106 1
        $signature = $this->certificate->sign($c14n, $this->algorithm);
107 1
        $signatureValue = base64_encode($signature);
108 1
        $signatureValueNode = $dom->createElement('SignatureValue', $signatureValue);
109 1
        $signatureNode->appendChild($signatureValueNode);
110
111 1
        $keyInfoNode = $dom->createElement('KeyInfo');
112 1
        $signatureNode->appendChild($keyInfoNode);
113 1
        $x509DataNode = $dom->createElement('X509Data');
114 1
        $keyInfoNode->appendChild($x509DataNode);
115 1
        $pubKeyClean = $this->certificate->publicKey();
116 1
        $x509CertificateNode = $dom->createElement('X509Certificate', $pubKeyClean);
117 1
        $x509DataNode->appendChild($x509CertificateNode);
118
119 1
        return $dom;
120
    }
121
122 1
    private function algorithmData()
123
    {
124 1
        switch ($this->algorithm) {
125 1
            case OPENSSL_ALGO_SHA256:
126
                $digestAlgorithm = 'sha256';
127
                $nsSignatureMethod = self::SHA256_SIG;
128
                $nsDigestMethod = self::SHA256;
129
                break;
130
            default:
131 1
                $nsSignatureMethod = self::SHA1_SIG;
132 1
                $nsDigestMethod = self::SHA1;
133 1
                $digestAlgorithm = 'sha1';
134 1
                break;
135
        }
136
137
        return [
138 1
            'digestAlgorithm' => $digestAlgorithm,
139 1
            'nsSignatureMethod' => $nsSignatureMethod,
140 1
            'nsDigestMethod' => $nsDigestMethod
141
        ];
142
    }
143
144
    /**
145
    * Calculate digest value for given node
146
    * @param DOMNode $node
147
    * @param string $algorithm
148
    * @return string
149
    */
150 1
    private function makeDigest(DOMNode $node, $algorithm)
151
    {
152 1
        $c14n = $this->canonize($node);
153 1
        $hashValue = hash($algorithm, $c14n, true);
154 1
        return base64_encode($hashValue);
155
    }
156
157
    /**
158
    * Reduced to the canonical form
159
    * @param DOMNode $node
160
    * @return string
161
    */
162 1
    private function canonize(DOMNode $node)
163
    {
164 1
        return $node->C14N(
165 1
            $this->canonical[0],
166 1
            $this->canonical[1],
167 1
            $this->canonical[2],
168 1
            $this->canonical[3]
169
        );
170
    }
171
}
172