SignableElementTrait::getReference()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 44
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 29
c 0
b 0
f 0
dl 0
loc 44
rs 9.1448
cc 5
nc 4
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\XML;
6
7
use DOMElement;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\XML\DOMDocumentFactory;
10
use SimpleSAML\XMLSchema\Type\AnyURIValue;
11
use SimpleSAML\XMLSchema\Type\Base64BinaryValue;
12
use SimpleSAML\XMLSchema\Type\IDValue;
13
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmInterface;
14
use SimpleSAML\XMLSecurity\Constants as C;
15
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
16
use SimpleSAML\XMLSecurity\Exception\UnsupportedAlgorithmException;
17
use SimpleSAML\XMLSecurity\Type\DigestValue as DigestValueType;
18
use SimpleSAML\XMLSecurity\Utils\XML;
19
use SimpleSAML\XMLSecurity\XML\ds\CanonicalizationMethod;
20
use SimpleSAML\XMLSecurity\XML\ds\DigestMethod;
21
use SimpleSAML\XMLSecurity\XML\ds\DigestValue;
22
use SimpleSAML\XMLSecurity\XML\ds\KeyInfo;
23
use SimpleSAML\XMLSecurity\XML\ds\Reference;
24
use SimpleSAML\XMLSecurity\XML\ds\Signature;
25
use SimpleSAML\XMLSecurity\XML\ds\SignatureMethod;
26
use SimpleSAML\XMLSecurity\XML\ds\SignatureValue;
27
use SimpleSAML\XMLSecurity\XML\ds\SignedInfo;
28
use SimpleSAML\XMLSecurity\XML\ds\Transform;
29
use SimpleSAML\XMLSecurity\XML\ds\Transforms;
30
31
use function base64_encode;
32
use function hash;
33
use function in_array;
34
35
/**
36
 * Trait SignableElementTrait
37
 *
38
 * @package simplesamlphp/xml-security
39
 * @phpstan-ignore trait.unused
40
 */
41
trait SignableElementTrait
42
{
43
    use CanonicalizableElementTrait;
44
45
46
    /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature|null */
47
    protected ?Signature $signature = null;
48
49
    /** @var string */
50
    private string $c14nAlg = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS;
51
52
    /** @var \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null */
53
    private ?KeyInfo $keyInfo = null;
54
55
    /** @var \SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmInterface|null */
56
    protected ?SignatureAlgorithmInterface $signer = null;
57
58
59
    /**
60
     * Get the ID of this element.
61
     *
62
     * When this method returns null, the signature created for this object will reference the entire document.
63
     *
64
     * @return \SimpleSAML\XML\Type\IDValue|null The ID of this element, or null if we don't have one.
65
     */
66
    abstract public function getId(): ?IDValue;
67
68
69
    /**
70
     * Sign the current element.
71
     *
72
     * The signature will not be applied until toXML() is called.
73
     *
74
     * @param \SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmInterface $signer The actual signer implementation
75
     * to use.
76
     * @param string $canonicalizationAlg The identifier of the canonicalization algorithm to use.
77
     * @param \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null $keyInfo A KeyInfo object to add to the signature.
78
     */
79
    public function sign(
80
        SignatureAlgorithmInterface $signer,
81
        string $canonicalizationAlg = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS,
82
        ?KeyInfo $keyInfo = null,
83
    ): void {
84
        $this->signer = $signer;
85
        $this->keyInfo = $keyInfo;
86
        Assert::oneOf(
87
            $canonicalizationAlg,
88
            [
89
                C::C14N_INCLUSIVE_WITH_COMMENTS,
90
                C::C14N_INCLUSIVE_WITHOUT_COMMENTS,
91
                C::C14N_EXCLUSIVE_WITH_COMMENTS,
92
                C::C14N_EXCLUSIVE_WITHOUT_COMMENTS,
93
            ],
94
            'Unsupported canonicalization algorithm: %s',
95
            UnsupportedAlgorithmException::class,
96
        );
97
        $this->c14nAlg = $canonicalizationAlg;
98
    }
99
100
101
    /**
102
     * Get a ds:Reference pointing to this object.
103
     *
104
     * @param string $digestAlg The digest algorithm to use.
105
     * @param \SimpleSAML\XMLSecurity\XML\ds\Transforms $transforms The transforms to apply to the object.
106
     */
107
    #[\NoDiscard]
108
    private function getReference(
109
        string $digestAlg,
110
        Transforms $transforms,
111
        DOMElement $xml,
112
        string $canonicalDocument,
113
    ): Reference {
114
        $id = $this->getId();
115
        $uri = null;
116
        if (empty($id)) { // document reference
117
            Assert::notNull(
118
                $xml->ownerDocument->documentElement,
119
                'Cannot create a document reference without a root element in the document.',
120
                RuntimeException::class,
121
            );
122
            Assert::true(
123
                $xml->isSameNode($xml->ownerDocument->documentElement),
124
                'Cannot create a document reference when signing an object that is not the root of the document. ' .
125
                'Please give your object an identifier.',
126
                RuntimeException::class,
127
            );
128
            if (in_array($this->c14nAlg, [C::C14N_INCLUSIVE_WITH_COMMENTS, C::C14N_EXCLUSIVE_WITH_COMMENTS])) {
129
                $uri = '#xpointer(/)';
130
            }
131
        } elseif (in_array($this->c14nAlg, [C::C14N_INCLUSIVE_WITH_COMMENTS, C::C14N_EXCLUSIVE_WITH_COMMENTS])) {
132
            // regular reference, but must retain comments
133
            $uri = '#xpointer(id(' . $id . '))';
134
        } else { // regular reference, can ignore comments
135
            $uri = '#' . $id;
136
        }
137
138
        return new Reference(
139
            new DigestMethod(
140
                AnyURIValue::fromString($digestAlg),
141
            ),
142
            new DigestValue(
143
                DigestValueType::fromString(
144
                    base64_encode(hash(C::$DIGEST_ALGORITHMS[$digestAlg], $canonicalDocument, true)),
145
                ),
146
            ),
147
            $transforms,
148
            null,
149
            null,
150
            ($uri !== null) ? AnyURIValue::fromString($uri) : null,
151
        );
152
    }
153
154
155
    /**
156
     * Do the actual signing of the document.
157
     *
158
     * Note that this method does not insert the signature in the returned \DOMElement. The signature will be available
159
     * in $this->signature as a \SimpleSAML\XMLSecurity\XML\ds\Signature object, which can then be converted to XML
160
     * calling toXML() on it, passing the \DOMElement value returned here as a parameter. The resulting \DOMElement
161
     * can then be inserted in the position desired.
162
     *
163
     * E.g.:
164
     *     $xml = // our XML to sign
165
     *     $signedXML = $this->doSign($xml);
166
     *     $signedXML->appendChild($this->signature->toXML($signedXML));
167
     *
168
     * @param \DOMElement $xml The element to sign.
169
     * @return \DOMElement The signed element, without the signature attached to it just yet.
170
     */
171
    #[\NoDiscard]
172
    protected function doSign(DOMElement $xml): DOMElement
173
    {
174
        Assert::notNull(
175
            $this->signer,
176
            'Cannot call toSignedXML() without calling sign() first.',
177
            RuntimeException::class,
178
        );
179
180
        $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

180
        /** @scrutinizer ignore-call */ 
181
        $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...
181
        $digest = $this->signer->getDigest();
182
183
        $transforms = new Transforms([
184
            new Transform(
185
                AnyURIValue::fromString(C::XMLDSIG_ENVELOPED),
186
            ),
187
            new Transform(
188
                AnyURIValue::fromString($this->c14nAlg),
189
            ),
190
        ]);
191
192
        $canonicalDocument = XML::processTransforms($transforms, $xml);
193
194
        $signedInfo = new SignedInfo(
195
            new CanonicalizationMethod(
196
                AnyURIValue::fromString($this->c14nAlg),
197
            ),
198
            new SignatureMethod(
199
                AnyURIValue::fromString($algorithm),
200
            ),
201
            [$this->getReference($digest, $transforms, $xml, $canonicalDocument)],
202
        );
203
204
        $signingData = $signedInfo->canonicalize($this->c14nAlg);
205
        $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

205
        $signedData = base64_encode(/** @scrutinizer ignore-type */ $this->signer->sign($signingData));
Loading history...
206
207
        $this->setSignature(
0 ignored issues
show
Bug introduced by
It seems like setSignature() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

207
        $this->/** @scrutinizer ignore-call */ 
208
               setSignature(
Loading history...
208
            new Signature(
209
                $signedInfo,
210
                new SignatureValue(
211
                    Base64BinaryValue::fromString($signedData),
212
                ),
213
                $this->keyInfo,
214
            ),
215
        );
216
        return DOMDocumentFactory::fromString($canonicalDocument)->documentElement;
217
    }
218
219
220
    /**
221
     * Get the list of algorithms that are blacklisted for any signing operation.
222
     *
223
     * @return string[]|null An array with all algorithm identifiers that are blacklisted, or null to use this
224
     * libraries default.
225
     */
226
    abstract public function getBlacklistedAlgorithms(): ?array;
227
}
228