2015-08-24 09:56:04 -04:00
< ? php
/**
2016-07-21 10:49:16 -04:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2019-12-03 13:57:53 -05:00
* @ author Bjoern Schiessle < bjoern @ schiessle . org >
2016-05-26 13:56:05 -04:00
* @ author Björn Schießle < bjoern @ schiessle . org >
2020-03-31 04:49:10 -04:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2017-11-06 09:56:42 -05:00
* @ author Kenneth Newwood < kenneth @ newwood . name >
2016-07-21 10:49:16 -04:00
* @ author Morris Jobke < hey @ morrisjobke . de >
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2016-01-12 09:02:16 -05:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
2015-08-24 09:56:04 -04:00
*
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License , version 3 ,
2019-12-03 13:57:53 -05:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2015-08-24 09:56:04 -04:00
*
*/
namespace OCA\Encryption\Crypto ;
use OC\Encryption\Exceptions\DecryptionFailedException ;
use OC\Files\View ;
use OCA\Encryption\KeyManager ;
use OCA\Encryption\Users\Setup ;
2016-03-02 05:16:53 -05:00
use OCA\Encryption\Util ;
2015-08-24 09:56:04 -04:00
use OCP\IConfig ;
use OCP\IL10N ;
2023-01-25 05:42:45 -05:00
use OCP\IUser ;
2015-08-24 09:56:04 -04:00
use OCP\IUserManager ;
2023-01-25 05:42:45 -05:00
use OCP\L10N\IFactory ;
use OCP\Mail\Headers\AutoSubmitted ;
2015-08-24 09:56:04 -04:00
use OCP\Mail\IMailer ;
use OCP\Security\ISecureRandom ;
use Symfony\Component\Console\Helper\ProgressBar ;
use Symfony\Component\Console\Helper\QuestionHelper ;
use Symfony\Component\Console\Helper\Table ;
use Symfony\Component\Console\Input\InputInterface ;
use Symfony\Component\Console\Output\OutputInterface ;
use Symfony\Component\Console\Question\ConfirmationQuestion ;
class EncryptAll {
/** @var Setup */
protected $userSetup ;
/** @var IUserManager */
protected $userManager ;
/** @var View */
protected $rootView ;
/** @var KeyManager */
protected $keyManager ;
2016-03-02 05:16:53 -05:00
/** @var Util */
protected $util ;
2015-08-24 09:56:04 -04:00
/** @var array */
protected $userPasswords ;
/** @var IConfig */
protected $config ;
/** @var IMailer */
protected $mailer ;
/** @var IL10N */
protected $l ;
2023-01-25 05:42:45 -05:00
/** @var IFactory */
protected $l10nFactory ;
2015-08-24 09:56:04 -04:00
/** @var QuestionHelper */
protected $questionHelper ;
/** @var OutputInterface */
protected $output ;
/** @var InputInterface */
protected $input ;
/** @var ISecureRandom */
protected $secureRandom ;
public function __construct (
Setup $userSetup ,
IUserManager $userManager ,
View $rootView ,
KeyManager $keyManager ,
2016-03-02 05:16:53 -05:00
Util $util ,
2015-08-24 09:56:04 -04:00
IConfig $config ,
IMailer $mailer ,
IL10N $l ,
2023-01-25 05:42:45 -05:00
IFactory $l10nFactory ,
2015-08-24 09:56:04 -04:00
QuestionHelper $questionHelper ,
ISecureRandom $secureRandom
) {
$this -> userSetup = $userSetup ;
$this -> userManager = $userManager ;
$this -> rootView = $rootView ;
$this -> keyManager = $keyManager ;
2016-03-02 05:16:53 -05:00
$this -> util = $util ;
2015-08-24 09:56:04 -04:00
$this -> config = $config ;
$this -> mailer = $mailer ;
$this -> l = $l ;
2023-01-25 05:42:45 -05:00
$this -> l10nFactory = $l10nFactory ;
2015-08-24 09:56:04 -04:00
$this -> questionHelper = $questionHelper ;
$this -> secureRandom = $secureRandom ;
// store one time passwords for the users
2020-03-26 04:30:18 -04:00
$this -> userPasswords = [];
2015-08-24 09:56:04 -04:00
}
/**
* start to encrypt all files
*
* @ param InputInterface $input
* @ param OutputInterface $output
*/
public function encryptAll ( InputInterface $input , OutputInterface $output ) {
$this -> input = $input ;
$this -> output = $output ;
$headline = 'Encrypt all files with the ' . Encryption :: DISPLAY_NAME ;
$this -> output -> writeln ( " \n " );
$this -> output -> writeln ( $headline );
$this -> output -> writeln ( str_pad ( '' , strlen ( $headline ), '=' ));
$this -> output -> writeln ( " \n " );
2016-03-02 05:16:53 -05:00
if ( $this -> util -> isMasterKeyEnabled ()) {
$this -> output -> writeln ( 'Use master key to encrypt all files.' );
$this -> keyManager -> validateMasterKey ();
} else {
//create private/public keys for each user and store the private key password
$this -> output -> writeln ( 'Create key-pair for every user' );
$this -> output -> writeln ( '------------------------------' );
$this -> output -> writeln ( '' );
$this -> output -> writeln ( 'This module will encrypt all files in the users files folder initially.' );
$this -> output -> writeln ( 'Already existing versions and files in the trash bin will not be encrypted.' );
$this -> output -> writeln ( '' );
$this -> createKeyPairs ();
}
2015-08-24 09:56:04 -04:00
2017-09-11 06:52:25 -04:00
// output generated encryption key passwords
2016-03-02 05:16:53 -05:00
if ( $this -> util -> isMasterKeyEnabled () === false ) {
//send-out or display password list and write it to a file
$this -> output -> writeln ( " \n " );
$this -> output -> writeln ( 'Generated encryption key passwords' );
$this -> output -> writeln ( '----------------------------------' );
$this -> output -> writeln ( '' );
$this -> outputPasswords ();
}
2017-09-11 06:52:25 -04:00
//setup users file system and encrypt all files one by one (take should encrypt setting of storage into account)
$this -> output -> writeln ( " \n " );
$this -> output -> writeln ( 'Start to encrypt users files' );
$this -> output -> writeln ( '----------------------------' );
$this -> output -> writeln ( '' );
$this -> encryptAllUsersFiles ();
2015-08-24 09:56:04 -04:00
$this -> output -> writeln ( " \n " );
}
/**
* create key - pair for every user
*/
protected function createKeyPairs () {
$this -> output -> writeln ( " \n " );
$progress = new ProgressBar ( $this -> output );
$progress -> setFormat ( " %message% \n [%bar%] " );
$progress -> start ();
2020-04-10 08:19:56 -04:00
foreach ( $this -> userManager -> getBackends () as $backend ) {
2015-08-24 09:56:04 -04:00
$limit = 500 ;
$offset = 0 ;
do {
$users = $backend -> getUsers ( '' , $limit , $offset );
foreach ( $users as $user ) {
if ( $this -> keyManager -> userHasKeys ( $user ) === false ) {
$progress -> setMessage ( 'Create key-pair for ' . $user );
$progress -> advance ();
$this -> setupUserFS ( $user );
$password = $this -> generateOneTimePassword ( $user );
$this -> userSetup -> setupUser ( $user , $password );
} else {
// users which already have a key-pair will be stored with a
// empty password and filtered out later
$this -> userPasswords [ $user ] = '' ;
}
}
$offset += $limit ;
2020-04-10 08:19:56 -04:00
} while ( count ( $users ) >= $limit );
2015-08-24 09:56:04 -04:00
}
$progress -> setMessage ( 'Key-pair created for all users' );
$progress -> finish ();
}
/**
* iterate over all user and encrypt their files
*/
protected function encryptAllUsersFiles () {
$this -> output -> writeln ( " \n " );
$progress = new ProgressBar ( $this -> output );
$progress -> setFormat ( " %message% \n [%bar%] " );
$progress -> start ();
$numberOfUsers = count ( $this -> userPasswords );
$userNo = 1 ;
2016-03-02 05:16:53 -05:00
if ( $this -> util -> isMasterKeyEnabled ()) {
$this -> encryptAllUserFilesWithMasterKey ( $progress );
} else {
foreach ( $this -> userPasswords as $uid => $password ) {
$userCount = " $uid ( $userNo of $numberOfUsers ) " ;
$this -> encryptUsersFiles ( $uid , $progress , $userCount );
$userNo ++ ;
}
2015-08-24 09:56:04 -04:00
}
$progress -> setMessage ( " all files encrypted " );
$progress -> finish ();
}
2016-03-02 05:16:53 -05:00
/**
* encrypt all user files with the master key
*
* @ param ProgressBar $progress
*/
protected function encryptAllUserFilesWithMasterKey ( ProgressBar $progress ) {
$userNo = 1 ;
2020-04-10 08:19:56 -04:00
foreach ( $this -> userManager -> getBackends () as $backend ) {
2016-03-02 05:16:53 -05:00
$limit = 500 ;
$offset = 0 ;
do {
$users = $backend -> getUsers ( '' , $limit , $offset );
foreach ( $users as $user ) {
$userCount = " $user ( $userNo ) " ;
$this -> encryptUsersFiles ( $user , $progress , $userCount );
$userNo ++ ;
}
$offset += $limit ;
2020-04-10 08:19:56 -04:00
} while ( count ( $users ) >= $limit );
2016-03-02 05:16:53 -05:00
}
}
2015-08-24 09:56:04 -04:00
/**
* encrypt files from the given user
*
* @ param string $uid
* @ param ProgressBar $progress
* @ param string $userCount
*/
protected function encryptUsersFiles ( $uid , ProgressBar $progress , $userCount ) {
$this -> setupUserFS ( $uid );
2020-03-26 04:30:18 -04:00
$directories = [];
2020-10-05 09:12:57 -04:00
$directories [] = '/' . $uid . '/files' ;
2015-08-24 09:56:04 -04:00
2020-04-10 08:19:56 -04:00
while ( $root = array_pop ( $directories )) {
2015-08-24 09:56:04 -04:00
$content = $this -> rootView -> getDirectoryContent ( $root );
foreach ( $content as $file ) {
$path = $root . '/' . $file [ 'name' ];
if ( $this -> rootView -> is_dir ( $path )) {
$directories [] = $path ;
continue ;
} else {
$progress -> setMessage ( " encrypt files for user $userCount : $path " );
$progress -> advance ();
2020-04-10 08:19:56 -04:00
if ( $this -> encryptFile ( $path ) === false ) {
2015-08-24 09:56:04 -04:00
$progress -> setMessage ( " encrypt files for user $userCount : $path (already encrypted) " );
$progress -> advance ();
}
}
}
}
}
/**
* encrypt file
*
* @ param string $path
* @ return bool
*/
protected function encryptFile ( $path ) {
2018-10-24 10:15:17 -04:00
// skip already encrypted files
$fileInfo = $this -> rootView -> getFileInfo ( $path );
if ( $fileInfo !== false && $fileInfo -> isEncrypted ()) {
return true ;
}
2015-08-24 09:56:04 -04:00
$source = $path ;
$target = $path . '.encrypted.' . time ();
try {
$this -> rootView -> copy ( $source , $target );
$this -> rootView -> rename ( $target , $source );
} catch ( DecryptionFailedException $e ) {
if ( $this -> rootView -> file_exists ( $target )) {
$this -> rootView -> unlink ( $target );
}
return false ;
}
return true ;
}
/**
* output one - time encryption passwords
*/
protected function outputPasswords () {
$table = new Table ( $this -> output );
2020-03-26 04:30:18 -04:00
$table -> setHeaders ([ 'Username' , 'Private key password' ]);
2015-08-24 09:56:04 -04:00
//create rows
2020-03-26 04:30:18 -04:00
$newPasswords = [];
$unchangedPasswords = [];
2015-08-24 09:56:04 -04:00
foreach ( $this -> userPasswords as $uid => $password ) {
if ( empty ( $password )) {
$unchangedPasswords [] = $uid ;
} else {
$newPasswords [] = [ $uid , $password ];
}
}
2015-09-24 06:47:46 -04:00
if ( empty ( $newPasswords )) {
$this -> output -> writeln ( " \n All users already had a key-pair, no further action needed. \n " );
return ;
}
2015-08-24 09:56:04 -04:00
$table -> setRows ( $newPasswords );
$table -> render ();
if ( ! empty ( $unchangedPasswords )) {
$this -> output -> writeln ( " \n The following users already had a key-pair which was reused without setting a new password: \n " );
foreach ( $unchangedPasswords as $uid ) {
$this -> output -> writeln ( " $uid " );
}
}
$this -> writePasswordsToFile ( $newPasswords );
$this -> output -> writeln ( '' );
$question = new ConfirmationQuestion ( 'Do you want to send the passwords directly to the users by mail? (y/n) ' , false );
if ( $this -> questionHelper -> ask ( $this -> input , $this -> output , $question )) {
$this -> sendPasswordsByMail ();
}
}
/**
* write one - time encryption passwords to a csv file
*
* @ param array $passwords
*/
protected function writePasswordsToFile ( array $passwords ) {
$fp = $this -> rootView -> fopen ( 'oneTimeEncryptionPasswords.csv' , 'w' );
foreach ( $passwords as $pwd ) {
fputcsv ( $fp , $pwd );
}
fclose ( $fp );
$this -> output -> writeln ( " \n " );
$this -> output -> writeln ( 'A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv' );
$this -> output -> writeln ( '' );
$this -> output -> writeln ( 'Each of these users need to login to the web interface, go to the' );
2016-06-20 03:08:21 -04:00
$this -> output -> writeln ( 'personal settings section "basic encryption module" and' );
2015-08-24 09:56:04 -04:00
$this -> output -> writeln ( 'update the private key password to match the login password again by' );
$this -> output -> writeln ( 'entering the one-time password into the "old log-in password" field' );
$this -> output -> writeln ( 'and their current login password' );
}
/**
* setup user file system
*
* @ param string $uid
*/
protected function setupUserFS ( $uid ) {
\OC_Util :: tearDownFS ();
\OC_Util :: setupFS ( $uid );
}
/**
* generate one time password for the user and store it in a array
*
* @ param string $uid
* @ return string password
*/
protected function generateOneTimePassword ( $uid ) {
2021-07-07 11:53:36 -04:00
$password = $this -> secureRandom -> generate ( 16 , ISecureRandom :: CHAR_HUMAN_READABLE );
2015-08-24 09:56:04 -04:00
$this -> userPasswords [ $uid ] = $password ;
return $password ;
}
/**
* send encryption key passwords to the users by mail
*/
protected function sendPasswordsByMail () {
$noMail = [];
$this -> output -> writeln ( '' );
$progress = new ProgressBar ( $this -> output , count ( $this -> userPasswords ));
$progress -> start ();
2015-12-01 06:05:40 -05:00
foreach ( $this -> userPasswords as $uid => $password ) {
2015-08-24 09:56:04 -04:00
$progress -> advance ();
if ( ! empty ( $password )) {
2015-12-01 06:05:40 -05:00
$recipient = $this -> userManager -> get ( $uid );
2023-01-25 05:42:45 -05:00
if ( ! $recipient instanceof IUser ) {
continue ;
}
2015-12-01 06:05:40 -05:00
$recipientDisplayName = $recipient -> getDisplayName ();
$to = $recipient -> getEMailAddress ();
2015-08-24 09:56:04 -04:00
2021-02-15 16:28:28 -05:00
if ( $to === '' || $to === null ) {
2015-12-01 06:05:40 -05:00
$noMail [] = $uid ;
2015-08-24 09:56:04 -04:00
continue ;
}
2023-01-25 05:42:45 -05:00
$l = $this -> l10nFactory -> get ( 'encryption' , $this -> l10nFactory -> getUserLanguage ( $recipient ));
$template = $this -> mailer -> createEMailTemplate ( 'encryption.encryptAllPassword' , [
'user' => $recipient -> getUID (),
'password' => $password ,
]);
$template -> setSubject ( $l -> t ( 'one-time password for server-side-encryption' ));
// 'Hey there,<br><br>The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>
// Please login to the web interface, go to the section "Basic encryption module" of your personal settings and update your encryption password by entering this password into the "Old log-in password" field and your current login-password.<br><br>'
$template -> addHeader ();
$template -> addHeading ( $l -> t ( 'Encryption password' ));
$template -> addBodyText (
$l -> t ( 'The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.' , [ htmlspecialchars ( $password )]),
$l -> t ( 'The administration enabled server-side-encryption. Your files were encrypted using the password "%s".' , $password )
);
$template -> addBodyText (
$l -> t ( 'Please login to the web interface, go to the "Security" section of your personal settings and update your encryption password by entering this password into the "Old log-in password" field and your current login-password.' )
);
$template -> addFooter ();
2015-08-24 09:56:04 -04:00
// send it out now
try {
$message = $this -> mailer -> createMessage ();
$message -> setTo ([ $to => $recipientDisplayName ]);
2023-01-25 05:42:45 -05:00
$message -> useTemplate ( $template );
$message -> setAutoSubmitted ( AutoSubmitted :: VALUE_AUTO_GENERATED );
2015-08-24 09:56:04 -04:00
$this -> mailer -> send ( $message );
} catch ( \Exception $e ) {
2015-12-01 06:05:40 -05:00
$noMail [] = $uid ;
2015-08-24 09:56:04 -04:00
}
}
}
$progress -> finish ();
if ( empty ( $noMail )) {
$this -> output -> writeln ( " \n \n Password successfully send to all users " );
} else {
$table = new Table ( $this -> output );
2020-03-26 04:30:18 -04:00
$table -> setHeaders ([ 'Username' , 'Private key password' ]);
2015-08-24 09:56:04 -04:00
$this -> output -> writeln ( " \n \n Could not send password to following users: \n " );
$rows = [];
foreach ( $noMail as $uid ) {
$rows [] = [ $uid , $this -> userPasswords [ $uid ]];
}
$table -> setRows ( $rows );
$table -> render ();
}
}
}