Passed
Pull Request — master (#2)
by Jaime Pérez
03:26
created

SignableElementTrait   A

Complexity

Total Complexity 5

Size/Duplication

Total Lines 94
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 39
dl 0
loc 94
rs 10
c 3
b 0
f 1
wmc 5

3 Methods

Rating   Name   Duplication   Size   Complexity  
A sign() 0 18 1
A doSign() 0 33 3
A insertBefore() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\XML;
6
7
use DOMElement;
8
use DOMNode;
9
use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm;
10
use SimpleSAML\XMLSecurity\Constants;
11
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
12
use SimpleSAML\XMLSecurity\Utils\Security;
13
use SimpleSAML\XMLSecurity\Utils\XML;
14
use SimpleSAML\XMLSecurity\XML\ds\CanonicalizationMethod;
15
use SimpleSAML\XMLSecurity\XML\ds\DigestMethod;
16
use SimpleSAML\XMLSecurity\XML\ds\DigestValue;
17
use SimpleSAML\XMLSecurity\XML\ds\KeyInfo;
18
use SimpleSAML\XMLSecurity\XML\ds\Reference;
19
use SimpleSAML\XMLSecurity\XML\ds\Signature;
20
use SimpleSAML\XMLSecurity\XML\ds\SignatureMethod;
21
use SimpleSAML\XMLSecurity\XML\ds\SignatureValue;
22
use SimpleSAML\XMLSecurity\XML\ds\SignedInfo;
23
use SimpleSAML\XMLSecurity\XML\ds\Transform;
24
use SimpleSAML\XMLSecurity\XML\ds\Transforms;
25
use Webmozart\Assert\Assert;
26
27
/**
28
 * Trait SignableElementTrait
29
 *
30
 * @package simplesamlphp/xml-security
31
 */
32
trait SignableElementTrait
33
{
34
    /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature|null */
35
    protected ?Signature $signature = null;
36
37
    /** @var string */
38
    private string $c14nAlg = Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS;
39
40
    /** @var KeyInfo|null */
41
    private ?KeyInfo $keyInfo = null;
42
43
    /** @var \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm|null */
44
    private ?SignatureAlgorithm $signer = null;
45
46
47
    /**
48
     * @return string|null
49
     */
50
    abstract public function getId(): ?string;
51
52
53
    /**
54
     */
55
    public function sign(
56
        SignatureAlgorithm $signer,
57
        string $canonicalizationAlg = Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS,
58
        ?KeyInfo $keyInfo = null
59
    ): void {
60
        $this->signer = $signer;
61
        $this->keyInfo = $keyInfo;
62
        Assert::oneOf(
63
            $canonicalizationAlg,
64
            [
65
                Constants::C14N_INCLUSIVE_WITH_COMMENTS,
66
                Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS,
67
                Constants::C14N_EXCLUSIVE_WITH_COMMENTS,
68
                Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS
69
            ],
70
            'Unsupported canonicalization algorithm'
71
        );
72
        $this->c14nAlg = $canonicalizationAlg;
73
    }
74
75
76
    /**
77
     * @param \DOMElement $xml
78
     * @throws \Exception
79
     */
80
    private function doSign(DOMElement $xml): void
81
    {
82
        if ($this->signer === null) {
83
            throw new RuntimeException('Cannot call toSignedXML() without calling sign() first.');
84
        }
85
        $algorithm = $this->signer->getAlgorithmId();
86
        $digest = $this->signer->getDigest();
87
88
        $transforms = new Transforms([
89
            new Transform(Constants::XMLDSIG_ENVELOPED),
90
            new Transform($this->c14nAlg)
91
        ]);
92
93
        $refId = $this->getId();
94
        $reference = new Reference(
95
            new DigestMethod($digest),
96
            new DigestValue(Security::hash($digest, XML::processTransforms($transforms, $xml))),
97
            $transforms,
98
            null,
99
            null,
100
            ($refId !== null) ? '#' . $refId : null
101
        );
102
103
        $signedInfo = new SignedInfo(
104
            new CanonicalizationMethod($this->c14nAlg),
105
            new SignatureMethod($algorithm),
106
            [$reference]
107
        );
108
109
        $signingData = $signedInfo->canonicalize($this->c14nAlg);
110
        $signedData = base64_encode($this->signer->sign($signingData));
0 ignored issues
show
Bug introduced by
It seems like $this->signer->sign($signingData) can also be of type false; however, parameter $string of base64_encode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

110
        $signedData = base64_encode(/** @scrutinizer ignore-type */ $this->signer->sign($signingData));
Loading history...
111
112
        $this->signature = new Signature($signedInfo, new SignatureValue($signedData), $this->keyInfo);
113
    }
114
115
116
    /**
117
     * @param DOMElement $root
118
     * @param DOMNode $node
119
     * @param DOMElement $signature
120
     * @return DOMElement
121
     */
122
    private function insertBefore(DOMElement $root, DOMNode $node, DOMElement $signature): DOMElement
123
    {
124
        $root->removeChild($signature);
125
        return $root->insertBefore($signature, $node);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $root->insertBefore($signature, $node) returns the type DOMNode which includes types incompatible with the type-hinted return DOMElement.
Loading history...
126
    }
127
}
128