Skip to content
This repository has been archived by the owner on Dec 30, 2020. It is now read-only.

Commit

Permalink
JKU and X5U key sets added (#144)
Browse files Browse the repository at this point in the history
* JKU and X5U key sets added
* Documentation updated
  • Loading branch information
Spomky authored Oct 17, 2016
1 parent 7ceb406 commit 229c6ca
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 98 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "6.0.x-dev"
"dev-master": "6.1.x-dev"
}
}
}
19 changes: 16 additions & 3 deletions doc/object/jwkset.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,24 @@ To avoid calls to a server each time you need a certificate, the `createFromJKU`
use Jose\Factory\JWKFactory;

$cacheItemPool = YourValidCacheItemPool //An instance of a class that implements Psr\Cache\CacheItemPoolInterface
$ttl = 300; //Cache lifetime in seconds. Default is 86400 = 24 hrs.
$ttl = 300; //Cache lifetime in seconds. Default is 86400 = 24 hrs. 0 means the cache never expires (not recommended).

$jwk_set = JWKFactory::createFromJKU('http://www.example.com/certs', false, $cacheItemPool, $ttl);
```

### HTTP Connection Support

During tests for example, it is useful to retrieve keys using a non-encrypted connection (HTTP).
From the version 6.1 of this library, it is possible to allow URLs with the `http://` scheme.

You just have to set the last argument as `true`.

```php
use Jose\Factory\JWKFactory;

$jwk_set = JWKFactory::createFromJKU('http://www.example.com/certs', false, null, 0, true);
```


## Create a Key from a X509 Certificate Url (`x5u`)

Expand All @@ -140,9 +153,9 @@ use Jose\Factory\JWKFactory;
$jwk_set = JWKFactory::createFromX5U('https://www.googleapis.com/oauth2/v1/certs');
```

### Unsecured Connections and Caching Support
### Unsecured, HTTP Connections and Caching Support

The method `createFromX5U` supports the same arguments as the method `createFromJKU` for unsecured connections or caching support.
The method `createFromX5U` supports the same arguments as the method `createFromJKU` for unsecured, HTTP connections or caching support.

## Create a Key Set with Random keys

Expand Down
98 changes: 6 additions & 92 deletions src/Factory/JWKFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Base64Url\Base64Url;
use Jose\KeyConverter\KeyConverter;
use Jose\KeyConverter\RSAKey;
use Jose\Object\JKUJWKSet;
use Jose\Object\JWK;
use Jose\Object\JWKSet;
use Jose\Object\JWKSetInterface;
Expand All @@ -23,6 +24,7 @@
use Jose\Object\RotatableJWKSet;
use Jose\Object\StorableJWK;
use Jose\Object\StorableJWKSet;
use Jose\Object\X5UJWKSet;
use Mdanter\Ecc\Curves\CurveFactory;
use Mdanter\Ecc\Curves\NistCurve;
use Mdanter\Ecc\EccFactory;
Expand Down Expand Up @@ -322,64 +324,17 @@ public static function createFromKey($key, $password = null, array $additional_v
/**
* {@inheritdoc}
*/
public static function createFromJKU($jku, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null, $ttl = 86400)
public static function createFromJKU($jku, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null, $ttl = 86400, $allow_http_connection = false)
{
$content = self::getContent($jku, $allow_unsecured_connection, $cache, $ttl);

Assertion::keyExists($content, 'keys', 'Invalid content.');

return new JWKSet($content);
return new JKUJWKSet($jku, $cache, $ttl, $allow_unsecured_connection, $allow_http_connection);
}

/**
* {@inheritdoc}
*/
public static function createFromX5U($x5u, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null, $ttl = 86400)
{
$content = self::getContent($x5u, $allow_unsecured_connection, $cache, $ttl);

$jwkset = new JWKSet();
foreach ($content as $kid => $cert) {
$jwk = KeyConverter::loadKeyFromCertificate($cert);
Assertion::notEmpty($jwk, 'Invalid content.');
if (is_string($kid)) {
$jwk['kid'] = $kid;
}
$jwkset->addKey(new JWK($jwk));
}

return $jwkset;
}

/**
* @param string $url
* @param bool $allow_unsecured_connection
* @param \Psr\Cache\CacheItemPoolInterface|null $cache
* @param int|null $ttl
*
* @return array
*/
private static function getContent($url, $allow_unsecured_connection, CacheItemPoolInterface $cache = null, $ttl = 86400)
public static function createFromX5U($x5u, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null, $ttl = 86400, $allow_http_connection = false)
{
Assertion::nullOrInteger($ttl);
$cache_key = sprintf('JWKFactory-Content-%s', hash('sha512', $url));
if (null !== $cache) {
$item = $cache->getItem($cache_key);
if (!$item->isHit()) {
$content = self::downloadContent($url, $allow_unsecured_connection);
$item->set($content);
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
$cache->save($item);

return $content;
} else {
return $item->get();
}
}

return self::downloadContent($url, $allow_unsecured_connection);
return new X5UJWKSet($x5u, $cache, $ttl, $allow_unsecured_connection, $allow_http_connection);
}

/**
Expand All @@ -402,45 +357,4 @@ public static function createFromKeySet(JWKSetInterface $jwk_set, $key_index)

return $jwk_set->getKey($key_index);
}

/**
* @param string $url
* @param bool $allow_unsecured_connection
*
* @throws \InvalidArgumentException
*
* @return array
*/
private static function downloadContent($url, $allow_unsecured_connection)
{
// The URL must be a valid URL and scheme must be https
Assertion::false(
false === filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED),
'Invalid URL.'
);
Assertion::false(
false === $allow_unsecured_connection && 'https://' !== mb_substr($url, 0, 8, '8bit'),
'Unsecured connection.'
);

$params = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_URL => $url,
];
if (false === $allow_unsecured_connection) {
$params[CURLOPT_SSL_VERIFYPEER] = true;
$params[CURLOPT_SSL_VERIFYHOST] = 2;
}

$ch = curl_init();
curl_setopt_array($ch, $params);
$content = curl_exec($ch);
curl_close($ch);

Assertion::notEmpty($content, 'Unable to get content.');
$content = json_decode($content, true);
Assertion::isArray($content, 'Invalid content.');

return $content;
}
}
6 changes: 4 additions & 2 deletions src/Factory/JWKFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,22 @@ public static function createFromKey($key, $password = null, array $additional_v
* @param bool $allow_unsecured_connection
* @param \Psr\Cache\CacheItemPoolInterface|null $cache
* @param int|null $ttl
* @param bool $allow_http_connection
*
* @return \Jose\Object\JWKSetInterface
*/
public static function createFromJKU($jku, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null, $ttl = 86400);
public static function createFromJKU($jku, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null, $ttl = 86400, $allow_http_connection = false);

/**
* @param string $x5u
* @param bool $allow_unsecured_connection
* @param \Psr\Cache\CacheItemPoolInterface|null $cache
* @param int|null $ttl
* @param bool $allow_http_connection
*
* @return \Jose\Object\JWKSetInterface
*/
public static function createFromX5U($x5u, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null, $ttl = 86400);
public static function createFromX5U($x5u, $allow_unsecured_connection = false, CacheItemPoolInterface $cache = null, $ttl = 86400, $allow_http_connection = false);

/**
* @param array $x5c
Expand Down
139 changes: 139 additions & 0 deletions src/Object/DownloadedJWKSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/

namespace Jose\Object;

use Assert\Assertion;
use Psr\Cache\CacheItemPoolInterface;

/**
* Class DownloadedJWKSet.
*/
abstract class DownloadedJWKSet implements JWKSetInterface
{
use BaseJWKSet;
use JWKSetPEM;

/**
* @var string
*/
private $url;

/**
* @var null|\Psr\Cache\CacheItemPoolInterface
*/
private $cache;

/**
* @var int
*/
private $ttl;

/**
* @var bool
*/
private $allow_unsecured_connection;

/**
* DownloadedJWKSet constructor.
*
* @param string $url
* @param \Psr\Cache\CacheItemPoolInterface|null $cache
* @param int $ttl
* @param bool $allow_unsecured_connection
* @param bool $allow_http_connection
*/
public function __construct($url, CacheItemPoolInterface $cache = null, $ttl = 86400, $allow_unsecured_connection = false, $allow_http_connection = false)
{
Assertion::boolean($allow_unsecured_connection);
Assertion::boolean($allow_http_connection);
Assertion::integer($ttl);
Assertion::min($ttl, 0);
Assertion::false(false === filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED), 'Invalid URL.');
$allowed_protocols = ['https'];
if (true === $allow_http_connection) {
$allowed_protocols[] = 'http';
}
Assertion::inArray(mb_substr($url, 0, mb_strpos($url, '://', 0, '8bit'), '8bit'), $allowed_protocols, sprintf('The provided sector identifier URI is not valid: scheme must be one of the following: %s.', json_encode($allowed_protocols)));

$this->url = $url;
$this->cache = $cache;
$this->ttl = $ttl;
$this->allow_unsecured_connection = $allow_unsecured_connection;
}

/**
* {@inheritdoc}
*/
public function addKey(JWKInterface $key)
{
//Not available
}

/**
* {@inheritdoc}
*/
public function removeKey($index)
{
//Not available
}

/**
* @return string
*/
protected function getContent()
{
$cache_key = sprintf('JWKFactory-Content-%s', hash('sha512', $this->url));
if (null !== $this->cache) {
$item = $this->cache->getItem($cache_key);
if (!$item->isHit()) {
$content = $this->downloadContent();
$item->set($content);
if (0 !== $this->ttl) {
$item->expiresAfter($this->ttl);
}
$this->cache->save($item);

return $content;
} else {
return $item->get();
}
}

return $this->downloadContent();
}

/**
* @throws \InvalidArgumentException
*
* @return string
*/
private function downloadContent()
{
$params = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_URL => $this->url,
];
if (false === $this->allow_unsecured_connection) {
$params[CURLOPT_SSL_VERIFYPEER] = true;
$params[CURLOPT_SSL_VERIFYHOST] = 2;
}

$ch = curl_init();
curl_setopt_array($ch, $params);
$content = curl_exec($ch);
curl_close($ch);

Assertion::false(false === $content, 'Unable to get content.');

return $content;
}
}
32 changes: 32 additions & 0 deletions src/Object/JKUJWKSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/

namespace Jose\Object;

use Assert\Assertion;

/**
* Class JKUJWKSet.
*/
final class JKUJWKSet extends DownloadedJWKSet
{
/**
* @return \Jose\Object\JWKInterface[]
*/
public function getKeys()
{
$content = json_decode($this->getContent(), true);
Assertion::isArray($content, 'Invalid content.');
Assertion::keyExists($content, 'keys', 'Invalid content.');

return (new JWKSet($content))->getKeys();
}
}
Loading

0 comments on commit 229c6ca

Please sign in to comment.