22
33namespace SimpleSAML \Module \perun ;
44
5+ use SimpleSAML \Configuration ;
56use SimpleSAML \Logger ;
67use 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
820class 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}
0 commit comments