Skip to content
This repository was archived by the owner on Sep 19, 2022. It is now read-only.

Commit 0a7d127

Browse files
Merge pull request #165 from BaranekD/scripts_improvements
fix: Security improvements in script calls
2 parents 59074f7 + 091289f commit 0a7d127

File tree

6 files changed

+184
-168
lines changed

6 files changed

+184
-168
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
55
#### Changed
66
- Improve WAYF searching by localized name and domain
77
- Implemented filter EnsureVoMember
8+
- Security improvements in script calls
89

910
#### Fixed
1011
- Detailed endpoint format when spaced in EndpointMapToArray
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
$config = array(
4+
/**
5+
* Path to public key
6+
*/
7+
'pubKey' => 'Path_to_public_key',
8+
9+
/**
10+
* Path to private key
11+
*/
12+
'privKey' => 'Path_to_private_key',
13+
14+
/**
15+
* Hash algorithm
16+
*/
17+
'hashAlg' => 'sha256',
18+
19+
/**
20+
* Signature algorith
21+
*/
22+
'sigAlg' => 'RS512',
23+
24+
/**
25+
* Challenge length
26+
*/
27+
'challengeLength' => 64,
28+
);

lib/Auth/Process/UpdateUserExtSource.php

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@
22

33
namespace SimpleSAML\Module\perun\Auth\Process;
44

5-
use Jose\Component\KeyManagement\JWKFactory;
6-
use Jose\Component\Core\AlgorithmManager;
7-
use Jose\Component\Signature\JWSBuilder;
8-
use Jose\Component\Signature\Serializer\CompactSerializer;
9-
use Jose\Component\Signature\Algorithm\RS512;
105
use SimpleSAML\Auth\ProcessingFilter;
116
use SimpleSAML\Error\Exception;
12-
use SimpleSAML\Logger;
137
use SimpleSAML\Module;
8+
use SimpleSAML\Module\perun\ChallengeManager;
149
use SimpleSAML\Module\perun\UpdateUESThread;
1510

1611
/**
@@ -25,7 +20,6 @@ class UpdateUserExtSource extends ProcessingFilter
2520
{
2621
private $attrMap;
2722
private $attrsToConversion;
28-
private $pathToKey;
2923

3024
const SCRIPT_NAME = 'updateUes';
3125

@@ -41,73 +35,27 @@ public function __construct($config, $reserved)
4135
);
4236
}
4337

44-
if (!isset($config['pathToKey'])) {
45-
throw new Exception(
46-
'perun:UpdateUserExtSource: missing mandatory configuration option \'pathToKey\'.'
47-
);
48-
}
49-
5038
if (isset($config['arrayToStringConversion'])) {
5139
$this->attrsToConversion = (array)$config['arrayToStringConversion'];
5240
} else {
5341
$this->attrsToConversion = [];
5442
}
5543

5644
$this->attrMap = (array)$config['attrMap'];
57-
$this->pathToKey = $config['pathToKey'];
5845
}
5946

6047
public function process(&$request)
6148
{
6249
$id = uniqid("", true);
6350

64-
$dataChallenge = [
65-
'id' => $id,
66-
'scriptName' => self::SCRIPT_NAME
67-
];
68-
69-
$json = json_encode($dataChallenge);
70-
71-
$curlChallenge = curl_init();
72-
curl_setopt($curlChallenge, CURLOPT_POSTFIELDS, $json);
73-
curl_setopt($curlChallenge, CURLOPT_URL, Module::getModuleURL('perun/getChallenge.php'));
74-
curl_setopt($curlChallenge, CURLOPT_RETURNTRANSFER, true);
75-
76-
$challenge = curl_exec($curlChallenge);
77-
78-
if (empty($challenge)) {
79-
Logger::error('Retrieving the challenge was not successful.');
80-
return;
81-
}
82-
83-
$jwk = JWKFactory::createFromKeyFile($this->pathToKey);
84-
$algorithmManager = new AlgorithmManager([new RS512()]);
85-
$jwsBuilder = new JWSBuilder($algorithmManager);
86-
51+
$challengeManager = new ChallengeManager();
8752
$data = [
8853
'attributes' => $request['Attributes'],
8954
'attrMap' => $this->attrMap,
9055
'attrsToConversion' => $this->attrsToConversion,
9156
'perunUserId' => $request['perun']['user']->getId()
9257
];
93-
94-
$payload = json_encode([
95-
'iat' => time(),
96-
'nbf' => time(),
97-
'exp' => time() + 300,
98-
'challenge' => $challenge,
99-
'id' => $id,
100-
'data' => $data
101-
]);
102-
103-
$jws = $jwsBuilder
104-
->create()
105-
->withPayload($payload)
106-
->addSignature($jwk, ['alg' => 'RS512'])
107-
->build();
108-
109-
$serializer = new CompactSerializer();
110-
$token = $serializer->serialize($jws, 0);
58+
$token = $challengeManager->generateToken($id, self::SCRIPT_NAME, $data);
11159

11260
$cmd = 'curl -X POST -H "Content-Type: application/json" -d ' . escapeshellarg($token) . ' ' .
11361
escapeshellarg(Module::getModuleURL('perun/updateUes.php')) . ' > /dev/null &';

lib/ChallengeManager.php

Lines changed: 149 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,166 @@
22

33
namespace SimpleSAML\Module\perun;
44

5+
use SimpleSAML\Configuration;
56
use SimpleSAML\Logger;
67
use SimpleSAML\Module\perun\databaseCommand\ChallengesDbCmd;
8+
use Jose\Component\KeyManagement\JWKFactory;
9+
use Jose\Component\Core\AlgorithmManager;
10+
use Jose\Component\Signature\JWSBuilder;
11+
use Jose\Component\Signature\Serializer\CompactSerializer;
12+
use Jose\Component\Checker\AlgorithmChecker;
13+
use Jose\Component\Checker\ClaimCheckerManager;
14+
use Jose\Component\Checker\HeaderCheckerManager;
15+
use Jose\Component\Signature\JWSTokenSupport;
16+
use Jose\Component\Signature\JWSVerifier;
17+
use Jose\Component\Signature\Serializer\JWSSerializerManager;
18+
use Jose\Component\Checker;
719

820
class ChallengeManager
921
{
1022
const LOG_PREFIX = 'Perun:ChallengeManager: ';
23+
24+
const CONFIG_FILE_NAME = 'challenges_config.php';
25+
const HASH_ALG = 'hashAlg';
26+
const SIG_ALG = 'sigAlg';
27+
const CHALLENGE_LENGTH = 'challengeLength';
28+
const PUB_KEY = 'pubKey';
29+
const PRIV_KEY = 'privKey';
30+
1131
private $challengeDbCmd;
1232

33+
private $hashAlg;
34+
private $challengeLength;
35+
36+
private $privKey;
37+
private $pubKey;
38+
39+
1340
public function __construct()
1441
{
1542
$this->challengeDbCmd = new ChallengesDbCmd();
43+
$config = Configuration::getConfig(self::CONFIG_FILE_NAME);
44+
$this->hashAlg = $config->getString(self::HASH_ALG, 'sha512');
45+
$this->sighAlg = $config->getString(self::SIG_ALG, 'sha512');
46+
$this->challengeLength = $config->getInteger(self::CHALLENGE_LENGTH, 32);
47+
$this->pubKey = $config->getString(self::PUB_KEY);
48+
$this->privKey = $config->getString(self::PRIV_KEY);
1649
}
1750

18-
public function insertChallenge($challenge, $id, $scriptName): bool
51+
public function generateChallenge($id, $scriptName): string
1952
{
20-
if (empty($challenge) ||
21-
empty($id) ||
53+
try {
54+
$challenge = hash($this->hashAlg, random_bytes($this->challengeLength));
55+
} catch (Exception $ex) {
56+
http_response_code(500);
57+
throw new Exception('ChallengeManager.generateChallenge: Error while generating a challenge');
58+
}
59+
60+
if (empty($id) ||
2261
empty($scriptName) ||
2362
!$this->challengeDbCmd->insertChallenge($challenge, $id, $scriptName)) {
2463
Logger::error(self::LOG_PREFIX . 'Error while creating a challenge');
2564
http_response_code(500);
26-
return false;
65+
throw new Exception('ChallengeManager.generateChallenge: Error while generating a challenge');
66+
}
67+
return $challenge;
68+
}
69+
70+
public function generateToken($id, $scriptName, $data)
71+
{
72+
73+
$challenge = $this->generateChallenge($id, $scriptName);
74+
75+
if (empty($challenge)) {
76+
Logger::error('Retrieving the challenge was not successful.');
77+
return;
2778
}
2879

29-
return true;
80+
$jwk = JWKFactory::createFromKeyFile($this->privKey);
81+
$algorithmManager = new AlgorithmManager(
82+
[
83+
self::getAlgorithm('Signature\\Algorithm', $this->sighAlg)
84+
]
85+
);
86+
$jwsBuilder = new JWSBuilder($algorithmManager);
87+
$payload = json_encode([
88+
'iat' => time(),
89+
'nbf' => time(),
90+
'exp' => time() + 300,
91+
'challenge' => $challenge,
92+
'id' => $id,
93+
'data' => $data
94+
]);
95+
96+
$jws = $jwsBuilder
97+
->create()
98+
->withPayload($payload)
99+
->addSignature($jwk, ['alg' => $this->sighAlg])
100+
->build();
101+
102+
$serializer = new CompactSerializer();
103+
$token = $serializer->serialize($jws, 0);
104+
105+
return $token;
30106
}
31107

32-
public function readChallengeFromDb($id)
108+
public function decodeToken($token)
109+
{
110+
$algorithmManager = new AlgorithmManager(
111+
[
112+
self::getAlgorithm('Signature\\Algorithm', $this->sighAlg)
113+
]
114+
);
115+
$jwsVerifier = new JWSVerifier($algorithmManager);
116+
$jwk = JWKFactory::createFromKeyFile($this->pubKey);
117+
118+
$serializerManager = new JWSSerializerManager([new CompactSerializer()]);
119+
$jws = $serializerManager->unserialize($token);
120+
121+
$headerCheckerManager = new HeaderCheckerManager(
122+
[new AlgorithmChecker([$this->sighAlg])],
123+
[new JWSTokenSupport()]
124+
);
125+
126+
$headerCheckerManager->check($jws, 0);
127+
128+
$isVerified = $jwsVerifier->verifyWithKey($jws, $jwk, 0);
129+
130+
if (!$isVerified) {
131+
Logger::error('Perun.updateUes: The token signature is invalid!');
132+
http_response_code(401);
133+
exit;
134+
}
135+
136+
$claimCheckerManager = new ClaimCheckerManager(
137+
[
138+
new Checker\IssuedAtChecker(),
139+
new Checker\NotBeforeChecker(),
140+
new Checker\ExpirationTimeChecker(),
141+
]
142+
);
143+
$claims = json_decode($jws->getPayload(), true);
144+
145+
$claimCheckerManager->check($claims);
146+
147+
$challenge = $claims['challenge'];
148+
$id = $claims['id'];
149+
150+
$challengeManager = new ChallengeManager();
151+
152+
$challengeDb = $challengeManager->readChallengeFromDb($id);
153+
154+
$checkAccessSucceeded = self::checkAccess($challenge, $challengeDb);
155+
$challengeSuccessfullyDeleted = $challengeManager->deleteChallengeFromDb($id);
156+
157+
if (!$checkAccessSucceeded || !$challengeSuccessfullyDeleted) {
158+
exit;
159+
}
160+
161+
return $claims;
162+
}
163+
164+
private function readChallengeFromDb($id)
33165
{
34166
if (empty($id)) {
35167
http_response_code(400);
@@ -45,7 +177,7 @@ public function readChallengeFromDb($id)
45177
return $result;
46178
}
47179

48-
public static function checkAccess($challenge, $challengeDb): bool
180+
private static function checkAccess($challenge, $challengeDb): bool
49181
{
50182
if (empty($challenge) || empty($challengeDb)) {
51183
http_response_code(400);
@@ -61,7 +193,7 @@ public static function checkAccess($challenge, $challengeDb): bool
61193
return true;
62194
}
63195

64-
public function deleteChallengeFromDb($id): bool
196+
private function deleteChallengeFromDb($id): bool
65197
{
66198
if (empty($id)) {
67199
http_response_code(400);
@@ -76,4 +208,13 @@ public function deleteChallengeFromDb($id): bool
76208

77209
return true;
78210
}
211+
212+
private static function getAlgorithm($path, $className)
213+
{
214+
$classPath = sprintf('Jose\\Component\\%s\\%s', $path, $className);
215+
if (! class_exists($classPath)) {
216+
throw new \Exception('Invalid algorithm specified: ' . $classPath);
217+
}
218+
return new $classPath();
219+
}
79220
}

www/getChallenge.php

Lines changed: 0 additions & 48 deletions
This file was deleted.

0 commit comments

Comments
 (0)