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

SignableElementTrait::sign()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
eloc 11
c 2
b 0
f 1
nc 1
nop 3
dl 0
loc 19
rs 9.9
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\XML;
6
7
use DOMElement;
8
use DOMNode;
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm;
11
use SimpleSAML\XMLSecurity\Constants;
12
use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException;
13
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
14
use SimpleSAML\XMLSecurity\Utils\Security;
15
use SimpleSAML\XMLSecurity\Utils\XML;
16
use SimpleSAML\XMLSecurity\XML\ds\CanonicalizationMethod;
17
use SimpleSAML\XMLSecurity\XML\ds\DigestMethod;
18
use SimpleSAML\XMLSecurity\XML\ds\DigestValue;
19
use SimpleSAML\XMLSecurity\XML\ds\KeyInfo;
20
use SimpleSAML\XMLSecurity\XML\ds\Reference;
21
use SimpleSAML\XMLSecurity\XML\ds\Signature;
22
use SimpleSAML\XMLSecurity\XML\ds\SignatureMethod;
23
use SimpleSAML\XMLSecurity\XML\ds\SignatureValue;
24
use SimpleSAML\XMLSecurity\XML\ds\SignedInfo;
25
use SimpleSAML\XMLSecurity\XML\ds\Transform;
26
use SimpleSAML\XMLSecurity\XML\ds\Transforms;
27
28
/**
29
 * Trait SignableElementTrait
30
 *
31
 * @package simplesamlphp/xml-security
32
 */
33
trait SignableElementTrait
34
{
35
    use CanonicalizableElementTrait;
36
37
    /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature|null */
38
    protected ?Signature $signature = null;
39
40
    /** @var string */
41
    private string $c14nAlg = Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS;
42
43
    /** @var \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null */
44
    private ?KeyInfo $keyInfo = null;
45
46
    /** @var \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm|null */
47
    private ?SignatureAlgorithm $signer = null;
48
49
50
    /**
51
     * Get the ID of this element.
52
     *
53
     * When this method returns null, the signature created for this object will reference the entire document.
54
     *
55
     * @return string|null The ID of this element, or null if we don't have one.
56
     */
57
    abstract public function getId(): ?string;
58
59
60
    /**
61
     * Sign the current element.
62
     *
63
     * @note The signature will not be applied until toSignedXML() is called.
64
     *
65
     * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm $signer The actual signer implementation to use.
66
     * @param string $canonicalizationAlg The identifier of the canonicalization algorithm to use.
67
     * @param \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null $keyInfo A KeyInfo object to add to the signature.
68
     */
69
    public function sign(
70
        SignatureAlgorithm $signer,
71
        string $canonicalizationAlg = Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS,
72
        ?KeyInfo $keyInfo = null
73
    ): void {
74
        $this->signer = $signer;
75
        $this->keyInfo = $keyInfo;
76
        Assert::oneOf(
77
            $canonicalizationAlg,
78
            [
79
                Constants::C14N_INCLUSIVE_WITH_COMMENTS,
80
                Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS,
81
                Constants::C14N_EXCLUSIVE_WITH_COMMENTS,
82
                Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS
83
            ],
84
            'Unsupported canonicalization algorithm',
85
            InvalidArgumentException::class
86
        );
87
        $this->c14nAlg = $canonicalizationAlg;
88
    }
89
90
91
    /**
92
     * @param \DOMElement $xml
93
     * @throws \Exception
94
     */
95
    private function doSign(DOMElement $xml): void
96
    {
97
        Assert::notNull(
98
            $this->signer,
99
            'Cannot call toSignedXML() without calling sign() first.',
100
            RuntimeException::class
101
        );
102
103
        $algorithm = $this->signer->getAlgorithmId();
0 ignored issues
show
Bug introduced by
The method getAlgorithmId() does not exist on null. ( Ignorable by Annotation )

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

103
        /** @scrutinizer ignore-call */ 
104
        $algorithm = $this->signer->getAlgorithmId();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
104
        $digest = $this->signer->getDigest();
105
106
        $transforms = new Transforms([
107
            new Transform(Constants::XMLDSIG_ENVELOPED),
108
            new Transform($this->c14nAlg)
109
        ]);
110
111
        $refId = $this->getId();
112
        $reference = new Reference(
113
            new DigestMethod($digest),
114
            new DigestValue(Security::hash($digest, XML::processTransforms($transforms, $xml))),
115
            $transforms,
116
            null,
117
            null,
118
            ($refId !== null) ? '#' . $refId : null
119
        );
120
121
        $signedInfo = new SignedInfo(
122
            new CanonicalizationMethod($this->c14nAlg),
123
            new SignatureMethod($algorithm),
124
            [$reference]
125
        );
126
127
        $signingData = $signedInfo->canonicalize($this->c14nAlg);
128
        $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

128
        $signedData = base64_encode(/** @scrutinizer ignore-type */ $this->signer->sign($signingData));
Loading history...
129
130
        $this->signature = new Signature($signedInfo, new SignatureValue($signedData), $this->keyInfo);
131
    }
132
133
134
    /**
135
     * @param DOMElement $root
136
     * @param DOMNode $node
137
     * @param DOMElement $signature
138
     * @return DOMElement
139
     */
140
    private function insertBefore(DOMElement $root, DOMNode $node, DOMElement $signature): DOMElement
141
    {
142
        $root->removeChild($signature);
143
        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...
144
    }
145
}
146