Exemplo n.º 1
0
 /**
  * Creates an association for OpenID versions 1 and 2.
  *
  * This function calls {@link DiffieHellman::associateAsServer()} where required, to 
  * generate the cryptographic values required for an association response.
  *
  * @param int $mode either ASSOCIATION_SHARED or ASSOCIATION_PRIVATE
  * @param string $assoc_type a valid OpenID association type
  * @link http://openid.net/specs/openid-authentication-1_1.html#anchor14, http://openid.net/specs/openid-authentication-2_0.html#anchor20
  */
 function __construct($mode = self::ASSOCIATION_SHARED, $assoc_type = 'HMAC-SHA1')
 {
     $rand = new Random();
     $assoc_types = self::getAssociationTypes();
     $this->assoc_handle = $rand->id();
     $this->assoc_type = $assoc_type;
     $mac_size = $assoc_types[$assoc_type]['mac_size'];
     $this->mac_key = base64_encode($rand->bytes($mac_size));
     $this->created = time();
     if ($mode == self::ASSOCIATION_PRIVATE) {
         $this->private = true;
     }
 }
Exemplo n.º 2
0
 /**
  * Creates a auto login cookie.  The login cookie will be based on the
  * current log in user.
  *
  * @param string $id the ID of the series of auto login cookies,  Cookies
  * belonging to the same user and computer have the same ID.  If none is specified,
  * one will be generated
  * @param int $expires the time at which the cookie will expire.  If none is specified
  * the time specified in {@link SIMPLEID_REMEMBERME_EXPIRES_IN} will be
  * used
  *
  */
 protected function createCookie($id = NULL, $expires = NULL)
 {
     $user = $this->auth->getUser();
     $rand = new Random();
     if ($expires == NULL) {
         $this->logger->log(LogLevel::DEBUG, 'Automatic login token created for ' . $user['uid']);
     } else {
         $this->logger->log(LogLevel::DEBUG, 'Automatic login token renewed for ' . $user['uid']);
     }
     if ($id == NULL) {
         $id = $rand->id();
     }
     if ($expires == NULL) {
         $expires = time() + SIMPLEID_LONG_TOKEN_EXPIRES_IN;
     }
     $data = array('typ' => 'rememberme', 'id' => $id, 'uid' => $user['uid'], 'exp' => $expires, 'uaid' => $this->auth->assignUAID());
     $token = new SecurityToken();
     $cookie = $token->generate($data);
     $this->f3->set('COOKIE.' . $this->cookie_name, $cookie, SIMPLEID_LONG_TOKEN_EXPIRES_IN);
 }
Exemplo n.º 3
0
 /**
  * Detects the current installed version of SimpleID, selects the individual upgrade
  * functions applicable to this upgrade and displays the upgrade
  * selection page.
  */
 function select()
 {
     global $upgrade_access_check;
     $token = new SecurityToken();
     if ($this->f3->exists('POST.tk') === false || !$token->verify($this->f3->get('POST.tk'), 'upgrade_info')) {
         $this->f3->set('message', $this->t('SimpleID detected a potential security attack.  Please try again.'));
         $this->info();
         return;
     }
     $tpl = new \Template();
     $cache = \Cache::instance();
     $cache->reset('.upgrade');
     $list = $this->getUpgradeList();
     if (count($list) == 0) {
         $this->f3->set('script_complete', $this->t('Your SimpleID installation is up-to-date.  This script is complete.'));
     } else {
         $rand = new Random();
         $upgid = $rand->id();
         $cache->set($upgid . '.upgrade', array('list' => $list, 'results' => ''));
         $this->f3->set('upgid', $upgid);
         $this->f3->set('tk', $token->generate('upgrade_selection', SecurityToken::OPTION_BIND_SESSION));
         $this->f3->set('click_continue', $this->t('Click <strong>Continue</strong> to proceed with the upgrade.'));
         $this->f3->set('continue_button', $this->t('Continue'));
     }
     $this->f3->set('original_version', $this->getVersion());
     $this->f3->set('this_version', SIMPLEID_VERSION);
     $this->f3->set('version_detected', $this->t('The version of SimpleID you are updating from has been automatically detected.'));
     $this->f3->set('original_version_label', $this->t('Original version'));
     $this->f3->set('this_version_label', $this->t('Upgrade version'));
     $this->f3->set('edit_upgrade_php', $this->t('Remember to edit upgrade.php to check <code>$upgrade_access_check</code> back to <code>FALSE</code>.'));
     $this->f3->set('title', $this->t('Upgrade'));
     $this->f3->set('page_class', 'dialog-page');
     $this->f3->set('layout', 'upgrade_selection.html');
     print $tpl->render('page.html');
 }
 /**
  * Registration endpoint
  */
 public function register()
 {
     $rand = new Random();
     $response = new Response();
     $this->checkHttps('error');
     $this->logger->log(LogLevel::DEBUG, 'SimpleID\\Protocols\\Connect\\ConnectClientRegistrationModule->register');
     // Access token OR rate limit
     if (!$this->isTokenAuthorized(self::CLIENT_REGISTRATION_INIT_SCOPE)) {
         $limiter = new RateLimiter('connect_register');
         if (!$limiter->throttle()) {
             header('Retry-After: ' . $limiter->getInterval());
             // We never display a log for rate limit errors
             $response->setError('invalid_request', 'client has been blocked from making further requests')->renderJSON(429);
             return;
         }
     }
     if (!$this->f3->exists('BODY')) {
         $response->setError('invalid_request')->renderJSON();
         return;
     }
     $request = json_decode($this->f3->get('BODY'), true);
     if ($request == null) {
         $response->setError('invalid_request', 'unable to parse body')->renderJSON();
         return;
     }
     if (!isset($request['redirect_uris'])) {
         $response->setError('invalid_redirect_uri', 'redirect_uris missing')->renderJSON();
         return;
     }
     // Verify redirect_uri based on application_type
     $application_type = isset($request['application_type']) ? $request['application_type'] : 'web';
     $grant_types = isset($request['grant_types']) ? $request['grant_types'] : array('authorization_code');
     foreach ($request['redirect_uris'] as $redirect_uri) {
         $parts = parse_url($redirect_uri);
         if (isset($parts['fragment'])) {
             $response->setError('invalid_redirect_uri', 'redirect_uris cannot contain a fragment')->renderJSON();
             return;
         }
         if ($application_type == 'web' && in_array('implicit', $grant_types)) {
             if (strtolower($parts['scheme']) != 'https' || strtolower($parts['host']) == 'localhost' && $parts['host'] == '127.0.0.1') {
                 $response->setError('invalid_redirect_uri', 'implicit grant type must use https URIs')->renderJSON();
                 return;
             }
         } elseif ($application_type == 'native') {
             // Native Clients MUST only register redirect_uris using custom URI schemes or URLs using the http: scheme with localhost as the hostname.
             // Authorization Servers MAY place additional constraints on Native Clients.
             // Authorization Servers MAY reject Redirection URI values using the http scheme, other than the localhost case for Native Clients.
             // The Authorization Server MUST verify that all the registered redirect_uris conform to these constraints. This prevents sharing a Client ID across different types of Clients.
             if (strtolower($parts['scheme']) == 'http' && (strtolower($parts['host']) != 'localhost' || $parts['host'] != '127.0.0.1') || strtolower($parts['scheme']) == 'https') {
                 $response->setError('invalid_redirect_uri', 'native clients cannot use https URIs')->renderJSON();
                 return;
             }
         }
     }
     // Verify sector_identifier_url
     $subject_type = isset($request['subject_type']) ? $request['subject_type'] : 'public';
     if (isset($request['sector_identifier_uri'])) {
         if (!$this->verifySectorIdentifier($request['sector_identifier_uri'], $request['redirect_uris'])) {
             $response->setError('invalid_client_metadata', 'cannot verify sector_identifier_uri')->renderJSON();
             return;
         }
     }
     $client = new ConnectDynamicClient();
     $client_id = $client->getStoreID();
     // Map data
     foreach ($request as $name => $value) {
         $parts = explode('#', $name, 2);
         $client_path = isset(self::$metadata_map[$parts[0]]) ? self::$metadata_map[$parts[0]] : 'connect.' . $parts[0];
         if (isset($parts[1])) {
             $client_path .= '#' . $locale;
         }
         $client->pathSet($client_path, $value);
     }
     $client->fetchJWKs();
     $response->loadData($request);
     $response->loadData(array('client_id' => $client->getStoreID(), 'registration_client_uri' => $this->getCanonicalURL('connect/client/' . $client->getStoreID()), 'client_id_issued_at' => time()));
     if ($client['oauth']['token_endpoint_auth_method'] != 'none') {
         $client->pathSet('oauth.client_secret', $rand->secret());
         $response['client_secret'] = $client['oauth']['client_secret'];
         $response['client_secret_expires_at'] = 0;
     }
     $store = StoreManager::instance();
     $store->saveClient($client);
     $this->logger->log(LogLevel::INFO, 'Created dynamic client: ' . $client_id);
     $auth = new Authorization($client, $client, self::CLIENT_REGISTRATION_ACCESS_SCOPE);
     $store->saveAuth($auth);
     $token = $auth->issueAccessToken(array(self::CLIENT_REGISTRATION_ACCESS_SCOPE));
     $response['registration_access_token'] = $token['access_token'];
     $this->f3->status(201);
     $response->renderJSON();
 }
Exemplo n.º 5
0
 public function __construct($data = array())
 {
     parent::__construct($data);
     $rand = new Random();
     $this->cid = '_' . $rand->id() . '.oauth';
 }
Exemplo n.º 6
0
 /**
  * Gets the site-specific key for generating identifiers.
  *
  * If the key does not exist, it is automatically generated.
  *
  * @return string site-specific key as a binary string
  */
 private static function getOpaqueToken()
 {
     $store = StoreManager::instance();
     $opaque_token = $store->getSetting('opaque-token');
     if ($opaque_token == NULL) {
         $rand = new Random();
         $opaque_token = $rand->bytes(16);
         $store->setSetting('opaque-token', base64_encode($opaque_token));
     } else {
         $opaque_token = base64_decode($opaque_token);
     }
     return $opaque_token;
 }
Exemplo n.º 7
0
 /**
  * Gets the site-specific encryption and signing key.
  *
  * If the key does not exist, it is automatically generated.
  *
  * @return string the site-specific encryption and signing key
  * as a base64url encoded string
  */
 protected static function getKey()
 {
     $store = StoreManager::instance();
     $key = $store->getSetting('oauth-fernet');
     if ($key == NULL) {
         $rand = new Random();
         $key = Fernet::base64url_encode($rand->bytes(32));
         $store->setSetting('oauth-fernet', $key);
     }
     return $key;
 }
Exemplo n.º 8
0
 /**
  * Creates an authorization code.
  *
  * Once the authorization code object has been created, the code can be retrieved using
  * the {@link getCode()} method.
  *
  * @param Authorization $authorization the authorization that wishes to generate
  * this code
  * @param string|null $redirect_uri the redirect_uri parameter in the authorisation request, if
  * present
  * @param array $scope the allowed scope - this should be a subset of the scope provided by the
  * authorization
  * @param array $additional additional data to be stored in the authorization code
  * @return Code the authorization code object
  */
 public static function create($authorization, $redirect_uri, $scope, $additional = array())
 {
     $code = new Code();
     $rand = new Random();
     $cache = \Cache::instance();
     $code->cid = $rand->id();
     $code->aid = $authorization->getStoreID();
     $code->auth_state = $authorization->getAuthState();
     $code->redirect_uri = $redirect_uri;
     $code->scope = !is_array($scope) ? explode(' ', $scope) : $scope;
     $code->additional = $additional;
     $code->expires = time() + SIMPLEID_INSTANT_TOKEN_EXPIRES_IN;
     $cache->set($code->getCode() . '.code', $code, SIMPLEID_INSTANT_TOKEN_EXPIRES_IN);
     $code->is_valid = true;
     return $code;
 }
Exemplo n.º 9
0
 /**
  * Resets the current authorisation state
  *
  * @param string the new authorisation state
  */
 public function resetAuthState()
 {
     $rand = new Random();
     $this->auth_state = substr($rand->id(), -9);
     return $this->auth_state;
 }
Exemplo n.º 10
0
 /**
  * Displays the page used to set up login verification using one-time
  * passwords.
  */
 public function setup()
 {
     $auth = AuthManager::instance();
     $store = StoreManager::instance();
     $user = $auth->getUser();
     $tpl = new \Template();
     $token = new SecurityToken();
     // Require HTTPS, redirect if necessary
     $this->checkHttps('redirect', true);
     if (!$auth->isLoggedIn()) {
         $this->f3->reroute('/my/dashboard');
         return;
     }
     if ($this->f3->get('POST.op') == $this->t('Disable')) {
         if ($this->f3->exists('POST.tk') === false || !$token->verify($this->f3->get('POST.tk'), 'otp')) {
             $this->f3->set('message', $this->t('SimpleID detected a potential security attack.  Please try again.'));
             $this->f3->mock('GET /my/dashboard');
             return;
         }
         if (isset($user['otp'])) {
             unset($user['otp']);
             $store->saveUser($user);
         }
         $this->f3->set('message', $this->t('Login verification has been disabled.'));
         $this->f3->mock('GET /my/dashboard');
         return;
     } elseif ($this->f3->get('POST.op') == $this->t('Verify')) {
         $params = $token->getPayload($this->f3->get('POST.otp_params'));
         $this->f3->set('otp_params', $this->f3->get('POST.otp_params'));
         if ($this->f3->exists('POST.tk') === false || !$token->verify($this->f3->get('POST.tk'), 'otp')) {
             $this->f3->set('message', $this->t('SimpleID detected a potential security attack.  Please try again.'));
             page_dashboard();
             return;
         } elseif ($this->f3->exists('POST.otp') === false || $this->f3->get('POST.otp') == '') {
             $this->f3->set('message', $this->t('You need to enter the verification code to complete enabling login verification.'));
         } elseif ($this->verifyOTP($params, $this->f3->get('POST.otp'), 10) === false) {
             $this->f3->set('message', $this->t('The verification code is not correct.'));
         } else {
             $user['otp'] = $params;
             $store->saveUser($user);
             $this->f3->set('message', $this->t('Login verification has been enabled.'));
             $this->f3->mock('GET /my/dashboard');
             return;
         }
     } else {
         $rand = new Random();
         $params = array('type' => 'totp', 'secret' => $rand->bytes(10), 'algorithm' => 'sha1', 'digits' => 6, 'period' => 30, 'drift' => 0, 'remember' => array());
         $this->f3->set('otp_params', $token->generate($params, SecurityToken::OPTION_BIND_SESSION));
     }
     $secret = new BigNum($params['secret'], 256);
     $code = strtr($secret->val(32), '0123456789abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567');
     $code = str_repeat('A', 16 - strlen($code)) . $code;
     for ($i = 0; $i < strlen($code); $i += 4) {
         $this->f3->set('secret' . ($i + 1), substr($code, $i, 4));
     }
     $url = 'otpauth://totp/SimpleID?secret=' . $code . '&digits=' . $params['digits'] . '&period=' . $params['period'];
     $this->f3->set('qr', addslashes($url));
     $this->f3->set('about_otp', $this->t('Login verification adds an extra layer of protection to your account. When enabled, you will need to enter an additional security code whenever you log into SimpleID.'));
     $this->f3->set('otp_warning', $this->t('<strong>WARNING:</strong> If you enable login verification and lose your authenticator app, you will need to <a href="!url">edit your identity file manually</a> before you can log in again.', array('!url' => 'http://simpleid.koinic.net/docs/2/common_problems/#otp')));
     $this->f3->set('setup_otp', $this->t('To set up login verification, following these steps.'));
     $this->f3->set('download_app', $this->t('Download an authenticator app that supports TOTP for your smartphone, such as Google Authenticator.'));
     $this->f3->set('add_account', $this->t('Add your SimpleID account to authenticator app using this key.  If you are viewing this page on your smartphone you can use <a href="!url">this link</a> or scan the QR code to add your account.', array('!url' => $url)));
     $this->f3->set('verify_code', $this->t('To check that your account has been added properly, enter the verification code from your phone into the box below, and click Verify.'));
     $this->f3->set('tk', $token->generate('otp', SecurityToken::OPTION_BIND_SESSION));
     $this->f3->set('otp_label', $this->t('Verification code:'));
     $this->f3->set('submit_button', $this->t('Verify'));
     $this->f3->set('page_class', 'dialog-page');
     $this->f3->set('title', $this->t('Login Verification'));
     $this->f3->set('framekiller', true);
     $this->f3->set('layout', 'auth_otp_setup.html');
     print $tpl->render('page.html');
 }
Exemplo n.º 11
0
 /**
  * Generates a random integer, which will be used to derive a private key
  * for Diffie-Hellman key exchange.  The integer must be less than $stop
  *
  * @param BigNum $stop a prime number as a bignum
  * @return BigNum the random integer as a bignum
  */
 private function generateRandom($stop)
 {
     $duplicate_cache = array();
     $rand = new Random();
     // Used as the key for the duplicate cache
     $rbytes = $stop->val(256);
     if (array_key_exists($rbytes, $duplicate_cache)) {
         list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
     } else {
         if ($rbytes[0] == "") {
             $nbytes = strlen($rbytes) - 1;
         } else {
             $nbytes = strlen($rbytes);
         }
         $mxrand = new BigNum(256);
         $mxrand = $mxrand->pow(new BigNum($nbytes));
         // If we get a number less than this, then it is in the
         // duplicated range.
         $duplicate = $mxrand->mod($stop);
         if (count($duplicate_cache) > 10) {
             $duplicate_cache = array();
         }
         $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
     }
     do {
         $bytes = "" . $rand->bytes($nbytes);
         $n = new BigNum($bytes, 256);
         // Keep looping if this value is in the low duplicated range
     } while ($n->cmp($duplicate) < 0);
     return $n->mod($stop);
 }
Exemplo n.º 12
0
 /**
  * Gets the site-specific encryption and signing key.
  *
  * If the key does not exist, it is automatically generated.
  *
  * @return string the site-specific encryption and signing key
  * as a base64url encoded string
  */
 private static function getSiteToken()
 {
     $store = StoreManager::instance();
     $site_token = $store->getSetting('site-token');
     if ($site_token == NULL) {
         $rand = new Random();
         $site_token = Fernet::base64url_encode($rand->bytes(32));
         $store->setSetting('site-token', $site_token);
     }
     return $site_token;
 }
Exemplo n.º 13
0
 /**
  * Builds the JOSE response.  This will return one of the following:
  *
  * - A JSON encoded string, if {@link $signed_response_alg} and
  *   {@link $encrypted_response_alg} are both null
  * - A signed JWT (JWS), if {@link $signed_response_alg} is set
  * - A JWE containing a nested JWT, if both {@link $signed_response_alg}
  *   and {@link $encrypted_response_alg} are set
  *
  * @param SimpleJWT\Keys\KeySet $set the key set used to sign and/or
  * encrypt the token.  If set to null, the default set of keys
  * configured for the client and the server are loaded
  * @return string the response body
  */
 function buildJOSE($set = null)
 {
     $rand = new Random();
     $typ = $this->getType();
     if ($typ == 'json') {
         return json_encode($this->container);
     }
     if ($set == null) {
         $builder = new KeySetBuilder($client);
         $set = $builder->addClientSecret()->addClientPublicKeys()->addServerPrivateKeys()->toKeySet();
     }
     $headers = array_merge($this->headers, array('alg' => $this->signed_response_alg));
     $claims = array_merge($this->container, array('iss' => $this->issuer, 'aud' => $this->client->getStoreID(), 'jti' => $rand->id()));
     $jwt = new JWT($headers, $claims);
     try {
         $token = $jwt->encode($set);
     } catch (CryptException $e) {
         return null;
     }
     if ($typ == 'jwt') {
         return $token;
     }
     $headers = array('alg' => $this->encrypted_response_alg, 'enc' => $this->encrypted_response_enc, 'cty' => 'JWT');
     $jwe = new JWE($headers, $token);
     try {
         return $jwe->encrypt($set);
     } catch (CryptException $e) {
         return null;
     }
 }
Exemplo n.º 14
0
 /**
  * Returns an OpenID response indicating a positive assertion.
  *
  * @param Request $request the OpenID request
  * @param int $result the authentication result providing the positive
  * assertion
  * @return Response an OpenID response with a positive assertion
  * @link http://openid.net/specs/openid-authentication-1_1.html#anchor17, http://openid.net/specs/openid-authentication-1_1.html#anchor23, http://openid.net/specs/openid-authentication-2_0.html#positive_assertions
  */
 protected function createOKResponse($request)
 {
     $rand = new Random();
     $response = new Response($request);
     $response->setArray(array('mode' => 'id_res', 'op_endpoint' => $this->getCanonicalURL(), 'response_nonce' => $rand->openIDNonce()));
     if (isset($request['openid.assoc_handle'])) {
         $response['assoc_handle'] = $request['openid.assoc_handle'];
     }
     if (isset($request['openid.identity'])) {
         $response['identity'] = $request['openid.identity'];
     }
     if (isset($request['openid.return_to'])) {
         $response['return_to'] = $request['openid.return_to'];
     }
     if ($request->getVersion() == Message::OPENID_VERSION_2 && isset($request['openid.claimed_id'])) {
         $response['claimed_id'] = $request['openid.claimed_id'];
     }
     $this->mgr->invokeAll('openIDResponse', true, $request, $response);
     $this->logger->log(LogLevel::INFO, 'OpenID authentication response', $response->toArray());
     return $response;
 }
Exemplo n.º 15
0
 /**
  * Assigns and returns a unique login state for the current
  * authenticated session with user agent (UALS).
  *
  * A UALS uniquely identifies the current authenticated session with
  * the user agent (e.g. browser).  It is reset with each successful
  * login and logout.  The cookie associated with a UALS is only
  * valid for the current session.
  *
  * This function will look for a cookie sent by the user agent with
  * the name returned by {@link getCookieName()} with a suffix
  * of uals.  If the cookie does not exist, it will generate a
  * UALS and return it to the user agent with a Set-Cookie
  * response header.
  *
  * @param bool $reset true to reset the UALS
  * @return string the UALS
  */
 public function assignUALoginState($reset = false)
 {
     $name = $this->getCookieName('uals');
     if ($this->f3->exists('COOKIE.' . $name) === true && !$reset) {
         $this->ua_login_state = $this->f3->get('COOKIE.' . $name);
     } else {
         $rand = new Random();
         $opaque = new OpaqueIdentifier();
         $this->ua_login_state = $opaque->generate($this->assignUAID() . ':' . $rand->id());
         // We don't use f3->set->COOKIE, as this automatically sets the cookie to be httponly
         // We want this to be script readable.
         setcookie($this->getCookieName('uals'), $this->ua_login_state, 0, $this->f3->get('BASE'), '', true, false);
     }
     return $this->ua_login_state;
 }