Passed
Push — master ( 463105...84cd59 )
by Roberto
04:47
created

Signer::createSignature()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 71
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 56
CRAP Score 3.0011

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 71
ccs 56
cts 59
cp 0.9492
rs 9.1369
cc 3
eloc 66
nc 4
nop 7
crap 3.0011

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace NFePHP\Common;
4
5
/**
6
 * Class to signner a Xml
7
 * Meets packages :
8
 *     sped-nfe,
9
 *     sped-cte,
10
 *     sped-mdfe,
11
 *     sped-nfse,
12
 *     sped-efinanceira
13
 *     e sped-esfinge
14
 *
15
 * @category  NFePHP
16
 * @package   NFePHP\Common\Signer
17
 * @copyright NFePHP Copyright (c) 2016
18
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
19
 * @license   https://opensource.org/licenses/MIT MIT
20
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
21
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
22
 * @link      http://github.com/nfephp-org/sped-common for the canonical source repository
23
 */
24
25
use NFePHP\Common\Certificate;
26
use NFePHP\Common\Certificate\PublicKey;
27
use NFePHP\Common\Strings;
28
use NFePHP\Common\Exception\SignnerException;
29
use DOMDocument;
30
use DOMElement;
31
32
class Signer
33
{
34
    private static $canonical = [false,false,null,null];
35
    
36
    /**
37
     * Make Signature tag
38
     * @param string $content
39
     * @param string $tagname
40
     * @param string $marker for URI
0 ignored issues
show
Bug introduced by
There is no parameter named $marker. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
41
     * @param string $algorithm
42
     * @param array $canonical parameters to format node for signature
43
     * @param string $rootname name of tag to insert signature block
44
     * @return string
45
     * @throws \NFePHP\Common\Exception\SignnerException
46
     */
47 1
    public static function sign(
48
        Certificate $certificate,
49
        $content,
50
        $tagname = '',
51
        $mark = 'Id',
52
        $algorithm = OPENSSL_ALGO_SHA1,
53
        $canonical = [false,false,null,null],
54
        $rootname = ''
55
    ) {
56
        //$content = Strings::clearXmlString($content, true);
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
57 1
        if (!empty($canonical)) {
58 1
            self::$canonical = $canonical;
59
        }
60 1
        $dom = new DOMDocument('1.0', 'UTF-8');
61 1
        $dom->loadXML($content);
62 1
        $dom->preserveWhiteSpace = false;
63 1
        $dom->formatOutput = false;
64 1
        $root = $dom->documentElement;
65 1
        if (!empty($rootname)) {
66
            $root = $dom->getElementsByTagName($rootname)->item(0);
67
        }
68 1
        $node = $dom->getElementsByTagName($tagname)->item(0);
69 1
        if (empty($node) || empty($root)) {
70
            throw SignerException::tagNotFound($tagname . ' ' . $rootname);
71
        }
72 1
        if (! self::existsSignature($dom)) {
73 1
            $dom = self::createSignature(
74
                $certificate,
75
                $dom,
76
                $root,
0 ignored issues
show
Compatibility introduced by
$root of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
77
                $node,
0 ignored issues
show
Compatibility introduced by
$node of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
78
                $mark,
79
                $algorithm,
80
                $canonical
81
            );
82
        };
83
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
84 1
            . $dom->saveXML($dom->documentElement, LIBXML_NOXMLDECL);
85
    }
86
    
87
    /**
88
     * Remove old signature from document to replace it
89
     * @param DOMDocument $dom
90
     * @return DOMDocument
91
     */
92
    public static function removeSignature(DOMDocument $dom)
93
    {
94
        $node = $dom->documentElement;
95
        $signature = $node->getElementsByTagName('Signature')->item(0);
96
        if (!empty($signature)) {
97
            $parent = $signature->parentNode;
98
            $oldsignature = $parent->removeChild($signature);
0 ignored issues
show
Unused Code introduced by
$oldsignature is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
99
        }
100
        return $dom;
101
    }
102
    
103
    /**
104
     * Verify if xml signature is valid
105
     * @param string $content xml content
0 ignored issues
show
Bug introduced by
There is no parameter named $content. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
106
     * @param string $tagname tag for sign
107
     * @return boolean
108
     */
109
    public static function isSigned(DOMDocument $dom, $tagname)
110
    {
111
        if (self::existsSignature($dom)) {
112
            self::digestCheck($dom, $tagname);
113
            self::signatureCheck($dom);
114
        }
115
        return true;
116
    }
117
    
118
    /**
119
     * Method that provides the signature of xml as standard SEFAZ
120
     * @param Certificate $certificate
121
     * @param \DOMDocument $dom
122
     * @param \DOMElement $root xml root
123
     * @param \DOMElement $node node to be signed
124
     * @param string $mark Marker signed attribute
125
     * @param int $algorithm cryptographic algorithm
126
     * @param array $canonical parameters to format node for signature
127
     * @return \DOMDocument
128
     */
129 1
    private static function createSignature(
130
        Certificate $certificate,
131
        DOMDocument $dom,
132
        DOMElement $root,
133
        DOMElement $node,
134
        $mark,
135
        $algorithm = OPENSSL_ALGO_SHA1,
136
        $canonical = [false,false,null,null]
137
    ) {
138 1
        $nsDSIG = 'http://www.w3.org/2000/09/xmldsig#';
139 1
        $nsCannonMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
140 1
        $nsSignatureMethod = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
141 1
        $nsDigestMethod = 'http://www.w3.org/2000/09/xmldsig#sha1';
142 1
        $digestAlgorithm = 'sha1';
143 1
        if ($algorithm == OPENSSL_ALGO_SHA256) {
144
            $digestAlgorithm = 'sha256';
145
            $nsSignatureMethod = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
146
            $nsDigestMethod = 'http://www.w3.org/2001/04/xmlenc#sha256';
147
        }
148 1
        $nsTransformMethod1 ='http://www.w3.org/2000/09/xmldsig#enveloped-signature';
149 1
        $nsTransformMethod2 = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
150 1
        $idSigned = trim($node->getAttribute($mark));
151 1
        $digestValue = self::makeDigest($node, $digestAlgorithm, $canonical);
152 1
        $signatureNode = $dom->createElementNS($nsDSIG, 'Signature');
153 1
        $root->appendChild($signatureNode);
154 1
        $signedInfoNode = $dom->createElement('SignedInfo');
155 1
        $signatureNode->appendChild($signedInfoNode);
156 1
        $canonicalNode = $dom->createElement('CanonicalizationMethod');
157 1
        $signedInfoNode->appendChild($canonicalNode);
158 1
        $canonicalNode->setAttribute('Algorithm', $nsCannonMethod);
159 1
        $signatureMethodNode = $dom->createElement('SignatureMethod');
160 1
        $signedInfoNode->appendChild($signatureMethodNode);
161 1
        $signatureMethodNode->setAttribute('Algorithm', $nsSignatureMethod);
162 1
        $referenceNode = $dom->createElement('Reference');
163 1
        $signedInfoNode->appendChild($referenceNode);
164 1
        if (!empty($idSigned)) {
165 1
            $idSigned = "#$idSigned";
166
        }
167 1
        $referenceNode->setAttribute('URI', $idSigned);
168 1
        $transformsNode = $dom->createElement('Transforms');
169 1
        $referenceNode->appendChild($transformsNode);
170 1
        $transfNode1 = $dom->createElement('Transform');
171 1
        $transformsNode->appendChild($transfNode1);
172 1
        $transfNode1->setAttribute('Algorithm', $nsTransformMethod1);
173 1
        $transfNode2 = $dom->createElement('Transform');
174 1
        $transformsNode->appendChild($transfNode2);
175 1
        $transfNode2->setAttribute('Algorithm', $nsTransformMethod2);
176 1
        $digestMethodNode = $dom->createElement('DigestMethod');
177 1
        $referenceNode->appendChild($digestMethodNode);
178 1
        $digestMethodNode->setAttribute('Algorithm', $nsDigestMethod);
179 1
        $digestValueNode = $dom->createElement('DigestValue', $digestValue);
180 1
        $referenceNode->appendChild($digestValueNode);
181 1
        $c14n = $signedInfoNode->C14N(
182 1
            $canonical[0],
183 1
            $canonical[1],
184 1
            $canonical[2],
185 1
            $canonical[3]
186
        );
187 1
        $signature = $certificate->sign($c14n, $algorithm);
188 1
        $signatureValue = base64_encode($signature);
189 1
        $signatureValueNode = $dom->createElement('SignatureValue', $signatureValue);
190 1
        $signatureNode->appendChild($signatureValueNode);
191 1
        $keyInfoNode = $dom->createElement('KeyInfo');
192 1
        $signatureNode->appendChild($keyInfoNode);
193 1
        $x509DataNode = $dom->createElement('X509Data');
194 1
        $keyInfoNode->appendChild($x509DataNode);
195 1
        $pubKeyClean = $certificate->publicKey->unFormated();
196 1
        $x509CertificateNode = $dom->createElement('X509Certificate', $pubKeyClean);
197 1
        $x509DataNode->appendChild($x509CertificateNode);
198 1
        return $dom;
199
    }
200
201
    /**
202
     * Check if Signature tag already exists
203
     * @param \DOMDocument $dom
204
     * @return boolean
205
     */
206 1
    private static function existsSignature(DOMDocument $dom)
207
    {
208 1
        $signature = $dom->getElementsByTagName('Signature')->item(0);
209 1
        if (!isset($signature)) {
210 1
            return false;
211
        }
212
        return true;
213
    }
214
    
215
    /**
216
     * Verify signature value
217
     * @param \DOMDocument $dom
218
     * @return boolean
219
     */
220
    private static function signatureCheck(DOMDocument $dom)
221
    {
222
        $signature = $dom->getElementsByTagName('Signature')->item(0);
223
        $sigMethAlgo = $signature->getElementsByTagName('SignatureMethod')->item(0)->getAttribute('Algorithm');
224
        if ($sigMethAlgo == 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
225
            $algorithm = OPENSSL_ALGO_SHA1;
226
        } else {
227
            $algorithm = OPENSSL_ALGO_SHA256;
228
        }
229
        $certificateContent = $signature->getElementsByTagName('X509Certificate')->item(0)->nodeValue;
230
        $publicKey = PublicKey::createFromContent($certificateContent);
231
        $signContent = $signature->getElementsByTagName('SignedInfo')->item(0)->C14N(true, false, null, null);
232
        $signatureValue = $signature->getElementsByTagName('SignatureValue')->item(0)->nodeValue;
233
        $decodedSignature = base64_decode(str_replace(array("\r", "\n"), '', $signatureValue));
234
        return $publicKey->verify($signContent, $decodedSignature, $algorithm);
235
    }
236
    
237
    /**
238
     * digestCheck
239
     * Verify digest value
240
     * @param string $content
0 ignored issues
show
Bug introduced by
There is no parameter named $content. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
241
     * @param string $tagid
0 ignored issues
show
Bug introduced by
There is no parameter named $tagid. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
242
     * @return boolean
243
     * @throws \NFePHP\Common\Exception\SignerException
244
     */
245
    private static function digestCheck(DOMDocument $dom, $tagname = '')
246
    {
247
        $root = $dom->documentElement;
0 ignored issues
show
Unused Code introduced by
$root is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
248
        $node = $dom->getElementsByTagName($tagname)->item(0);
249
        if (empty($node)) {
250
            throw SignnerException::tagNotFound($tagname);
251
        }
252
        $signature = $dom->getElementsByTagName('Signature')->item(0);
253
        if (! empty($signature)) {
254
            $clone = $signature->cloneNode(true);
0 ignored issues
show
Unused Code introduced by
$clone is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
255
        } else {
256
            $signature = $dom->getElementsByTagName('Signature')->item(0);
257
        }
258
        $sigMethAlgo = $signature->getElementsByTagName('SignatureMethod')->item(0)->getAttribute('Algorithm');
259
        $algorithm = 'sha256';
260
        if ($sigMethAlgo == 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
261
            $algorithm = 'sha1';
262
        }
263
        $sigURI = $signature->getElementsByTagName('Reference')->item(0)->getAttribute('URI');
264
        if ($sigURI == '') {
265
            $node->removeChild($signature);
266
        }
267
        $calculatedDigest = self::makeDigest($node, $algorithm);
0 ignored issues
show
Compatibility introduced by
$node of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
268
        $informedDigest = $signature->getElementsByTagName('DigestValue')->item(0)->nodeValue;
269
        if ($calculatedDigest != $informedDigest) {
270
            throw SignerException::digestComparisonFailed();
271
        }
272
        return true;
273
    }
274
    
275
    /**
276
     * Calculate digest value for given node
277
     * @param \DOMElement $node
278
     * @param string $algorithm
279
     * @param array $canonical
280
     * @return string
281
     */
282 1
    private static function makeDigest(DOMElement $node, $algorithm, $canonical = [false,false,null,null])
283
    {
284 1
        $dados = $node->C14N(true, false, null, null);
0 ignored issues
show
Unused Code introduced by
$dados is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
285
        //calcular o hash dos dados
286 1
        $c14n = $node->C14N(
287 1
            $canonical[0],
288 1
            $canonical[1],
289 1
            $canonical[2],
290 1
            $canonical[3]
291
        );
292 1
        $hashValue = hash($algorithm, $c14n, true);
293 1
        return base64_encode($hashValue);
294
    }
295
}
296