-
Notifications
You must be signed in to change notification settings - Fork 1
/
InfoCard.php
497 lines (415 loc) · 17.3 KB
/
InfoCard.php
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* @category Zend
* @package Zend_InfoCard
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: InfoCard.php 24593 2012-01-05 20:35:02Z matthew $
*/
/**
* Zend_InfoCard_Xml_EncryptedData
*/
require_once 'Zend/InfoCard/Xml/EncryptedData.php';
/**
* Zend_InfoCard_Xml_Assertion
*/
require_once 'Zend/InfoCard/Xml/Assertion.php';
/**
* Zend_InfoCard_Cipher
*/
require_once 'Zend/InfoCard/Cipher.php';
/**
* Zend_InfoCard_Xml_Security
*/
require_once 'Zend/InfoCard/Xml/Security.php';
/**
* Zend_InfoCard_Adapter_Interface
*/
require_once 'Zend/InfoCard/Adapter/Interface.php';
/**
* Zend_InfoCard_Claims
*/
require_once 'Zend/InfoCard/Claims.php';
/**
* @category Zend
* @package Zend_InfoCard
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_InfoCard
{
/**
* URI for XML Digital Signature SHA1 Digests
*/
const DIGEST_SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
/**
* An array of certificate pair files and optional passwords for them to search
* when trying to determine which certificate was used to encrypt the transient key
*
* @var Array
*/
protected $_keyPairs;
/**
* The instance to use to decrypt public-key encrypted data
*
* @var Zend_InfoCard_Cipher_Pki_Interface
*/
protected $_pkiCipherObj;
/**
* The instance to use to decrypt symmetric encrypted data
*
* @var Zend_InfoCard_Cipher_Symmetric_Interface
*/
protected $_symCipherObj;
/**
* The InfoCard Adapter to use for callbacks into the application using the component
* such as when storing assertions, etc.
*
* @var Zend_InfoCard_Adapter_Interface
*/
protected $_adapter;
/**
* InfoCard Constructor
*
* @throws Zend_InfoCard_Exception
*/
public function __construct()
{
$this->_keyPairs = array();
if(!extension_loaded('mcrypt')) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Use of the Zend_InfoCard component requires the mcrypt extension to be enabled in PHP");
}
if(!extension_loaded('openssl')) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Use of the Zend_InfoCard component requires the openssl extension to be enabled in PHP");
}
}
/**
* Sets the adapter uesd for callbacks into the application using the component, used
* when doing things such as storing / retrieving assertions, etc.
*
* @param Zend_InfoCard_Adapter_Interface $a The Adapter instance
* @return Zend_InfoCard The instnace
*/
public function setAdapter(Zend_InfoCard_Adapter_Interface $a)
{
$this->_adapter = $a;
return $this;
}
/**
* Retrieves the adapter used for callbacks into the application using the component.
* If no adapter was set then an instance of Zend_InfoCard_Adapter_Default is used
*
* @return Zend_InfoCard_Adapter_Interface The Adapter instance
*/
public function getAdapter()
{
if($this->_adapter === null) {
require_once 'Zend/InfoCard/Adapter/Default.php';
$this->setAdapter(new Zend_InfoCard_Adapter_Default());
}
return $this->_adapter;
}
/**
* Gets the Public Key Cipher object used in this instance
*
* @return Zend_InfoCard_Cipher_Pki_Interface
*/
public function getPkiCipherObject()
{
return $this->_pkiCipherObj;
}
/**
* Sets the Public Key Cipher Object used in this instance
*
* @param Zend_InfoCard_Cipher_Pki_Interface $cipherObj
* @return Zend_InfoCard
*/
public function setPkiCipherObject(Zend_InfoCard_Cipher_Pki_Interface $cipherObj)
{
$this->_pkiCipherObj = $cipherObj;
return $this;
}
/**
* Get the Symmetric Cipher Object used in this instance
*
* @return Zend_InfoCard_Cipher_Symmetric_Interface
*/
public function getSymCipherObject()
{
return $this->_symCipherObj;
}
/**
* Sets the Symmetric Cipher Object used in this instance
*
* @param Zend_InfoCard_Cipher_Symmetric_Interface $cipherObj
* @return Zend_InfoCard
*/
public function setSymCipherObject($cipherObj)
{
$this->_symCipherObj = $cipherObj;
return $this;
}
/**
* Remove a Certificate Pair by Key ID from the search list
*
* @throws Zend_InfoCard_Exception
* @param string $key_id The Certificate Key ID returned from adding the certificate pair
* @return Zend_InfoCard
*/
public function removeCertificatePair($key_id)
{
if(!key_exists($key_id, $this->_keyPairs)) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Attempted to remove unknown key id: $key_id");
}
unset($this->_keyPairs[$key_id]);
return $this;
}
/**
* Add a Certificate Pair to the list of certificates searched by the component
*
* @throws Zend_InfoCard_Exception
* @param string $private_key_file The path to the private key file for the pair
* @param string $public_key_file The path to the certificate / public key for the pair
* @param string $type (optional) The URI for the type of key pair this is (default RSA with OAEP padding)
* @param string $password (optional) The password for the private key file if necessary
* @return string A key ID representing this key pair in the component
*/
public function addCertificatePair($private_key_file, $public_key_file, $type = Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P, $password = null)
{
if(!file_exists($private_key_file) ||
!file_exists($public_key_file)) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Could not locate the public and private certificate pair files: $private_key_file, $public_key_file");
}
if(!is_readable($private_key_file) ||
!is_readable($public_key_file)) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Could not read the public and private certificate pair files (check permissions): $private_key_file, $public_key_file");
}
$key_id = md5($private_key_file.$public_key_file);
if(key_exists($key_id, $this->_keyPairs)) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Attempted to add previously existing certificate pair: $private_key_file, $public_key_file");
}
switch($type) {
case Zend_InfoCard_Cipher::ENC_RSA:
case Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P:
$this->_keyPairs[$key_id] = array('private' => $private_key_file,
'public' => $public_key_file,
'type_uri' => $type);
if($password !== null) {
$this->_keyPairs[$key_id]['password'] = $password;
} else {
$this->_keyPairs[$key_id]['password'] = null;
}
return $key_id;
break;
default:
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Invalid Certificate Pair Type specified: $type");
}
}
/**
* Return a Certificate Pair from a key ID
*
* @throws Zend_InfoCard_Exception
* @param string $key_id The Key ID of the certificate pair in the component
* @return array An array containing the path to the private/public key files,
* the type URI and the password if provided
*/
public function getCertificatePair($key_id)
{
if(key_exists($key_id, $this->_keyPairs)) {
return $this->_keyPairs[$key_id];
}
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Invalid Certificate Pair ID provided: $key_id");
}
/**
* Retrieve the digest of a given public key / certificate using the provided digest
* method
*
* @throws Zend_InfoCard_Exception
* @param string $key_id The certificate key id in the component
* @param string $digestMethod The URI of the digest method to use (default SHA1)
* @return string The digest value in binary format
*/
protected function _getPublicKeyDigest($key_id, $digestMethod = self::DIGEST_SHA1)
{
$certificatePair = $this->getCertificatePair($key_id);
$temp = file($certificatePair['public']);
unset($temp[count($temp)-1]);
unset($temp[0]);
$certificateData = base64_decode(implode("\n", $temp));
switch($digestMethod) {
case self::DIGEST_SHA1:
$digest_retval = sha1($certificateData, true);
break;
default:
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Invalid Digest Type Provided: $digestMethod");
}
return $digest_retval;
}
/**
* Find a certificate pair based on a digest of its public key / certificate file
*
* @param string $digest The digest value of the public key wanted in binary form
* @param string $digestMethod The URI of the digest method used to calculate the digest
* @return mixed The Key ID of the matching certificate pair or false if not found
*/
protected function _findCertifiatePairByDigest($digest, $digestMethod = self::DIGEST_SHA1)
{
foreach($this->_keyPairs as $key_id => $certificate_data) {
$cert_digest = $this->_getPublicKeyDigest($key_id, $digestMethod);
if($cert_digest == $digest) {
return $key_id;
}
}
return false;
}
/**
* Extracts the Signed Token from an EncryptedData block
*
* @throws Zend_InfoCard_Exception
* @param string $strXmlToken The EncryptedData XML block
* @return string The XML of the Signed Token inside of the EncryptedData block
*/
protected function _extractSignedToken($strXmlToken)
{
$encryptedData = Zend_InfoCard_Xml_EncryptedData::getInstance($strXmlToken);
// Determine the Encryption Method used to encrypt the token
switch($encryptedData->getEncryptionMethod()) {
case Zend_InfoCard_Cipher::ENC_AES128CBC:
case Zend_InfoCard_Cipher::ENC_AES256CBC:
break;
default:
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Unknown Encryption Method used in the secure token");
}
// Figure out the Key we are using to decrypt the token
$keyinfo = $encryptedData->getKeyInfo();
if(!($keyinfo instanceof Zend_InfoCard_Xml_KeyInfo_XmlDSig)) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Expected a XML digital signature KeyInfo, but was not found");
}
$encryptedKey = $keyinfo->getEncryptedKey();
switch($encryptedKey->getEncryptionMethod()) {
case Zend_InfoCard_Cipher::ENC_RSA:
case Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P:
break;
default:
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Unknown Key Encryption Method used in secure token");
}
$securityTokenRef = $encryptedKey->getKeyInfo()->getSecurityTokenReference();
$key_id = $this->_findCertifiatePairByDigest($securityTokenRef->getKeyReference());
if(!$key_id) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Unable to find key pair used to encrypt symmetric InfoCard Key");
}
$certificate_pair = $this->getCertificatePair($key_id);
// Santity Check
if($certificate_pair['type_uri'] != $encryptedKey->getEncryptionMethod()) {
require_once 'Zend/InfoCard/Exception.php';
throw new Zend_InfoCard_Exception("Certificate Pair which matches digest is not of same algorithm type as document, check addCertificate()");
}
$PKcipher = Zend_InfoCard_Cipher::getInstanceByURI($encryptedKey->getEncryptionMethod());
$base64DecodeSupportsStrictParam = version_compare(PHP_VERSION, '5.2.0', '>=');
if ($base64DecodeSupportsStrictParam) {
$keyCipherValueBase64Decoded = base64_decode($encryptedKey->getCipherValue(), true);
} else {
$keyCipherValueBase64Decoded = base64_decode($encryptedKey->getCipherValue());
}
$symmetricKey = $PKcipher->decrypt(
$keyCipherValueBase64Decoded,
file_get_contents($certificate_pair['private']),
$certificate_pair['password']
);
$symCipher = Zend_InfoCard_Cipher::getInstanceByURI($encryptedData->getEncryptionMethod());
if ($base64DecodeSupportsStrictParam) {
$dataCipherValueBase64Decoded = base64_decode($encryptedData->getCipherValue(), true);
} else {
$dataCipherValueBase64Decoded = base64_decode($encryptedData->getCipherValue());
}
$signedToken = $symCipher->decrypt($dataCipherValueBase64Decoded, $symmetricKey);
return $signedToken;
}
/**
* Process an input Infomation Card EncryptedData block sent from the client,
* validate it, and return the claims contained within it on success or an error message on error
*
* @param string $strXmlToken The XML token sent to the server from the client
* @return Zend_Infocard_Claims The Claims object containing the claims, or any errors which occurred
*/
public function process($strXmlToken)
{
$retval = new Zend_InfoCard_Claims();
require_once 'Zend/InfoCard/Exception.php';
try {
$signedAssertionsXml = $this->_extractSignedToken($strXmlToken);
} catch(Zend_InfoCard_Exception $e) {
$retval->setError('Failed to extract assertion document');
$retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE);
return $retval;
}
try {
$assertions = Zend_InfoCard_Xml_Assertion::getInstance($signedAssertionsXml);
} catch(Zend_InfoCard_Exception $e) {
$retval->setError('Failure processing assertion document');
$retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE);
return $retval;
}
if(!($assertions instanceof Zend_InfoCard_Xml_Assertion_Interface)) {
throw new Zend_InfoCard_Exception("Invalid Assertion Object returned");
}
if(!($reference_id = Zend_InfoCard_Xml_Security::validateXMLSignature($assertions->asXML()))) {
$retval->setError("Failure Validating the Signature of the assertion document");
$retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
return $retval;
}
// The reference id should be locally scoped as far as I know
if($reference_id[0] == '#') {
$reference_id = substr($reference_id, 1);
} else {
$retval->setError("Reference of document signature does not reference the local document");
$retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
return $retval;
}
// Make sure the signature is in reference to the same document as the assertions
if($reference_id != $assertions->getAssertionID()) {
$retval->setError("Reference of document signature does not reference the local document");
$retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
}
// Validate we haven't seen this before and the conditions are acceptable
$conditions = $this->getAdapter()->retrieveAssertion($assertions->getAssertionURI(), $assertions->getAssertionID());
if($conditions === false) {
$conditions = $assertions->getConditions();
}
if(is_array($condition_error = $assertions->validateConditions($conditions))) {
$retval->setError("Conditions of assertion document are not met: {$condition_error[1]} ({$condition_error[0]})");
$retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
}
$attributes = $assertions->getAttributes();
$retval->setClaims($attributes);
if($retval->getCode() == 0) {
$retval->setCode(Zend_InfoCard_Claims::RESULT_SUCCESS);
}
return $retval;
}
}