Skip to content
Snippets Groups Projects
Commit 8aaffe8f authored by John Franklin's avatar John Franklin
Browse files

Issue #3308944 by John Franklin: verify the nonce returned by login.gov.

parent 259271e0
No related branches found
No related tags found
No related merge requests found
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
namespace Drupal\login_gov\Plugin\OpenIDConnectClient; namespace Drupal\login_gov\Plugin\OpenIDConnectClient;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Crypt;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\GeneratedUrl; use Drupal\Core\GeneratedUrl;
use Drupal\Core\Link; use Drupal\Core\Link;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\openid_connect\Plugin\OpenIDConnectClientBase; use Drupal\openid_connect\Plugin\OpenIDConnectClientBase;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT; use Firebase\JWT\JWT;
/** /**
...@@ -198,12 +200,14 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase { ...@@ -198,12 +200,14 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase {
'token' => 'https://idp.int.identitysandbox.gov/api/openid_connect/token', 'token' => 'https://idp.int.identitysandbox.gov/api/openid_connect/token',
'userinfo' => 'https://idp.int.identitysandbox.gov/api/openid_connect/userinfo', 'userinfo' => 'https://idp.int.identitysandbox.gov/api/openid_connect/userinfo',
'end_session' => 'https://idp.int.identitysandbox.gov/openid_connect/logout', 'end_session' => 'https://idp.int.identitysandbox.gov/openid_connect/logout',
'certs' => 'https://idp.int.identitysandbox.gov/api/openid_connect/certs',
] : ] :
[ [
'authorization' => 'https://secure.login.gov/openid_connect/authorize', 'authorization' => 'https://secure.login.gov/openid_connect/authorize',
'token' => 'https://secure.login.gov/api/openid_connect/token', 'token' => 'https://secure.login.gov/api/openid_connect/token',
'userinfo' => 'https://secure.login.gov/api/openid_connect/userinfo', 'userinfo' => 'https://secure.login.gov/api/openid_connect/userinfo',
'end_session' => 'https://secure.login.gov/openid_connect/logout', 'end_session' => 'https://secure.login.gov/openid_connect/logout',
'certs' => 'https://secure.login.gov/api/openid_connect/certs',
]; ];
} }
...@@ -260,6 +264,19 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase { ...@@ -260,6 +264,19 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase {
return $this->configuration['private_key']; return $this->configuration['private_key'];
} }
/**
* Get login.gov's public signing key.
*
* @return array|null
* A list of public keys.
*/
protected function getPeerPublicKeys(): ?array {
$endpoints = $this->getEndpoints();
$keys_json = $this->httpClient->get($endpoints['certs'])->getBody()->getContents();
$keys = Json::decode($keys_json);
return JWK::parseKeySet($keys);
}
/** /**
* Generate a one-time use code word, a nonce. * Generate a one-time use code word, a nonce.
* *
...@@ -268,8 +285,6 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase { ...@@ -268,8 +285,6 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase {
* *
* @return string * @return string
* The nonce. * The nonce.
*
* @todo Save the nonce to verify later.
*/ */
protected function generateNonce(int $length = 26): string { protected function generateNonce(int $length = 26): string {
return substr(Crypt::randomBytesBase64($length), 0, $length); return substr(Crypt::randomBytesBase64($length), 0, $length);
...@@ -297,10 +312,13 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase { ...@@ -297,10 +312,13 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase {
*/ */
protected function getUrlOptions(string $scope, GeneratedUrl $redirect_uri): array { protected function getUrlOptions(string $scope, GeneratedUrl $redirect_uri): array {
$options = parent::getUrlOptions($scope, $redirect_uri); $options = parent::getUrlOptions($scope, $redirect_uri);
$nonce = $this->generateNonce();
$options['query'] += [ $options['query'] += [
'acr_values' => $this->generateAcrValue(), 'acr_values' => $this->generateAcrValue(),
'nonce' => $this->generateNonce(), 'nonce' => $nonce,
]; ];
$this->requestStack->getCurrentRequest()->getSession()->set('login_gov.nonce', $nonce);
if ($this->configuration['acr_level'] == '2' && $this->configuration['verified_within']['count']) { if ($this->configuration['acr_level'] == '2' && $this->configuration['verified_within']['count']) {
$options['query']['verified_within'] = $this->configuration['verified_within']['count'] . $this->configuration['verified_within']['units']; $options['query']['verified_within'] = $this->configuration['verified_within']['count'] . $this->configuration['verified_within']['units'];
...@@ -310,6 +328,25 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase { ...@@ -310,6 +328,25 @@ class OpenIDConnectLoginGovClient extends OpenIDConnectClientBase {
return $options; return $options;
} }
/**
* {@inheritdoc}
*/
public function retrieveTokens(string $authorization_code): ?array {
$tokens = parent::retrieveTokens($authorization_code);
// Verify the nonce is the one we sent earlier.
if (!empty($tokens['id_token'])) {
$keys = $this->getPeerPublicKeys();
$decoded_tokens = JWT::decode($tokens['id_token'], $keys);
$session_nonce = $this->requestStack->getCurrentRequest()->getSession()->get('login_gov.nonce');
if (!empty($session_nonce) && ($decoded_tokens->nonce !== $session_nonce)) {
return NULL;
}
}
return $tokens;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment