Skip to content

Commit 490a49c

Browse files
authored
Merge pull request #20 from pdsinterop/feature/nextcloud24
Feature/nextcloud24
2 parents f2b1a5e + 1688dc4 commit 490a49c

7 files changed

Lines changed: 116 additions & 97 deletions

File tree

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
"license": "MIT",
2121
"name": "pdsinterop/solid-auth",
2222
"require": {
23-
"php": "^7.3",
23+
"php": "^8.0",
2424
"ext-json": "*",
2525
"ext-mbstring": "*",
2626
"ext-openssl": "*",
2727
"laminas/laminas-diactoros": "^2.8",
28-
"lcobucci/jwt": "3.3.3",
29-
"league/oauth2-server": "^8.1",
28+
"lcobucci/jwt": "^4.1",
29+
"league/oauth2-server": "^8.3.5",
3030
"web-token/jwt-core": "^2.2"
3131
},
3232
"require-dev": {

src/Config/Keys.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Pdsinterop\Solid\Auth\Config;
44

55
use Defuse\Crypto\Key as CryptoKey;
6-
use Lcobucci\JWT\Signer\Key;
6+
use Lcobucci\JWT\Signer\Key\InMemory as Key;
77
use League\OAuth2\Server\CryptKey;
88

99
class Keys

src/Factory/ConfigFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Pdsinterop\Solid\Auth\Factory;
44

5-
use Lcobucci\JWT\Signer\Key;
5+
use Lcobucci\JWT\Signer\Key\InMemory;
66
use League\OAuth2\Server\CryptKey;
77
use Pdsinterop\Solid\Auth\Config;
88
use Pdsinterop\Solid\Auth\Enum\OAuth2\GrantType;
@@ -53,7 +53,7 @@ final public function create() : Config
5353

5454
$keys = new Config\Keys(
5555
new CryptKey($privateKey),
56-
new Key($publicKey),
56+
InMemory::plainText($publicKey),
5757
$encryptionKey
5858
);
5959

src/TokenGenerator.php

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
use Laminas\Diactoros\Response\JsonResponse as JsonResponse;
88
use League\OAuth2\Server\CryptTrait;
99

10+
use DateTimeImmutable;
11+
use Lcobucci\JWT\Configuration;
12+
use Lcobucci\JWT\Signer\Key\InMemory;
13+
use Lcobucci\JWT\Signer\Rsa\Sha256;
14+
1015
class TokenGenerator
1116
{
1217
use CryptTrait;
@@ -28,48 +33,47 @@ public function generateRegistrationAccessToken($clientId, $privateKey) {
2833
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);
2934

3035
// Create JWT
31-
$signer = new \Lcobucci\JWT\Signer\Rsa\Sha256();
32-
$keychain = new \Lcobucci\JWT\Signer\Keychain();
33-
$builder = new \Lcobucci\JWT\Builder();
34-
$token = $builder
35-
->setIssuer($issuer)
36-
->permittedFor($clientId)
37-
->set("sub", $clientId)
38-
->sign($signer, $keychain->getPrivateKey($privateKey))
39-
->getToken();
40-
return $token->__toString();
36+
$jwtConfig = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($privateKey));
37+
$token = $jwtConfig->builder()
38+
->issuedBy($issuer)
39+
->permittedFor($clientId)
40+
->relatedTo($clientId)
41+
->getToken($jwtConfig->signer(), $jwtConfig->signingKey());
42+
43+
return $token->toString();
4144
}
4245

4346
public function generateIdToken($accessToken, $clientId, $subject, $nonce, $privateKey, $dpopKey=null) {
4447
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);
4548

46-
$jwks = $this->getJwks();
49+
$jwks = $this->getJwks();
4750
$tokenHash = $this->generateTokenHash($accessToken);
4851

49-
// Create JWT
50-
$signer = new \Lcobucci\JWT\Signer\Rsa\Sha256();
51-
$keychain = new \Lcobucci\JWT\Signer\Keychain();
52-
$builder = new \Lcobucci\JWT\Builder();
53-
$token = $builder
54-
->setIssuer($issuer)
55-
->permittedFor($clientId)
56-
->setIssuedAt(time())
57-
->setNotBefore(time() - 1)
58-
->setExpiration(time() + 14*24*60*60)
59-
->set("azp", $clientId)
60-
->set("sub", $subject)
61-
->set("jti", $this->generateJti())
62-
->set("nonce", $nonce)
63-
->set("at_hash", $tokenHash) //FIXME: at_hash should only be added if the response_type is a token
64-
->set("c_hash", $tokenHash) // FIXME: c_hash should only be added if the response_type is a code
65-
->set("cnf", array(
66-
"jkt" => $dpopKey,
67-
// "jwk" => $jwks['keys'][0]
68-
))
69-
->withHeader('kid', $jwks['keys'][0]['kid'])
70-
->sign($signer, $keychain->getPrivateKey($privateKey))
71-
->getToken();
72-
return $token->__toString();
52+
// Create JWT
53+
$jwtConfig = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($privateKey));
54+
$now = new DateTimeImmutable();
55+
$useAfter = $now->sub(new \DateInterval('PT1S'));
56+
$expire = $now->add(new \DateInterval('PT' . 14*24*60*60 . 'S'));
57+
58+
$token = $jwtConfig->builder()
59+
->issuedBy($issuer)
60+
->permittedFor($clientId)
61+
->issuedAt($now)
62+
->canOnlyBeUsedAfter($useAfter)
63+
->expiresAt($expire)
64+
->withClaim("azp", $clientId)
65+
->relatedTo($subject)
66+
->identifiedBy($this->generateJti())
67+
->withClaim("nonce", $nonce)
68+
->withClaim("at_hash", $tokenHash) //FIXME: at_hash should only be added if the response_type is a token
69+
->withClaim("c_hash", $tokenHash) // FIXME: c_hash should only be added if the response_type is a code
70+
->withClaim("cnf", array(
71+
"jkt" => $dpopKey,
72+
// "jwk" => $jwks['keys'][0]
73+
))
74+
->withHeader('kid', $jwks['keys'][0]['kid'])
75+
->getToken($jwtConfig->signer(), $jwtConfig->signingKey());
76+
return $token->toString();
7377
}
7478

7579
public function respondToRegistration($registration, $privateKey) {

src/Utils/DPop.php

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@
22

33
namespace Pdsinterop\Solid\Auth\Utils;
44

5-
use Lcobucci\JWT\Parser;
6-
use Lcobucci\JWT\Signer\Key;
7-
use Lcobucci\JWT\ValidationData;
5+
use Lcobucci\JWT\Configuration;
6+
use Lcobucci\Clock\SystemClock;
7+
use DateTimeImmutable;
8+
use DateInterval;
9+
use Lcobucci\JWT\Signer\Key\InMemory;
10+
use Lcobucci\JWT\Signer\Rsa\Sha256;
11+
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
12+
813
use Jose\Component\Core\JWK;
914
use Jose\Component\Core\Util\ECKey;
1015
use Jose\Component\Core\Util\RSAKey;
1116

1217
class DPop {
1318
public function getWebId($request) {
1419
$auth = explode(" ", $request->getServerParams()['HTTP_AUTHORIZATION']);
15-
$jwt = $auth[1];
20+
$jwt = $auth[1] ?? false;
1621

1722
if (strtolower($auth[0]) == "dpop") {
1823
$dpop = $request->getServerParams()['HTTP_DPOP'];
@@ -37,22 +42,22 @@ public function getDpopKey($dpop, $request) {
3742
//error_log("11");
3843
$this->validateDpop($dpop, $request);
3944
//error_log("22");
40-
41-
$parser = new \Lcobucci\JWT\Parser();
45+
4246
// 1. the string value is a well-formed JWT,
43-
$dpop = $parser->parse($dpop);
44-
$jwk = $dpop->getHeader("jwk");
47+
$jwtConfig = $configuration = Configuration::forUnsecuredSigner();
48+
$dpop = $jwtConfig->parser()->parse($dpop);
49+
$jwk = $dpop->headers()->get("jwk");
4550
//error_log(print_r($jwk, true));
4651

47-
return $jwk->kid;
52+
return $jwk['kid'];
4853
}
4954

5055
private function validateJwtDpop($jwt, $dpopKey) {
51-
$parser = new \Lcobucci\JWT\Parser();
52-
$jwt = $parser->parse($jwt);
53-
$cnf = $jwt->getClaim("cnf");
56+
$jwtConfig = $configuration = Configuration::forUnsecuredSigner();
57+
$jwt = $jwtConfig->parser()->parse($jwt);
58+
$cnf = $jwt->claims()->get("cnf");
5459

55-
if ($cnf->jkt == $dpopKey) {
60+
if ($cnf['jkt'] == $dpopKey) {
5661
//error_log("dpopKey matches");
5762
return true;
5863
}
@@ -88,17 +93,16 @@ private function validateDpop($dpop, $request) {
8893
received previously (see Section 9.1).
8994
*/
9095
//error_log("1");
91-
92-
$parser = new \Lcobucci\JWT\Parser();
9396
// 1. the string value is a well-formed JWT,
94-
$dpop = $parser->parse($dpop);
97+
$jwtConfig = $configuration = Configuration::forUnsecuredSigner();
98+
$dpop = $jwtConfig->parser()->parse($dpop);
9599

96100
//error_log("2");
97101
// 2. all required claims are contained in the JWT,
98-
$htm = $dpop->getClaim("htm"); // http method
99-
$htu = $dpop->getClaim("htu"); // http uri
100-
$typ = $dpop->getHeader("typ");
101-
$alg = $dpop->getHeader("alg");
102+
$htm = $dpop->claims()->get("htm"); // http method
103+
$htu = $dpop->claims()->get("htu"); // http uri
104+
$typ = $dpop->headers()->get("typ");
105+
$alg = $dpop->headers()->get("alg");
102106

103107
//error_log("3");
104108
// 3. the "typ" field in the header has the value "dpop+jwt",
@@ -117,7 +121,7 @@ private function validateDpop($dpop, $request) {
117121
//error_log("5");
118122
// 5. that the JWT is signed using the public key contained in the
119123
// "jwk" header of the JWT,
120-
$jwk = $dpop->getHeader("jwk");
124+
$jwk = $dpop->headers()->get("jwk");
121125
$webTokenJwk = \Jose\Component\Core\JWK::createFromJson(json_encode($jwk));
122126
switch ($alg) {
123127
case "RS256":
@@ -126,16 +130,21 @@ private function validateDpop($dpop, $request) {
126130
break;
127131
case "ES256":
128132
$pem = \Jose\Component\Core\Util\ECKey::convertToPEM($webTokenJwk);
129-
$signer = new \Lcobucci\JWT\Signer\Ecdsa\Sha256();
133+
$signer = \Lcobucci\JWT\Signer\Ecdsa\Sha256::create();
130134
break;
131135
default:
132136
throw new \Exception("unsupported algorithm");
133137
break;
134138
}
135-
$key = new \Lcobucci\JWT\Signer\Key($pem);
136-
if (!$dpop->verify($signer, $key)) {
137-
throw new \Exception("invalid signature");
138-
}
139+
$key = InMemory::plainText($pem);
140+
$jwtConfig = Configuration::forSymmetricSigner($signer, InMemory::plainText($pem));
141+
142+
// FIXME: Add constraints;
143+
// $constraint = new LooseValidAt($clock, $leeway); // It will use the current time to validate (iat, nbf and exp)
144+
// $jwtConfig->setValidationConstraints($constraint);
145+
// if (!$jwtConfig->validator()->validate($dpop, ...$jwtConfig->validationConstraints())) {
146+
// throw new \Exception("invalid signature");
147+
// }
139148

140149
//error_log("6");
141150
// 6. the "htm" claim matches the HTTP method value of the HTTP request
@@ -162,9 +171,12 @@ private function validateDpop($dpop, $request) {
162171

163172
//error_log("8");
164173
// 8. the token was issued within an acceptable timeframe (see Section 9.1), and
165-
$leeway = 5; // allow 5 seconds clock skew
166-
$validationData = new ValidationData(time() + $leeway); // It will use the current time to validate (iat, nbf and exp)
167-
if (!$dpop->validate($validationData)) {
174+
175+
$leeway = new \DateInterval("PT60S"); // allow 60 seconds clock skew
176+
$clock = SystemClock::fromUTC();
177+
$constraint = new LooseValidAt($clock, $leeway); // It will use the current time to validate (iat, nbf and exp)
178+
$jwtConfig->setValidationConstraints($constraint);
179+
if (!$jwtConfig->validator()->validate($dpop, ...$jwtConfig->validationConstraints())) {
168180
throw new \Exception("token timing is invalid");
169181
}
170182

@@ -176,14 +188,14 @@ private function validateDpop($dpop, $request) {
176188
}
177189

178190
private function getSubjectFromJwt($jwt) {
179-
$parser = new \Lcobucci\JWT\Parser();
191+
$jwtConfig = $configuration = Configuration::forUnsecuredSigner();
180192
try {
181-
$jwt = $parser->parse($jwt);
193+
$jwt = $jwtConfig->parser()->parse($jwt);
182194
} catch(\Exception $e) {
183195
return $this->server->getResponse()->withStatus(409, "Invalid JWT token");
184196
}
185197

186-
$sub = $jwt->getClaim("sub");
198+
$sub = $jwt->claims()->get("sub");
187199
return $sub;
188200
}
189201
}

src/Utils/Jwks.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
namespace Pdsinterop\Solid\Auth\Utils;
44

55
use JsonSerializable;
6-
use Lcobucci\JWT\Signer\Key;
6+
use Lcobucci\JWT\Signer\Key\InMemory;
77
use Pdsinterop\Solid\Auth\Enum\Jwk\Parameter as JwkParameter;
88
use Pdsinterop\Solid\Auth\Enum\Rsa\Parameter as RsaParameter;
99

1010
class Jwks implements JsonSerializable
1111
{
1212
////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
1313

14-
/** @var Key */
14+
/** @var InMemory */
1515
private $publicKey;
1616

1717
//////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
1818

19-
final public function __construct(Key $publicKey)
19+
final public function __construct(InMemory $publicKey)
2020
{
2121
$this->publicKey = $publicKey;
2222
}
@@ -64,8 +64,8 @@ private function create() : array
6464

6565
$publicKeys = [$this->publicKey];
6666

67-
array_walk($publicKeys, function (Key $publicKey) use (&$jwks) {
68-
$certificate = $publicKey->getContent();
67+
array_walk($publicKeys, function (InMemory $publicKey) use (&$jwks) {
68+
$certificate = $publicKey->contents();
6969

7070
$key = openssl_pkey_get_public($certificate);
7171
$keyInfo = openssl_pkey_get_details($key);

0 commit comments

Comments
 (0)