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) |
|
|
|
|
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)) { |
|
|
|
|
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
|
|
|
|
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.