diff --git a/conf/tresor.conf b/conf/tresor.conf index f254357..0374795 100644 --- a/conf/tresor.conf +++ b/conf/tresor.conf @@ -1,5 +1,8 @@ # Caddyfile for DigiErbe Tresor tresor.example.com { + # enable TLS encryption + tls internal + #PHP php_fastcgi unix//run/php/php-fpm.sock @@ -7,8 +10,9 @@ tresor.example.com { root * /Pfad/Zu/DigiErbe/Tresor/public # Authentication - basic_auth / { - username $2a$14$SetHashedPasswordHere + basic_auth { + ownerName $2a$14$SetHashedPasswordHere + allowedName $2a$14$SetHashedPasswordHere } # Activate file server in Caddy @@ -32,4 +36,4 @@ tresor.example.com { log { output file /var/log/caddy/DigiErbe/tresor.log } -} \ No newline at end of file +} diff --git a/private/keys/allowedUsers.json b/private/keys/allowedUsers.json new file mode 100644 index 0000000..ae9f507 --- /dev/null +++ b/private/keys/allowedUsers.json @@ -0,0 +1,5 @@ +{ + "ownerName": { + "myKey.pem": ["allowedName"] + } +} diff --git a/private/keys/allowed_users.json b/private/keys/allowed_users.json deleted file mode 100644 index e9983be..0000000 --- a/private/keys/allowed_users.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ownerName": { - "myKey.pem": ["alloweduser"] - } -} \ No newline at end of file diff --git a/public/ReqeustManager.php b/public/RequestManager.php old mode 100644 new mode 100755 similarity index 100% rename from public/ReqeustManager.php rename to public/RequestManager.php diff --git a/public/TokenManager.php b/public/TokenManager.php old mode 100644 new mode 100755 index 0eee6da..8835e56 --- a/public/TokenManager.php +++ b/public/TokenManager.php @@ -8,7 +8,8 @@ class TokenManager { /* * VARIABLES */ - private string $storagePath; // Subdirectory under Tresor's /private directory to use to store all the token information + private string $tokenStoragePath; // Subdirectory under Tresor's /public directory to use to store all the token information + private string $keyStoragePath; // Subdirectory under Tresor's /private directory to store all keyfiles private int $expireDays; // number of dates for a token to expire private int $maxDownloads; // maximum number of downloads before a token becomes invalid private int $tokenLength; // number of random bytes to use to create the token name @@ -19,34 +20,44 @@ class TokenManager { */ // initialize the token instance in PHP - public __contruct( + public function __construct( string $storagePath = '/tokens', - int $exireDays = 1, + string $keyStoragePath = '/keys', + int $expireDays = 1, int $maxDownloads = 1, int $tokenLength = 32 ) { - $this->storagePath = $storagePath; - $this->$expireDays = $expireDays; - $this->$maxDownloads = $maxDownloads; - $this->$tokenLength = $tokenLength; + $this->tokenStoragePath = __DIR__ . $storagePath; + $this->keyStoragePath = substr(__DIR__, 0, strrpos(__DIR__, '/') + 1) . 'private' . $keyStoragePath; + $this->expireDays = $expireDays; + $this->maxDownloads = $maxDownloads; + $this->tokenLength = $tokenLength; + + if(!is_dir($this->tokenStoragePath)) + mkdir($this->tokenStoragePath, 0755, true); } // generate a new token and save it to the file system - public generateToken( + public function generateToken( string $filename, string $ownerName, string $allowedUser ) { - return (['error'=>'no token generated', 'status'=>'500']); + return (['error'=>'no token generated', 'status'=>500]); } // retrieve/download the key file connected with the token - public retrieveToken(string $tokenName) { + public function retrieveToken(string $tokenName) { + $result = $this->validateToken($tokenName); + if(!isset($result['success'])) + return ['error'=>'token invalid', 'status'=>422]; + // send the file name of the designated key for download + return ['success'=>$result['success'], 'status'=>200]; } // check a token and remove it when it's expired - public cleanupToken(string $tokenName) { + public function cleanupToken(string $tokenName) { } @@ -55,12 +66,49 @@ class TokenManager { */ // check whether a token is still valid and accessible by the user requesting it - private validateToken($tokenName) { - return null; + private function validateToken($tokenName) { + // check if tokenName is a valid hexadecimal string + if(!ctype_xdigit($tokenName)) + return false; + + // check if token file exists and read data if so + $tokenPath = $this->getTokenFilePath($tokenName); + if(!file_exists($tokenPath)) + return false; + $tokenData = json_decode(file_get_contents($tokenPath)); + + // check expiry of the token + if( new DateTime() > new DateTime($tokenData->expires)) + return false; + + // safety-check if requesting user name is matching the allowed characters (a-zA-Z0-9) + if(preg_match('/[^a-zA-Z0-9]/', $_SERVER['PHP_AUTH_USER'])) + return false; + // check if the requesting user is allowed to query the token + if(levenshtein($_SERVER['PHP_AUTH_USER'], $tokenData->allowedUser)) + return false; + // check if requesting user is allowed to retrieve the key + $allowedUsersFilePath = __DIR__ . '/../private/keys/allowedUsers.json'; + if(!file_exists($allowedUsersFilePath)) + return false; + $allowedUsers = json_decode(file_get_contents($allowedUsersFilePath))->{$tokenData->owner}->{$tokenData->file}; + if(array_search($_SERVER['PHP_AUTH_USER'], $allowedUsers, true) === false) + return false; + + //all checks completed successfully + $path = substr( __DIR__, 0, strrpos(__DIR__, '/') + 1 ); + return ['success'=>$this->keyStoragePath . '/' . $tokenData->owner . '/' . $tokenData->file, 'status'=>200]; } - private getDownloadFileInfo ($tokenName) { + private function getDownloadFileInfo ($tokenName) { $fileInfo = []; return null; } -} \ No newline at end of file + + private function getTokenFilePath($tokenName) { + $filePath = $this->tokenStoragePath . '/' . $tokenName . '.json'; + if(!file_exists($filePath)) + return false; + return $filePath; + } +} diff --git a/public/index.php b/public/index.php old mode 100644 new mode 100755 index 91f1237..64f76bd --- a/public/index.php +++ b/public/index.php @@ -12,13 +12,19 @@ $method = $_SERVER['REQUEST_METHOD']; $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $path = str_replace('/index.php', '', $path); $path = rtrim($path, '/'); +$path = ltrim($path, '/'); + +$tokenManager = new TokenManager(); switch ($path) { - case '/request': // request access to another user's emergency / legacy key file - request_access(); + case 'request': // request access to another user's emergency / legacy key file + requestAccess(); break; - case 'deny': //deny access - deny_access(); + case 'deny': // deny requested access + denyAccess(); + break; + case 'download': + downloadKey(); break; default: user_interface(); @@ -29,21 +35,51 @@ function ReturnJsonResponse($data, $status = 200) { http_response_code($status); header('Content-Type: application/json'); header('Cache-Control: no-cache, no-store, must-revalidate'); - echo json_encode($data); + print json_encode($data); exit; } -function request_access() { +function requestAccess() { $data = ['request access' => 'request not allowed']; ReturnJsonResponse($data, 403); } -function deny_access() { +function denyAccess() { $data = ['deny access'=>'emergency user request revoked']; ReturnJsonResponse($data); } +function downloadKey() { + global $tokenManager; + if(!isset($_REQUEST['token']) || !ctype_xdigit($_REQUEST['token'])) + ReturnJsonResponse(['error'=>'missing or invalid token'], 400); + $result = $tokenManager->retrieveToken($_REQUEST['token']); + if(isset($result['error'])) + ReturnJsonResponse(['error'=>$result['error']], $result['status']); + + if(!isset($result['success'])) + ReturnJsonResponse(['error'=>'token could not be validated'], 400); + + //check if returned keyfile exists + if(!file_exists($result['success'])) + ReturnJsonResponse(['error'=>'keyfile not found'], 404); + + $filePath = $result['success']; + $fileName = basename($filePath); + $fileSize = filesize($filePath); + + header('Content-Type: application/octet-stream'); + header("Content-Disposition: attachment; filename=\"{$fileName}\""); + header("Content-Length: {$fileSize}"); + header('Cache-Control: no-cache, no-store, must-revalidate'); + header('Pragma: no-cache'); + header('Expires: 0'); + + readfile($filePath); + exit; +} + function user_interface() { print('