Completed
Push — master ( 2a51dc...dc430d )
by Tim
16s queued 12s
created

SignableElementTrait::sign()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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

164
        /** @scrutinizer ignore-call */ 
165
        $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...
165
        $digest = $this->signer->getDigest();
166
167
        $transforms = new Transforms([
168
            new Transform(C::XMLDSIG_ENVELOPED),
169
            new Transform($this->c14nAlg)
170
        ]);
171
172
        $canonicalDocument = XML::processTransforms($transforms, $xml);
173
174
        $signedInfo = new SignedInfo(
175
            new CanonicalizationMethod($this->c14nAlg),
176
            new SignatureMethod($algorithm),
177
            [$this->getReference($digest, $transforms, $xml, $canonicalDocument)]
178
        );
179
180
        $signingData = $signedInfo->canonicalize($this->c14nAlg);
181
        $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

181
        $signedData = base64_encode(/** @scrutinizer ignore-type */ $this->signer->sign($signingData));
Loading history...
182
183
        $this->signature = new Signature($signedInfo, new SignatureValue($signedData), $this->keyInfo);
184
        return DOMDocumentFactory::fromString($canonicalDocument)->documentElement;
185
    }
186
}
187