Subversion Repositories oidplus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
868 daniel-mar 1
<?php
2
namespace RobRichards\XMLSecLibs;
3
 
4
use DOMElement;
5
use Exception;
6
 
7
/**
8
 * xmlseclibs.php
9
 *
10
 * Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>.
11
 * All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions
15
 * are met:
16
 *
17
 *   * Redistributions of source code must retain the above copyright
18
 *     notice, this list of conditions and the following disclaimer.
19
 *
20
 *   * Redistributions in binary form must reproduce the above copyright
21
 *     notice, this list of conditions and the following disclaimer in
22
 *     the documentation and/or other materials provided with the
23
 *     distribution.
24
 *
25
 *   * Neither the name of Robert Richards nor the names of his
26
 *     contributors may be used to endorse or promote products derived
27
 *     from this software without specific prior written permission.
28
 *
29
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
32
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
33
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
35
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
36
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
37
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
39
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
40
 * POSSIBILITY OF SUCH DAMAGE.
41
 *
42
 * @author    Robert Richards <rrichards@cdatazone.org>
43
 * @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org>
44
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
45
 */
46
 
47
class XMLSecurityKey
48
{
49
    const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
50
    const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
51
    const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
52
    const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
53
    const AES128_GCM = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
54
    const AES192_GCM = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
55
    const AES256_GCM = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
56
    const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
57
    const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
58
    const RSA_OAEP = 'http://www.w3.org/2009/xmlenc11#rsa-oaep';
59
    const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
60
    const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
61
    const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
62
    const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
63
    const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
64
    const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
65
    const AUTHTAG_LENGTH = 16;
66
 
67
    /** @var array */
68
    private $cryptParams = array();
69
 
70
    /** @var int|string */
71
    public $type = 0;
72
 
73
    /** @var mixed|null */
74
    public $key = null;
75
 
76
    /** @var string  */
77
    public $passphrase = "";
78
 
79
    /** @var string|null */
80
    public $iv = null;
81
 
82
    /** @var string|null */
83
    public $name = null;
84
 
85
    /** @var mixed|null */
86
    public $keyChain = null;
87
 
88
    /** @var bool */
89
    public $isEncrypted = false;
90
 
91
    /** @var XMLSecEnc|null */
92
    public $encryptedCtx = null;
93
 
94
    /** @var mixed|null */
95
    public $guid = null;
96
 
97
    /**
98
     * This variable contains the certificate as a string if this key represents an X509-certificate.
99
     * If this key doesn't represent a certificate, this will be null.
100
     * @var string|null
101
     */
102
    private $x509Certificate = null;
103
 
104
    /**
105
     * This variable contains the certificate thumbprint if we have loaded an X509-certificate.
106
     * @var string|null
107
     */
108
    private $X509Thumbprint = null;
109
 
110
    /**
111
     * @param string $type
112
     * @param null|array $params
113
     * @throws Exception
114
     */
115
    public function __construct($type, $params=null)
116
    {
117
        switch ($type) {
118
            case (self::TRIPLEDES_CBC):
119
                $this->cryptParams['library'] = 'openssl';
120
                $this->cryptParams['cipher'] = 'des-ede3-cbc';
121
                $this->cryptParams['type'] = 'symmetric';
122
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
123
                $this->cryptParams['keysize'] = 24;
124
                $this->cryptParams['blocksize'] = 8;
125
                break;
126
            case (self::AES128_CBC):
127
                $this->cryptParams['library'] = 'openssl';
128
                $this->cryptParams['cipher'] = 'aes-128-cbc';
129
                $this->cryptParams['type'] = 'symmetric';
130
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
131
                $this->cryptParams['keysize'] = 16;
132
                $this->cryptParams['blocksize'] = 16;
133
                break;
134
            case (self::AES192_CBC):
135
                $this->cryptParams['library'] = 'openssl';
136
                $this->cryptParams['cipher'] = 'aes-192-cbc';
137
                $this->cryptParams['type'] = 'symmetric';
138
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
139
                $this->cryptParams['keysize'] = 24;
140
                $this->cryptParams['blocksize'] = 16;
141
                break;
142
            case (self::AES256_CBC):
143
                $this->cryptParams['library'] = 'openssl';
144
                $this->cryptParams['cipher'] = 'aes-256-cbc';
145
                $this->cryptParams['type'] = 'symmetric';
146
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
147
                $this->cryptParams['keysize'] = 32;
148
                $this->cryptParams['blocksize'] = 16;
149
                break;
150
            case (self::AES128_GCM):
151
                $this->cryptParams['library'] = 'openssl';
152
                $this->cryptParams['cipher'] = 'aes-128-gcm';
153
                $this->cryptParams['type'] = 'symmetric';
154
                $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
155
                $this->cryptParams['keysize'] = 16;
156
                $this->cryptParams['blocksize'] = 16;
157
                break;
158
            case (self::AES192_GCM):
159
                $this->cryptParams['library'] = 'openssl';
160
                $this->cryptParams['cipher'] = 'aes-192-gcm';
161
                $this->cryptParams['type'] = 'symmetric';
162
                $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
163
                $this->cryptParams['keysize'] = 24;
164
                $this->cryptParams['blocksize'] = 16;
165
                break;
166
            case (self::AES256_GCM):
167
                $this->cryptParams['library'] = 'openssl';
168
                $this->cryptParams['cipher'] = 'aes-256-gcm';
169
                $this->cryptParams['type'] = 'symmetric';
170
                $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
171
                $this->cryptParams['keysize'] = 32;
172
                $this->cryptParams['blocksize'] = 16;
173
                break;
174
            case (self::RSA_1_5):
175
                $this->cryptParams['library'] = 'openssl';
176
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
177
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
178
                if (is_array($params) && ! empty($params['type'])) {
179
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
180
                        $this->cryptParams['type'] = $params['type'];
181
                        break;
182
                    }
183
                }
184
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
185
            case (self::RSA_OAEP_MGF1P):
186
                $this->cryptParams['library'] = 'openssl';
187
                $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
188
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
189
                $this->cryptParams['hash'] = null;
190
                if (is_array($params) && ! empty($params['type'])) {
191
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
192
                        $this->cryptParams['type'] = $params['type'];
193
                        break;
194
                    }
195
                }
196
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
197
            case (self::RSA_OAEP):
198
                $this->cryptParams['library'] = 'openssl';
199
                $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
200
                $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#rsa-oaep';
201
                $this->cryptParams['hash'] = 'http://www.w3.org/2009/xmlenc11#mgf1sha1';
202
                if (is_array($params) && ! empty($params['type'])) {
203
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
204
                        $this->cryptParams['type'] = $params['type'];
205
                        break;
206
                    }
207
                }
208
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
209
            case (self::RSA_SHA1):
210
                $this->cryptParams['library'] = 'openssl';
211
                $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
212
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
213
                if (is_array($params) && ! empty($params['type'])) {
214
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
215
                        $this->cryptParams['type'] = $params['type'];
216
                        break;
217
                    }
218
                }
219
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
220
            case (self::RSA_SHA256):
221
                $this->cryptParams['library'] = 'openssl';
222
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
223
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
224
                $this->cryptParams['digest'] = 'SHA256';
225
                if (is_array($params) && ! empty($params['type'])) {
226
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
227
                        $this->cryptParams['type'] = $params['type'];
228
                        break;
229
                    }
230
                }
231
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
232
            case (self::RSA_SHA384):
233
                $this->cryptParams['library'] = 'openssl';
234
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
235
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
236
                $this->cryptParams['digest'] = 'SHA384';
237
                if (is_array($params) && ! empty($params['type'])) {
238
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
239
                        $this->cryptParams['type'] = $params['type'];
240
                        break;
241
                    }
242
                }
243
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
244
            case (self::RSA_SHA512):
245
                $this->cryptParams['library'] = 'openssl';
246
                $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
247
                $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
248
                $this->cryptParams['digest'] = 'SHA512';
249
                if (is_array($params) && ! empty($params['type'])) {
250
                    if ($params['type'] == 'public' || $params['type'] == 'private') {
251
                        $this->cryptParams['type'] = $params['type'];
252
                        break;
253
                    }
254
                }
255
                throw new Exception('Certificate "type" (private/public) must be passed via parameters');
256
            case (self::HMAC_SHA1):
257
                $this->cryptParams['library'] = $type;
258
                $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
259
                break;
260
            default:
261
                throw new Exception('Invalid Key Type');
262
        }
263
        $this->type = $type;
264
    }
265
 
266
    /**
267
     * Retrieve the key size for the symmetric encryption algorithm..
268
     *
269
     * If the key size is unknown, or this isn't a symmetric encryption algorithm,
270
     * null is returned.
271
     *
272
     * @return int|null  The number of bytes in the key.
273
     */
274
    public function getSymmetricKeySize()
275
    {
276
        if (! isset($this->cryptParams['keysize'])) {
277
            return null;
278
        }
279
        return $this->cryptParams['keysize'];
280
    }
281
 
282
    /**
283
     * Generates a session key using the openssl-extension.
284
     * In case of using DES3-CBC the key is checked for a proper parity bits set.
285
     * @return string
286
     * @throws Exception
287
     */
288
    public function generateSessionKey()
289
    {
290
        if (!isset($this->cryptParams['keysize'])) {
291
            throw new Exception('Unknown key size for type "' . $this->type . '".');
292
        }
293
        $keysize = $this->cryptParams['keysize'];
294
 
295
        $key = openssl_random_pseudo_bytes($keysize);
296
 
297
        if ($this->type === self::TRIPLEDES_CBC) {
298
            /* Make sure that the generated key has the proper parity bits set.
299
             * Mcrypt doesn't care about the parity bits, but others may care.
300
            */
301
            for ($i = 0; $i < strlen($key); $i++) {
302
                $byte = ord($key[$i]) & 0xfe;
303
                $parity = 1;
304
                for ($j = 1; $j < 8; $j++) {
305
                    $parity ^= ($byte >> $j) & 1;
306
                }
307
                $byte |= $parity;
308
                $key[$i] = chr($byte);
309
            }
310
        }
311
 
312
        $this->key = $key;
313
        return $key;
314
    }
315
 
316
    /**
317
     * Get the raw thumbprint of a certificate
318
     *
319
     * @param string $cert
320
     * @return null|string
321
     */
322
    public static function getRawThumbprint($cert)
323
    {
324
 
325
        $arCert = explode("\n", $cert);
326
        $data = '';
327
        $inData = false;
328
 
329
        foreach ($arCert AS $curData) {
330
            if (! $inData) {
331
                if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
332
                    $inData = true;
333
                }
334
            } else {
335
                if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
336
                    break;
337
                }
338
                $data .= trim($curData);
339
            }
340
        }
341
 
342
        if (! empty($data)) {
343
            return strtolower(sha1(base64_decode($data)));
344
        }
345
 
346
        return null;
347
    }
348
 
349
    /**
350
     * Loads the given key, or - with isFile set true - the key from the keyfile.
351
     *
352
     * @param string $key
353
     * @param bool $isFile
354
     * @param bool $isCert
355
     * @throws Exception
356
     */
357
    public function loadKey($key, $isFile=false, $isCert = false)
358
    {
359
        if ($isFile) {
360
            $this->key = file_get_contents($key);
361
        } else {
362
            $this->key = $key;
363
        }
364
        if ($isCert) {
365
            $this->key = openssl_x509_read($this->key);
366
            openssl_x509_export($this->key, $str_cert);
367
            $this->x509Certificate = $str_cert;
368
            $this->key = $str_cert;
369
        } else {
370
            $this->x509Certificate = null;
371
        }
372
        if ($this->cryptParams['library'] == 'openssl') {
373
            switch ($this->cryptParams['type']) {
374
                case 'public':
375
                        if ($isCert) {
376
                            /* Load the thumbprint if this is an X509 certificate. */
377
                            $this->X509Thumbprint = self::getRawThumbprint($this->key);
378
                        }
379
                        $this->key = openssl_get_publickey($this->key);
380
                        if (! $this->key) {
381
                            throw new Exception('Unable to extract public key');
382
                        }
383
                        break;
384
 
385
                    case 'private':
386
                    $this->key = openssl_get_privatekey($this->key, $this->passphrase);
387
                    break;
388
 
389
                case'symmetric':
390
                    if (strlen($this->key) < $this->cryptParams['keysize']) {
391
                        throw new Exception('Key must contain at least '.$this->cryptParams['keysize'].' characters for this cipher, contains '.strlen($this->key));
392
                    }
393
                    break;
394
 
395
                default:
396
                    throw new Exception('Unknown type');
397
            }
398
        }
399
    }
400
 
401
    /**
402
     * ISO 10126 Padding
403
     *
404
     * @param string $data
405
     * @param integer $blockSize
406
     * @throws Exception
407
     * @return string
408
     */
409
    private function padISO10126($data, $blockSize)
410
    {
411
        if ($blockSize > 256) {
412
            throw new Exception('Block size higher than 256 not allowed');
413
        }
414
        $padChr = $blockSize - (strlen($data) % $blockSize);
415
        $pattern = chr($padChr);
416
        return $data . str_repeat($pattern, $padChr);
417
    }
418
 
419
    /**
420
     * Remove ISO 10126 Padding
421
     *
422
     * @param string $data
423
     * @return string
424
     */
425
    private function unpadISO10126($data)
426
    {
427
        $padChr = substr($data, -1);
428
        $padLen = ord($padChr);
429
        return substr($data, 0, -$padLen);
430
    }
431
 
432
    /**
433
     * Encrypts the given data (string) using the openssl-extension
434
     *
435
     * @param string $data
436
     * @return string
437
     */
438
    private function encryptSymmetric($data)
439
    {
440
        $this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher']));
441
        $authTag = null;
442
        if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
443
            if (version_compare(PHP_VERSION, '7.1.0') < 0) {
444
                throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
445
            }
446
            $authTag = openssl_random_pseudo_bytes(self::AUTHTAG_LENGTH);
447
            $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
448
        } else {
449
            $data = $this->padISO10126($data, $this->cryptParams['blocksize']);
450
            $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
451
        }
452
 
453
        if (false === $encrypted) {
454
            throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string());
455
        }
456
        return $this->iv . $encrypted . $authTag;
457
    }
458
 
459
    /**
460
     * Decrypts the given data (string) using the openssl-extension
461
     *
462
     * @param string $data
463
     * @return string
464
     */
465
    private function decryptSymmetric($data)
466
    {
467
        $iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']);
468
        $this->iv = substr($data, 0, $iv_length);
469
        $data = substr($data, $iv_length);
470
        $authTag = null;
471
        if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
472
            if (version_compare(PHP_VERSION, '7.1.0') < 0) {
473
                throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
474
            }
475
            // obtain and remove the authentication tag
476
            $offset = 0 - self::AUTHTAG_LENGTH;
477
            $authTag = substr($data, $offset);
478
            $data = substr($data, 0, $offset);
479
            $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
480
        } else {
481
            $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
482
        }
483
 
484
        if (false === $decrypted) {
485
            throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string());
486
        }
487
        return null !== $authTag ? $decrypted : $this->unpadISO10126($decrypted);
488
    }
489
 
490
    /**
491
     * Encrypts the given public data (string) using the openssl-extension
492
     *
493
     * @param string $data
494
     * @return string
495
     * @throws Exception
496
     */
497
    private function encryptPublic($data)
498
    {
499
        if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
500
            throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string());
501
        }
502
        return $encrypted;
503
    }
504
 
505
    /**
506
     * Decrypts the given public data (string) using the openssl-extension
507
     *
508
     * @param string $data
509
     * @return string
510
     * @throws Exception
511
     */
512
    private function decryptPublic($data)
513
    {
514
        if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
515
            throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string());
516
        }
517
        return $decrypted;
518
    }
519
 
520
    /**
521
     * Encrypts the given private data (string) using the openssl-extension
522
     *
523
     * @param string $data
524
     * @return string
525
     * @throws Exception
526
     */
527
    private function encryptPrivate($data)
528
    {
529
        if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
530
            throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string());
531
        }
532
        return $encrypted;
533
    }
534
 
535
    /**
536
     * Decrypts the given private data (string) using the openssl-extension
537
     *
538
     * @param string $data
539
     * @return string
540
     * @throws Exception
541
     */
542
    private function decryptPrivate($data)
543
    {
544
        if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
545
            throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string());
546
        }
547
        return $decrypted;
548
    }
549
 
550
    /**
551
     * Signs the given data (string) using the openssl-extension
552
     *
553
     * @param string $data
554
     * @return string
555
     * @throws Exception
556
     */
557
    private function signOpenSSL($data)
558
    {
559
        $algo = OPENSSL_ALGO_SHA1;
560
        if (! empty($this->cryptParams['digest'])) {
561
            $algo = $this->cryptParams['digest'];
562
        }
563
        if (! openssl_sign($data, $signature, $this->key, $algo)) {
564
            throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
565
        }
566
        return $signature;
567
    }
568
 
569
    /**
570
     * Verifies the given data (string) belonging to the given signature using the openssl-extension
571
     *
572
     * Returns:
573
     *  1 on succesful signature verification,
574
     *  0 when signature verification failed,
575
     *  -1 if an error occurred during processing.
576
     *
577
     * NOTE: be very careful when checking the return value, because in PHP,
578
     * -1 will be cast to True when in boolean context. So always check the
579
     * return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
580
     *
581
     * @param string $data
582
     * @param string $signature
583
     * @return int
584
     */
585
    private function verifyOpenSSL($data, $signature)
586
    {
587
        $algo = OPENSSL_ALGO_SHA1;
588
        if (! empty($this->cryptParams['digest'])) {
589
            $algo = $this->cryptParams['digest'];
590
        }
591
        return openssl_verify($data, $signature, $this->key, $algo);
592
    }
593
 
594
    /**
595
     * Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
596
     *
597
     * @param string $data
598
     * @return mixed|string
599
     */
600
    public function encryptData($data)
601
    {
602
        if ($this->cryptParams['library'] === 'openssl') {
603
            switch ($this->cryptParams['type']) {
604
                case 'symmetric':
605
                    return $this->encryptSymmetric($data);
606
                case 'public':
607
                    return $this->encryptPublic($data);
608
                case 'private':
609
                    return $this->encryptPrivate($data);
610
            }
611
        }
612
    }
613
 
614
    /**
615
     * Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
616
     *
617
     * @param string $data
618
     * @return mixed|string
619
     */
620
    public function decryptData($data)
621
    {
622
        if ($this->cryptParams['library'] === 'openssl') {
623
            switch ($this->cryptParams['type']) {
624
                case 'symmetric':
625
                    return $this->decryptSymmetric($data);
626
                case 'public':
627
                    return $this->decryptPublic($data);
628
                case 'private':
629
                    return $this->decryptPrivate($data);
630
            }
631
        }
632
    }
633
 
634
    /**
635
     * Signs the data (string) using the extension assigned to the type in the constructor.
636
     *
637
     * @param string $data
638
     * @return mixed|string
639
     */
640
    public function signData($data)
641
    {
642
        switch ($this->cryptParams['library']) {
643
            case 'openssl':
644
                return $this->signOpenSSL($data);
645
            case (self::HMAC_SHA1):
646
                return hash_hmac("sha1", $data, $this->key, true);
647
        }
648
    }
649
 
650
    /**
651
     * Verifies the data (string) against the given signature using the extension assigned to the type in the constructor.
652
     *
653
     * Returns in case of openSSL:
654
     *  1 on succesful signature verification,
655
     *  0 when signature verification failed,
656
     *  -1 if an error occurred during processing.
657
     *
658
     * NOTE: be very careful when checking the return value, because in PHP,
659
     * -1 will be cast to True when in boolean context. So always check the
660
     * return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
661
     *
662
     * @param string $data
663
     * @param string $signature
664
     * @return bool|int
665
     */
666
    public function verifySignature($data, $signature)
667
    {
668
        switch ($this->cryptParams['library']) {
669
            case 'openssl':
670
                return $this->verifyOpenSSL($data, $signature);
671
            case (self::HMAC_SHA1):
672
                $expectedSignature = hash_hmac("sha1", $data, $this->key, true);
673
                return strcmp($signature, $expectedSignature) == 0;
674
        }
675
    }
676
 
677
    /**
678
     * @deprecated
679
     * @see getAlgorithm()
680
     * @return mixed
681
     */
682
    public function getAlgorith()
683
    {
684
        return $this->getAlgorithm();
685
    }
686
 
687
    /**
688
     * @return mixed
689
     */
690
    public function getAlgorithm()
691
    {
692
        return $this->cryptParams['method'];
693
    }
694
 
695
    /**
696
     *
697
     * @param int $type
698
     * @param string $string
699
     * @return null|string
700
     */
701
    public static function makeAsnSegment($type, $string)
702
    {
703
        switch ($type) {
704
            case 0x02:
705
                if (ord($string) > 0x7f)
706
                    $string = chr(0).$string;
707
                break;
708
            case 0x03:
709
                $string = chr(0).$string;
710
                break;
711
        }
712
 
713
        $length = strlen($string);
714
 
715
        if ($length < 128) {
716
            $output = sprintf("%c%c%s", $type, $length, $string);
717
        } else if ($length < 0x0100) {
718
            $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
719
        } else if ($length < 0x010000) {
720
            $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
721
        } else {
722
            $output = null;
723
        }
724
        return $output;
725
    }
726
 
727
    /**
728
     *
729
     * Hint: Modulus and Exponent must already be base64 decoded
730
     * @param string $modulus
731
     * @param string $exponent
732
     * @return string
733
     */
734
    public static function convertRSA($modulus, $exponent)
735
    {
736
        /* make an ASN publicKeyInfo */
737
        $exponentEncoding = self::makeAsnSegment(0x02, $exponent);
738
        $modulusEncoding = self::makeAsnSegment(0x02, $modulus);
739
        $sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
740
        $bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding);
741
        $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
742
        $publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
743
 
744
        /* encode the publicKeyInfo in base64 and add PEM brackets */
745
        $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
746
        $encoding = "-----BEGIN PUBLIC KEY-----\n";
747
        $offset = 0;
748
        while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
749
            $encoding = $encoding.$segment."\n";
750
            $offset += 64;
751
        }
752
        return $encoding."-----END PUBLIC KEY-----\n";
753
    }
754
 
755
    /**
756
     * @param mixed $parent
757
     */
758
    public function serializeKey($parent)
759
    {
760
 
761
    }
762
 
763
    /**
764
     * Retrieve the X509 certificate this key represents.
765
     *
766
     * Will return the X509 certificate in PEM-format if this key represents
767
     * an X509 certificate.
768
     *
769
     * @return string The X509 certificate or null if this key doesn't represent an X509-certificate.
770
     */
771
    public function getX509Certificate()
772
    {
773
        return $this->x509Certificate;
774
    }
775
 
776
    /**
777
     * Get the thumbprint of this X509 certificate.
778
     *
779
     * Returns:
780
     *  The thumbprint as a lowercase 40-character hexadecimal number, or null
781
     *  if this isn't a X509 certificate.
782
     *
783
     *  @return string Lowercase 40-character hexadecimal number of thumbprint
784
     */
785
    public function getX509Thumbprint()
786
    {
787
        return $this->X509Thumbprint;
788
    }
789
 
790
 
791
    /**
792
     * Create key from an EncryptedKey-element.
793
     *
794
     * @param DOMElement $element The EncryptedKey-element.
795
     * @throws Exception
796
     *
797
     * @return XMLSecurityKey The new key.
798
     */
799
    public static function fromEncryptedKeyElement(DOMElement $element)
800
    {
801
 
802
        $objenc = new XMLSecEnc();
803
        $objenc->setNode($element);
804
        if (! $objKey = $objenc->locateKey()) {
805
            throw new Exception("Unable to locate algorithm for this Encrypted Key");
806
        }
807
        $objKey->isEncrypted = true;
808
        $objKey->encryptedCtx = $objenc;
809
        XMLSecEnc::staticLocateKeyInfo($objKey, $element);
810
        return $objKey;
811
    }
812
 
813
}