SecurityHelper   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
wmc 24
lcom 2
cbo 4
dl 0
loc 150
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A convertToCertificate() 0 6 1
A cleanCertificate() 0 8 2
A cleanCertificateWhiteSpace() 0 4 1
C getPemAlgorithm() 0 43 16
A decryptAssertion() 0 35 4
1
<?php
2
3
namespace flipbox\saml\core\helpers;
4
5
use flipbox\saml\core\AbstractPlugin;
6
use RobRichards\XMLSecLibs\XMLSecurityKey;
7
use SAML2\EncryptedAssertion;
8
use SAML2\Utilities\Certificate;
9
10
class SecurityHelper
11
{
12
13
    const XMLDSIG_DIGEST_MD5 = 'http://www.w3.org/2001/04/xmldsig-more#md5';
14
15
    private static $typeMap = [
16
        'RSA-SHA1' => XMLSecurityKey::RSA_SHA1,
17
        'RSA-SHA256' => XMLSecurityKey::RSA_SHA256,
18
        'RSA-SHA384' => XMLSecurityKey::RSA_SHA384,
19
        'RSA-SHA512' => XMLSecurityKey::RSA_SHA512,
20
    ];
21
22
    public static $validEncryptionMethods = [
23
        XMLSecurityKey::TRIPLEDES_CBC,
24
        // Prefered
25
        XMLSecurityKey::AES128_CBC,
26
        XMLSecurityKey::AES192_CBC,
27
        XMLSecurityKey::AES256_CBC,
28
29
        XMLSecurityKey::RSA_1_5,
30
        XMLSecurityKey::RSA_SHA1,
31
        XMLSecurityKey::RSA_OAEP_MGF1P,
32
    ];
33
34
    /**
35
     * @param string $certificate
36
     * @return string
37
     */
38
    public static function convertToCertificate(string $certificate)
39
    {
40
        return Certificate::convertToCertificate(
41
            static::cleanCertificate($certificate)
0 ignored issues
show
Bug introduced by
It seems like static::cleanCertificate($certificate) targeting flipbox\saml\core\helper...per::cleanCertificate() can also be of type array<integer,string> or null; however, SAML2\Utilities\Certific...:convertToCertificate() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
42
        );
43
    }
44
45
    /**
46
     * @param string $certificate
47
     * @return string|string[]|null
48
     */
49
    public static function cleanCertificate(string $certificate)
50
    {
51
        if (false == preg_match(Certificate::CERTIFICATE_PATTERN, $certificate, $matches)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match(\SAML2\Utilit...$certificate, $matches) of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
52
            throw new \InvalidArgumentException('Invalid PEM encoded certificate');
53
        }
54
55
        return static::cleanCertificateWhiteSpace($matches[1]);
56
    }
57
58
    /**
59
     * @param string $certificate
60
     * @return string|string[]|null
61
     */
62
    public static function cleanCertificateWhiteSpace(string $certificate)
63
    {
64
        return preg_replace('/\s+/', '', $certificate);
65
    }
66
67
    /**
68
     * @param $pem
69
     * @return mixed|string|null
70
     * @throws \Exception
71
     * Thank you lightsaml/lightsaml
72
     */
73
    public static function getPemAlgorithm($pem)
74
    {
75
        $res = openssl_x509_read($pem);
76
        $info = openssl_x509_parse($res);
77
        $signatureAlgorithm = null;
78
        $signatureType = isset($info['signatureTypeSN']) ? $info['signatureTypeSN'] : '';
79
        if ($signatureType && isset(self::$typeMap[$signatureType])) {
80
            $signatureAlgorithm = self::$typeMap[$signatureType];
81
        } else {
82
            openssl_x509_export($res, $out, false);
83
            if (preg_match('/^\s+Signature Algorithm:\s*(.*)\s*$/m', $out, $match)) {
84
                switch ($match[1]) {
85
                    case 'sha1WithRSAEncryption':
86
                    case 'sha1WithRSA':
87
                        $signatureAlgorithm = XMLSecurityKey::RSA_SHA1;
88
                        break;
89
                    case 'sha256WithRSAEncryption':
90
                    case 'sha256WithRSA':
91
                        $signatureAlgorithm = XMLSecurityKey::RSA_SHA256;
92
                        break;
93
                    case 'sha384WithRSAEncryption':
94
                    case 'sha384WithRSA':
95
                        $signatureAlgorithm = XMLSecurityKey::RSA_SHA384;
96
                        break;
97
                    case 'sha512WithRSAEncryption':
98
                    case 'sha512WithRSA':
99
                        $signatureAlgorithm = XMLSecurityKey::RSA_SHA512;
100
                        break;
101
                    case 'md5WithRSAEncryption':
102
                    case 'md5WithRSA':
103
                        $signatureAlgorithm = static::XMLDSIG_DIGEST_MD5;
104
                        break;
105
                    default:
106
                }
107
            }
108
        }
109
110
        if (! $signatureAlgorithm) {
111
            throw new \Exception('Unrecognized signature algorithm');
112
        }
113
114
        return $signatureAlgorithm;
115
    }
116
117
    /**
118
     * @param EncryptedAssertion $encryptedAssertion
119
     * @param $pemString
120
     * @param array $blacklist
121
     * @return \SAML2\Assertion
122
     * @throws \Exception
123
     */
124
    public static function decryptAssertion(EncryptedAssertion $encryptedAssertion, $pemString, array $blacklist = [])
125
    {
126
127
        $lastException = null;
128
        foreach (static::$validEncryptionMethods as $method) {
129
            \Craft::debug('Trying method: '. $method);
130
            if (in_array($method, $blacklist)) {
131
                \Craft::debug('Decryption with key #' . $method . ' blacklisted.', AbstractPlugin::SAML_CORE_HANDLE);
132
                continue;
133
            }
134
            $xmlSecurityKey = new XMLSecurityKey($method, [
135
                'type' => 'private',
136
            ]);
137
138
            $xmlSecurityKey->loadKey(
139
                $pemString,
140
                false,
141
                false
142
            );
143
144
            try {
145
                $assertion = $encryptedAssertion->getAssertion(
146
                    $xmlSecurityKey
147
                );
148
                \Craft::debug('Decryption with key #' . $method . ' succeeded.', AbstractPlugin::SAML_CORE_HANDLE);
149
                return $assertion;
150
            } catch (\Exception $e) {
151
                $lastException = $e;
152
                \Craft::debug('Decryption with key #' . $method . ' failed. ', AbstractPlugin::SAML_CORE_HANDLE);
153
            }
154
        }
155
156
        // Finally, throw it
157
        throw $lastException;
158
    }
159
}
160