Skip to content

Commit

Permalink
Refactor OIDC into shared class.
Browse files Browse the repository at this point in the history
  • Loading branch information
cooperaj committed Dec 11, 2023
1 parent dbd3070 commit 12d2d83
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 86 deletions.
96 changes: 10 additions & 86 deletions service-api/app/features/context/Acceptance/OidcContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

namespace BehatTest\Context\Acceptance;

use AppTest\OidcUtilities;
use Aws\Command;
use Aws\Result;
use Aws\ResultInterface;
use Behat\Behat\Context\Context;
use Behat\Testwork\Suite\Exception\SuiteSetupException;
use BehatTest\Context\BaseAcceptanceContextTrait;
use BehatTest\Context\SetupEnv;
use Fig\Http\Message\StatusCodeInterface;
Expand All @@ -17,7 +17,6 @@
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\Algorithm\RS256;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Component\Signature\Serializer\JWSSerializerManager;
Expand All @@ -41,80 +40,23 @@ class OidcContext implements Context
public string $email = '[email protected]';
public string $birthday = '1970-01-01';

public static function generateKeyPair(array $options): array
{
$key = openssl_pkey_new($options);
if ($key === false) {
throw new SuiteSetupException('Unable to create the identity key', 'onelogin');
}

$details = openssl_pkey_get_details($key);
if (! is_array($details)) {
throw new SuiteSetupException('Unable to get key details', 'onelogin');
}

$success = openssl_pkey_export($key, $privateKey);
if (!$success) {
throw new SuiteSetupException('Unable to export key to string', 'onelogin');
}

return [$privateKey, $details['key']];
}

protected function coreIdentityTokenSetup(): string
{
$token = json_encode(
[
'iss' => 'http://identity.one-login-mock/',
'sub' => $this->sub,
'exp' => time() + 300,
'iat' => time(),
'nbf' => time(),
'vc' => [
'type' => [
'VerifiableCredential',
'VerifiableIdentityCredential',
],
'credentialSubject' => [
'birthDate' => [
['value' => $this->birthday],
],
],
],
],
);
[$token, $this->oneLoginOutOfBandPublicKey] =
OidcUtilities::generateCoreIdentityToken($this->sub, $this->birthday);

[$private, $this->oneLoginOutOfBandPublicKey] = self::generateKeyPair(
[
'curve_name' => 'prime256v1',
'private_key_type' => OPENSSL_KEYTYPE_EC,
],
);

return $this->signToken($token, $private);
return $token;
}

protected function identityTokenSetup(): string
{
$token = json_encode(
[
'iss' => 'https://one-login-mock',
'sub' => $this->sub,
'aud' => $this->clientId,
'exp' => time() + 300,
'iat' => time(),
'nonce' => $this->nonce,
],
);

[$private, $this->oneLoginIssuerPublicKey] = self::generateKeyPair(
[
'curve_name' => 'prime256v1',
'private_key_type' => OPENSSL_KEYTYPE_EC,
],
[$token, $this->oneLoginIssuerPublicKey] = OidcUtilities::generateIdentityToken(
$this->sub,
$this->clientId,
$this->nonce,
);

return $this->signToken($token, $private);
return $token;
}

protected function oidcFixtureSetup(bool $withCache = false): void
Expand All @@ -126,7 +68,7 @@ protected function oidcFixtureSetup(bool $withCache = false): void

apcu_clear_cache();

[$this->oneLoginClientPrivateKey, $this->oneLoginClientPublicKey] = self::generateKeyPair(
[$this->oneLoginClientPrivateKey, $this->oneLoginClientPublicKey] = OidcUtilities::generateKeyPair(
[
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
Expand Down Expand Up @@ -175,24 +117,6 @@ function (RequestInterface $request): ResponseInterface {
);
}

/**
* Signs a JWT with an ES256 algorithm
*
* @param string $payload A json encoded array structure representing a JWT.
* @param string $key A public or private key in PEM format. Must be an EC key.
* @return string
*/
protected function signToken(string $payload, string $key): string
{
$jwsBuilder = (new JWSBuilder(new AlgorithmManager([new ES256()])))
->create()
->withPayload($payload)
->addSignature(JWKFactory::createFromKey($key), ['alg' => 'ES256'])
->build();

return (new CompactSerializer())->serialize($jwsBuilder, 0);
}

/**
* Verifies that the supplied JWS is correctly signed
*
Expand Down
49 changes: 49 additions & 0 deletions service-api/app/test/AppTest/OidcUtilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@

class OidcUtilities
{
/**
* Generates a core identity token as used by Gov One Login.
*
* @param string $sub The token subject field
* @param string $birthday A birthday to return in the identity data
* @return array{
* string,
* string
* } The signed token and a public key that can be used to verify it.
* @throws Exception
*/
public static function generateCoreIdentityToken(string $sub, string $birthday): array
{
$token = json_encode(
Expand Down Expand Up @@ -49,6 +60,44 @@ public static function generateCoreIdentityToken(string $sub, string $birthday):
];
}

/**
* Generates an identity token
*
* @param string $sub The token subject claim
* @param string $aud The token audience claim
* @param string $nonce A nonce that should be encoded as a claim
* @return array{
* string,
* string
* } The signed token and a public key that can be used to verify it.
* @throws Exception
*/
public static function generateIdentityToken(string $sub, string $aud, string $nonce): array
{
$token = json_encode(
[
'iss' => 'https://one-login-mock',
'sub' => $sub,
'aud' => $aud,
'exp' => time() + 300,
'iat' => time(),
'nonce' => $nonce,
],
);

[$private, $public] = self::generateKeyPair(
[
'curve_name' => 'prime256v1',
'private_key_type' => OPENSSL_KEYTYPE_EC,
],
);

return [
self::signToken($token, $private),
$public,
];
}

/**
* Generates an elliptic-curve keypair
*
Expand Down

0 comments on commit 12d2d83

Please sign in to comment.