1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
|
<?php
/**
* A helper class for signing XML.
*
* This is a helper class for signing XML documents.
*
* @author Olav Morken, UNINETT AS.
* @package SimpleSAMLphp
*/
class SimpleSAML_XML_Signer {
/**
* The name of the ID attribute.
*/
private $idAttrName;
/**
* The private key (as an XMLSecurityKey).
*/
private $privateKey;
/**
* The certificate (as text).
*/
private $certificate;
/**
* Extra certificates which should be included in the response.
*/
private $extraCertificates;
/**
* Constructor for the metadata signer.
*
* You can pass an list of options as key-value pairs in the array. This allows you to initialize
* a metadata signer in one call.
*
* The following keys are recognized:
* - privatekey The file with the private key, relative to the cert-directory.
* - privatekey_pass The passphrase for the private key.
* - certificate The file with the certificate, relative to the cert-directory.
* - privatekey_array The private key, as an array returned from SimpleSAML_Utilities::loadPrivateKey.
* - publickey_array The public key, as an array returned from SimpleSAML_Utilities::loadPublicKey.
* - id The name of the ID attribute.
*
* @param $options Associative array with options for the constructor. Defaults to an empty array.
*/
public function __construct($options = array()) {
assert('is_array($options)');
$this->idAttrName = FALSE;
$this->privateKey = FALSE;
$this->certificate = FALSE;
$this->extraCertificates = array();
if(array_key_exists('privatekey', $options)) {
$pass = NULL;
if(array_key_exists('privatekey_pass', $options)) {
$pass = $options['privatekey_pass'];
}
$this->loadPrivateKey($options['privatekey'], $pass);
}
if(array_key_exists('certificate', $options)) {
$this->loadCertificate($options['certificate']);
}
if (array_key_exists('privatekey_array', $options)) {
$this->loadPrivateKeyArray($options['privatekey_array']);
}
if (array_key_exists('publickey_array', $options)) {
$this->loadPublicKeyArray($options['publickey_array']);
}
if(array_key_exists('id', $options)) {
$this->setIdAttribute($options['id']);
}
}
/**
* Set the private key from an array.
*
* This function loads the private key from an array matching what is returned
* by SimpleSAML_Utilities::loadPrivateKey(...).
*
* @param array $privatekey The private key.
*/
public function loadPrivateKeyArray($privatekey) {
assert('is_array($privatekey)');
assert('array_key_exists("PEM", $privatekey)');
$this->privateKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'private'));
if (array_key_exists('password', $privatekey)) {
$this->privateKey->passphrase = $privatekey['password'];
}
$this->privateKey->loadKey($privatekey['PEM'], FALSE);
}
/**
* Set the private key.
*
* Will throw an exception if unable to load the private key.
*
* @param $file The file which contains the private key. The path is assumed to be relative
* to the cert-directory.
* @param $pass The passphrase on the private key. Pass no value or NULL if the private key is unencrypted.
*/
public function loadPrivateKey($file, $pass = NULL) {
assert('is_string($file)');
assert('is_string($pass) || is_null($pass)');
$keyFile = \SimpleSAML\Utils\Config::getCertPath($file);
if (!file_exists($keyFile)) {
throw new Exception('Could not find private key file "' . $keyFile . '".');
}
$keyData = file_get_contents($keyFile);
if($keyData === FALSE) {
throw new Exception('Unable to read private key file "' . $keyFile . '".');
}
$privatekey = array('PEM' => $keyData);
if($pass !== NULL) {
$privatekey['password'] = $pass;
}
$this->loadPrivateKeyArray($privatekey);
}
/**
* Set the public key / certificate we should include in the signature.
*
* This function loads the public key from an array matching what is returned
* by SimpleSAML_Utilities::loadPublicKey(...).
*
* @param array $publickey The public key.
*/
public function loadPublicKeyArray($publickey) {
assert('is_array($publickey)');
if (!array_key_exists('PEM', $publickey)) {
// We have a public key with only a fingerprint
throw new Exception('Tried to add a certificate fingerprint in a signature.');
}
// For now, we only assume that the public key is an X509 certificate
$this->certificate = $publickey['PEM'];
}
/**
* Set the certificate we should include in the signature.
*
* If this function isn't called, no certificate will be included.
* Will throw an exception if unable to load the certificate.
*
* @param $file The file which contains the certificate. The path is assumed to be relative to
* the cert-directory.
*/
public function loadCertificate($file) {
assert('is_string($file)');
$certFile = \SimpleSAML\Utils\Config::getCertPath($file);
if (!file_exists($certFile)) {
throw new Exception('Could not find certificate file "' . $certFile . '".');
}
$this->certificate = file_get_contents($certFile);
if($this->certificate === FALSE) {
throw new Exception('Unable to read certificate file "' . $certFile . '".');
}
}
/**
* Set the attribute name for the ID value.
*
* @param $idAttrName The name of the attribute which contains the id.
*/
public function setIDAttribute($idAttrName) {
assert('is_string($idAttrName)');
$this->idAttrName = $idAttrName;
}
/**
* Add an extra certificate to the certificate chain in the signature.
*
* Extra certificates will be added to the certificate chain in the order they
* are added.
*
* @param $file The file which contains the certificate, relative to the cert-directory.
*/
public function addCertificate($file) {
assert('is_string($file)');
$certFile = \SimpleSAML\Utils\Config::getCertPath($file);
if (!file_exists($certFile)) {
throw new Exception('Could not find extra certificate file "' . $certFile . '".');
}
$certificate = file_get_contents($certFile);
if($certificate === FALSE) {
throw new Exception('Unable to read extra certificate file "' . $certFile . '".');
}
$this->extraCertificates[] = $certificate;
}
/**
* Signs the given DOMElement and inserts the signature at the given position.
*
* The private key must be set before calling this function.
*
* @param $node The DOMElement we should generate a signature for.
* @param $insertInto The DOMElement we should insert the signature element into.
* @param $insertBefore The element we should insert the signature element before. Defaults to NULL,
* in which case the signature will be appended to the element spesified in
* $insertInto.
*/
public function sign($node, $insertInto, $insertBefore = NULL) {
assert('$node instanceof DOMElement');
assert('$insertInto instanceof DOMElement');
assert('is_null($insertBefore) || $insertBefore instanceof DOMElement ' .
'|| $insertBefore instanceof DOMComment || $insertBefore instanceof DOMText');
if($this->privateKey === FALSE) {
throw new Exception('Private key not set.');
}
$objXMLSecDSig = new XMLSecurityDSig();
$objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
$options = array();
if($this->idAttrName !== FALSE) {
$options['id_name'] = $this->idAttrName;
}
$objXMLSecDSig->addReferenceList(array($node), XMLSecurityDSig::SHA1,
array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N),
$options);
$objXMLSecDSig->sign($this->privateKey);
if($this->certificate !== FALSE) {
// Add the certificate to the signature
$objXMLSecDSig->add509Cert($this->certificate, TRUE);
}
// Add extra certificates
foreach($this->extraCertificates as $certificate) {
$objXMLSecDSig->add509Cert($certificate, TRUE);
}
$objXMLSecDSig->insertSignature($insertInto, $insertBefore);
}
}
|