/** * Creates a new client record and exports the data to the configured client export directory * * @return mixed The new client's ID or false on failure */ public function createClient() { $s_NewID = ClientEntity::getNewID(); $s_NewPath = $this->client_export_path . $s_NewID . DIRECTORY_SEPARATOR; $s_NewDBPath = $s_NewPath . $s_NewID . '.db'; $s_NewConfigPath = $s_NewPath . 'config.xml'; /* Create client directory */ if (!file_exists($s_NewPath) && !mkdir($s_NewPath, (int) $this->dir_umask, true)) { return false; } elseif (!is_writeable($s_NewPath)) { return false; } /* Initialize client data */ $s_NewDBKey = sha1(openssl_random_pseudo_bytes(4096)); $a_NewData['id'] = $s_NewID; $a_NewData['server_public_key'] = (string) $this->public_key; // Randomizing the initial counter sequence makes brute force attacks harder $a_NewData['counter'] = hexdec(bin2hex(openssl_random_pseudo_bytes(1))); $a_NewData['key'] = sha1(openssl_random_pseudo_bytes(4096)); $a_NewData['password_length'] = (int) $this->password_length; $a_NewData['status'] = ClientEntity::ACTIVE; $a_NewData['failed_auths'] = 0; /* Write the files to the client directory */ $o_ClientConfig = new SimpleXMLElement('<otpclient></otpclient>'); $o_ClientConfig->addChild('id', $a_NewData['id']); $o_ClientConfig->addChild('db_key', $s_NewDBKey); $o_ClientConfig->addChild('db_path', $s_NewDBPath); file_put_contents($s_NewConfigPath, $o_ClientConfig->asXML()); chmod($s_NewConfigPath, (int) $this->file_umask); copy($this->client_export_path . 'OTPClient.php', $s_NewPath . 'OTPClient.php'); chmod($s_NewPath . 'OTPClient.php', (int) $this->file_umask); copy($this->client_export_path . 'ClientEntity.php', $s_NewPath . 'ClientEntity.php'); chmod($s_NewPath . 'ClientEntity.php', (int) $this->file_umask); // Create client's database file $o_ClientDB = new PDO('sqlite:' . $s_NewDBPath); $o_ClientDB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); chmod($s_NewDBPath, (int) $this->file_umask); /* Create the new client records */ ClientEntity::save($a_NewData, $this->_o_DB, $this->db_key); $o_Result = $this->_o_DB->query("SELECT sql FROM sqlite_master WHERE tbl_name='clients'"); $a_Result = $o_Result->fetch(PDO::FETCH_NUM); $s_CreateTableQuery = $a_Result[0]; $o_ClientDB->exec($s_CreateTableQuery); ClientEntity::save($a_NewData, $o_ClientDB, $s_NewDBKey); /* The RFC specifies that the client should increment its counter before sending a password, but the server should only increment the counter AFTER a successful authentication attempt. This ensures that the server-side record is one count ahead of the client-side record to facilitate this requirement */ $NewClient = new ClientEntity($this->_o_DB, $a_NewData['id'], $this->db_key); $NewClient->counter++; return $s_NewConfigPath; }
/** * @test getNewID */ public function testGetNewID() { $s_NewID = ClientEntity::getNewID(); $this->assertTrue(strlen($s_NewID) == 36); $a_IDParts = explode('-', $s_NewID); $this->assertTrue(count($a_IDParts) == 5); $this->assertTrue(strlen($a_IDParts[0]) == 8); $this->assertTrue(strlen($a_IDParts[1]) == 4); $this->assertTrue(strlen($a_IDParts[2]) == 4); $this->assertTrue(substr($a_IDParts[2], 0, 1) == 4); $this->assertTrue(strlen($a_IDParts[3]) == 4); $this->assertTrue(strlen($a_IDParts[4]) == 12); }