Skip to content

Commit e40232e

Browse files
committed
add more tests
1 parent dc6e704 commit e40232e

File tree

2 files changed

+192
-50
lines changed

2 files changed

+192
-50
lines changed

src/JWK.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,27 @@ public static function parseKey(array $jwk)
103103
);
104104
}
105105
return new Key($publicKey, $jwk['alg']);
106+
case 'EC':
107+
if (isset($jwk['d'])) {
108+
// The key is actually a private key
109+
throw new UnexpectedValueException('Key data must be for a public key');
110+
}
111+
112+
if (empty($jwk['crv'])) {
113+
throw new UnexpectedValueException('crv not set');
114+
}
115+
116+
if (!isset(self::$curves[$jwk['crv']])) {
117+
throw new DomainException('Unrecognised or unsupported EC curve');
118+
}
119+
120+
if (empty($jwk['x']) || empty($jwk['y'])) {
121+
throw new UnexpectedValueException('x and y not set');
122+
}
123+
124+
$oid = self::$curves[$jwk['crv']];
125+
$publicKey = self::oidToPem($oid, $jwk['x'], $jwk['y']);
126+
return new Key($publicKey, $jwk['alg']);
106127
default:
107128
// Currently only RSA is supported
108129
break;
@@ -175,4 +196,84 @@ private static function encodeLength($length)
175196

176197
return \pack('Ca*', 0x80 | \strlen($temp), $temp);
177198
}
199+
200+
/**
201+
* Encodes a string into a DER-encoded OID.
202+
*
203+
* @param string $oid the OID string
204+
* @return string the binary DER-encoded OID
205+
*/
206+
private static function encodeOID($oid)
207+
{
208+
$octets = explode('.', $oid);
209+
210+
// Get the first octet
211+
$oid = chr(array_shift($octets) * 40 + array_shift($octets));
212+
213+
// Iterate over subsequent octets
214+
foreach ($octets as $octet) {
215+
if ($octet == 0) {
216+
$oid .= chr(0x00);
217+
continue;
218+
}
219+
$bin = '';
220+
221+
while ($octet) {
222+
$bin .= chr(0x80 | ($octet & 0x7f));
223+
$octet >>= 7;
224+
}
225+
$bin[0] = $bin[0] & chr(0x7f);
226+
227+
// Convert to big endian if necessary
228+
if (pack('V', 65534) == pack('L', 65534)) {
229+
$oid .= strrev($bin);
230+
} else {
231+
$oid .= $bin;
232+
}
233+
}
234+
235+
return $oid;
236+
}
237+
238+
private static $oid = '1.2.840.10045.2.1';
239+
private static $asn1ObjectIdentifier = 0x06;
240+
private static $asn1Integer = 0x02;
241+
private static $asn1Sequence = 0x10;
242+
private static $asn1BitString = 0x03;
243+
244+
private static $curves = [
245+
'P-256' => '1.2.840.10045.3.1.7', // Len: 64
246+
// 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported)
247+
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
248+
];
249+
250+
private static function oidToPem($oid, $x, $y)
251+
{
252+
$pem =
253+
JWT::encodeDER(
254+
self::$asn1Sequence,
255+
JWT::encodeDER(
256+
self::$asn1Sequence,
257+
JWT::encodeDER(
258+
self::$asn1ObjectIdentifier,
259+
self::encodeOID(self::$oid)
260+
)
261+
. JWT::encodeDER(
262+
self::$asn1ObjectIdentifier,
263+
self::encodeOID($oid)
264+
)
265+
) .
266+
JWT::encodeDER(
267+
self::$asn1BitString,
268+
chr(0x00) . chr(0x04)
269+
. JWT::urlsafeB64Decode($x)
270+
. JWT::urlsafeB64Decode($y)
271+
)
272+
);
273+
274+
return sprintf(
275+
"-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
276+
wordwrap(base64_encode($pem), 64, "\n", true)
277+
);
278+
}
178279
}

tests/CachedKeySetTest.php

Lines changed: 91 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,97 @@ public function testCachedKeyIdRefresh()
138138
$this->assertEquals('bar', $cachedKeySet['bar']->getAlgorithm());
139139
}
140140

141+
public function testCacheItemWithExpiresAfter()
142+
{
143+
$expiresAfter = 10;
144+
$cacheItem = $this->prophesize('Psr\Cache\CacheItemInterface');
145+
$cacheItem->isHit()
146+
->shouldBeCalledOnce()
147+
->willReturn(false);
148+
$cacheItem->set(Argument::any())
149+
->shouldBeCalledOnce();
150+
$cacheItem->expiresAfter($expiresAfter)
151+
->shouldBeCalledOnce();
152+
153+
$cache = $this->prophesize('Psr\Cache\CacheItemPoolInterface');
154+
$cache->getItem($this->testJwkUriKey)
155+
->shouldBeCalledOnce()
156+
->willReturn($cacheItem->reveal());
157+
$cache->save(Argument::any())
158+
->shouldBeCalledOnce();
159+
160+
$cachedKeySet = new CachedKeySet(
161+
$this->testJwkUri,
162+
$this->getMockHttpClient($this->testJwk1),
163+
$this->getMockHttpFactory(),
164+
$cache->reveal(),
165+
$expiresAfter
166+
);
167+
$this->assertInstanceOf('Firebase\JWT\Key', $cachedKeySet['foo']);
168+
$this->assertEquals('foo', $cachedKeySet['foo']->getAlgorithm());
169+
}
170+
171+
public function testJwtVerify()
172+
{
173+
$privKey1 = file_get_contents(__DIR__ . '/data/rsa1-private.pem');
174+
$payload = array('sub' => 'foo', 'exp' => strtotime('+10 seconds'));
175+
$msg = JWT::encode($payload, $privKey1, 'RS256', 'jwk1');
176+
177+
$cacheItem = $this->prophesize('Psr\Cache\CacheItemInterface');
178+
$cacheItem->isHit()
179+
->willReturn(true);
180+
$cacheItem->get()
181+
->willReturn(JWK::parseKeySet(
182+
json_decode(file_get_contents(__DIR__ . '/data/rsa-jwkset.json'), true)
183+
));
184+
185+
$cache = $this->prophesize('Psr\Cache\CacheItemPoolInterface');
186+
$cache->getItem($this->testJwkUriKey)
187+
->willReturn($cacheItem->reveal());
188+
189+
$cachedKeySet = new CachedKeySet(
190+
$this->testJwkUri,
191+
$this->prophesize('Psr\Http\Client\ClientInterface')->reveal(),
192+
$this->prophesize('Psr\Http\Message\RequestFactoryInterface')->reveal(),
193+
$cache->reveal()
194+
);
195+
196+
$result = JWT::decode($msg, $cachedKeySet);
197+
198+
$this->assertEquals("foo", $result->sub);
199+
}
200+
201+
/**
202+
* @dataProvider provideFullIntegration
203+
*/
204+
public function testFullIntegration($jwkUri, $kid)
205+
{
206+
if (!class_exists(TestMemoryCacheItemPool::class)) {
207+
$this->markTestSkipped('Use phpunit-system.xml.dist to run this tests');
208+
}
209+
210+
$cache = new TestMemoryCacheItemPool();
211+
$http = new \GuzzleHttp\Client();
212+
$factory = new \GuzzleHttp\Psr7\HttpFactory();
213+
214+
$cachedKeySet = new CachedKeySet(
215+
$jwkUri,
216+
$http,
217+
$factory,
218+
$cache
219+
);
220+
221+
$this->assertArrayHasKey($kid, $cachedKeySet);
222+
}
223+
224+
public function provideFullIntegration()
225+
{
226+
return [
227+
[$this->googleRsaUri, '182e450a35a2081faa1d9ae1d2d75a0f23d91df8'],
228+
// [$this->googleEcUri, 'LYyP2g']
229+
];
230+
}
231+
141232
private function getMockHttpClient($testJwk)
142233
{
143234
$body = $this->prophesize('Psr\Http\Message\StreamInterface');
@@ -188,56 +279,6 @@ private function getMockEmptyCache()
188279
return $cache->reveal();
189280
}
190281

191-
public function testCacheItemWithExpiresAfter()
192-
{
193-
$expiresAfter = 10;
194-
$cacheItem = $this->prophesize('Psr\Cache\CacheItemInterface');
195-
$cacheItem->isHit()
196-
->shouldBeCalledOnce()
197-
->willReturn(false);
198-
$cacheItem->set(Argument::any())
199-
->shouldBeCalledOnce();
200-
$cacheItem->expiresAfter($expiresAfter)
201-
->shouldBeCalledOnce();
202-
203-
$cache = $this->prophesize('Psr\Cache\CacheItemPoolInterface');
204-
$cache->getItem($this->testJwkUriKey)
205-
->shouldBeCalledOnce()
206-
->willReturn($cacheItem->reveal());
207-
$cache->save(Argument::any())
208-
->shouldBeCalledOnce();
209-
210-
$cachedKeySet = new CachedKeySet(
211-
$this->testJwkUri,
212-
$this->getMockHttpClient($this->testJwk1),
213-
$this->getMockHttpFactory(),
214-
$cache->reveal(),
215-
$expiresAfter
216-
);
217-
$this->assertInstanceOf('Firebase\JWT\Key', $cachedKeySet['foo']);
218-
$this->assertEquals('foo', $cachedKeySet['foo']->getAlgorithm());
219-
}
220-
221-
public function testFullIntegration()
222-
{
223-
if (!class_exists(TestMemoryCacheItemPool::class)) {
224-
$this->markTestSkipped('Use phpunit-system.xml.dist to run this tests');
225-
}
226-
227-
$cache = new TestMemoryCacheItemPool();
228-
$http = new \GuzzleHttp\Client();
229-
$factory = new \GuzzleHttp\Psr7\HttpFactory();
230-
231-
$cachedKeySet = new CachedKeySet(
232-
$this->googleRsaUri,
233-
$http,
234-
$factory,
235-
$cache
236-
);
237-
238-
$this->assertArrayHasKey('182e450a35a2081faa1d9ae1d2d75a0f23d91df8', $cachedKeySet);
239-
}
240-
241282
/*
242283
* For compatibility with PHPUnit 4.8 and PHP < 5.6
243284
*/

0 commit comments

Comments
 (0)