public function makeTOTP($provider = null) { /*** * Assign a user a multifactor authentication code * * @param string $provider The provider giving 2FA. * @return array with the status in the key "status", errors in "error" and "human_error", * username in "username", and provisioning data in "uri" ***/ if (empty($this->username)) { $this->getUser(); # We MUST have this properly assigned if (empty($this->username)) { return array('status' => false, 'error' => 'Unable to get user.'); } } if ($this->getSecret() !== false) { return array('status' => false, 'error' => '2FA has already been enabled for this user.', 'human_error' => "You've already enabled 2-factor authentication.", 'username' => $this->username); } try { if (!class_exists('Stronghash')) { require_once dirname(__FILE__) . '/../core/stronghash/php-stronghash.php'; } $salt = Stronghash::createSalt(); require_once dirname(__FILE__) . '/../base32/src/Base32/Base32.php'; $secret = Base32::encode($salt); ## The resulting provisioning URI should now be sent to the user ## Flag should be set server-side indicating the change id pending $l = $this->openDB(); $query = 'UPDATE `' . $this->getTable() . '` SET `' . $this->tmpColumn . "`='{$secret}' WHERE `" . $this->userColumn . "`='" . $this->username . "'"; $r = mysqli_query($l, $query); if ($r === false) { return array('status' => false, 'human_error' => 'Database error', 'error' => mysqli_error($l)); } # The data was saved correctly # Let's create the provisioning stuff! self::doLoadOTP(); $totp = new OTPHP\TOTP($secret); $totp->setDigest($this->getDigest()); $totp->setLabel($this->username); $totp->setIssuer($provider); $uri = $totp->getProvisioningURI($label, $provider); # iPhones don't actually accept the full, valid URI $unsafe_uri = urldecode($uri); $uri_args = explode('?', $unsafe_uri); $iphone_uri = $uri_args[0] . '?'; $iphone_args = array(); $iphone_safe_args = array('secret', 'issuer'); foreach (explode('&', $uri_args[1]) as $paramval) { $pv = explode('=', $paramval); $param = $pv[0]; $val = $pv[1]; if (in_array($param, $iphone_safe_args)) { $iphone_args[] = $param . '=' . $val; } } $iphone_uri .= implode('&', $iphone_args); /* $iphone32 = str_replace("=","",$secret_part[1]); */ /* $iphone_uri = $secret_part[0]."secret=".$iphone32; #still no good */ $retarr = self::generateQR($iphone_uri); # Let's get a human-readable secret $human_secret0 = str_replace('=', '', $secret); $i = 0; $human_secret = ''; foreach (str_split($human_secret0) as $char) { $human_secret .= $char; ++$i; if ($i == 4) { $human_secret .= ' '; $i = 0; } } $retarr['secret'] = $secret; $retarr['human_secret'] = $human_secret; $retarr['username'] = $this->username; return $retarr; } catch (Exception $e) { return array('status' => false, 'human_error' => 'Unexpected error in makeTOTP', 'error' => $e->getMessage(), 'username' => $this->username, 'provider' => $provider, 'label' => $totp->getLabel(), 'uri' => $uri, 'secret' => $secret); } }