├── source ├── Salt │ ├── SaltException.php │ ├── autoload.php │ ├── FieldElement.php │ ├── Salsa20 │ │ └── Salsa20.php │ ├── Poly1305 │ │ └── Poly1305.php │ └── Curve25519 │ │ └── Curve25519.php ├── Threema │ ├── MsgApi │ │ ├── Tests │ │ │ ├── threema.jpg │ │ │ ├── Constants.php │ │ │ ├── CommonTests.php │ │ │ └── CryptToolTest.php │ │ ├── Exceptions │ │ │ ├── BadMessageException.php │ │ │ ├── InvalidArgumentException.php │ │ │ ├── DecryptionFailedException.php │ │ │ └── UnsupportedMessageTypeException.php │ │ ├── Commands │ │ │ ├── MultiPartCommandInterface.php │ │ │ ├── CommandInterface.php │ │ │ ├── Credits.php │ │ │ ├── Capability.php │ │ │ ├── Results │ │ │ │ ├── CreditsResult.php │ │ │ │ ├── DownloadFileResult.php │ │ │ │ ├── FetchPublicKeyResult.php │ │ │ │ ├── LookupIdResult.php │ │ │ │ ├── UploadFileResult.php │ │ │ │ ├── SendE2EResult.php │ │ │ │ ├── SendSimpleResult.php │ │ │ │ ├── Result.php │ │ │ │ └── CapabilityResult.php │ │ │ ├── FetchPublicKey.php │ │ │ ├── DownloadFile.php │ │ │ ├── UploadFile.php │ │ │ ├── LookupPhone.php │ │ │ ├── LookupEmail.php │ │ │ ├── SendSimple.php │ │ │ └── SendE2E.php │ │ ├── Constants.php │ │ ├── Messages │ │ │ ├── ThreemaMessage.php │ │ │ ├── TextMessage.php │ │ │ ├── ImageMessage.php │ │ │ ├── FileMessage.php │ │ │ └── DeliveryReceipt.php │ │ ├── Tools │ │ │ ├── FileAnalysisTool.php │ │ │ ├── FileAnalysisResult.php │ │ │ ├── EncryptResult.php │ │ │ ├── CryptToolSodiumDep.php │ │ │ ├── CryptToolSodium.php │ │ │ ├── CryptToolSalt.php │ │ │ └── CryptTool.php │ │ ├── PublicKeyStore.php │ │ ├── Helpers │ │ │ ├── ReceiveMessageResult.php │ │ │ └── E2EHelper.php │ │ ├── PublicKeyStores │ │ │ ├── File.php │ │ │ └── PhpFile.php │ │ ├── Receiver.php │ │ ├── ConnectionSettings.php │ │ └── Connection.php │ ├── Core │ │ ├── Exception.php │ │ ├── KeyPair.php │ │ ├── AssocArray.php │ │ └── Url.php │ └── Console │ │ ├── Command │ │ ├── HashEmail.php │ │ ├── HashPhone.php │ │ ├── DerivePublicKey.php │ │ ├── GenerateKeyPair.php │ │ ├── Decrypt.php │ │ ├── Credits.php │ │ ├── Encrypt.php │ │ ├── LookupPublicKeyById.php │ │ ├── LookupIdByEmail.php │ │ ├── LookupIdByPhoneNo.php │ │ ├── Capability.php │ │ ├── SendSimple.php │ │ ├── SendE2EImage.php │ │ ├── SendE2EText.php │ │ ├── SendE2EFile.php │ │ ├── ReceiveMessage.php │ │ └── Base.php │ │ ├── Common.php │ │ └── Run.php └── bootstrap.php ├── threema_msgapi.phar ├── threema-msgapi-tool.php ├── samples ├── testFetchPublicKey.php ├── testKeyLookup.php ├── testSendSimple.php ├── testSendE2EText.php └── testSendE2EFile.php ├── LICENSE └── README.md /source/Salt/SaltException.php: -------------------------------------------------------------------------------- 1 | privateKey = $privateKey; 16 | $this->publicKey = $publicKey; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Constants.php: -------------------------------------------------------------------------------- 1 | '/Curve25519/Curve25519.php', 9 | 'fieldelement' => '/FieldElement.php', 10 | 'salt' => '/Salt.php', 11 | 'saltexception' => '/SaltException.php', 12 | 'poly1305' => '/Poly1305/Poly1305.php', 13 | 'salsa20' => '/Salsa20/Salsa20.php', 14 | ); 15 | } 16 | $cn = strtolower($class); 17 | if (isset($classes[$cn])) { 18 | require __DIR__ . $classes[$cn]; 19 | } 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/CommandInterface.php: -------------------------------------------------------------------------------- 1 | getArgument(self::argEmail); 24 | Common::required($email); 25 | $hashedEmail = CryptTool::getInstance()->hashEmail($email); 26 | Common::l($hashedEmail); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/HashPhone.php: -------------------------------------------------------------------------------- 1 | getArgument(self::argPhoneNo); 24 | Common::required($phoneNo); 25 | $hashedPhoneNo = CryptTool::getInstance()->hashPhoneNo($phoneNo); 26 | Common::l($hashedPhoneNo); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /threema-msgapi-tool.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | run(); 22 | } catch (\Threema\Core\Exception $exception) { 23 | echo "ERROR: " . $exception->getMessage() . "\n"; 24 | die(); 25 | } 26 | -------------------------------------------------------------------------------- /samples/testFetchPublicKey.php: -------------------------------------------------------------------------------- 1 | fetchPublicKey('ECHOECHO'); 25 | if($result->isSuccess()) { 26 | echo 'public key '.$result->getPublicKey() . "\n"; 27 | } 28 | else { 29 | echo 'error '.$result->getErrorMessage() . "\n"; 30 | } 31 | -------------------------------------------------------------------------------- /samples/testKeyLookup.php: -------------------------------------------------------------------------------- 1 | keyLookupByPhoneNumber('123456789'); 24 | if($result->isSuccess()) { 25 | echo 'Threema ID found: '.$result->getId() . "\n"; 26 | } 27 | else { 28 | echo 'Error: '.$result->getErrorMessage() . "\n"; 29 | } 30 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/DerivePublicKey.php: -------------------------------------------------------------------------------- 1 | getArgumentPrivateKey(self::argPrivateKey); 22 | 23 | Common::required($privateKey); 24 | 25 | $cryptTool = CryptTool::getInstance(); 26 | 27 | $publicKey = $cryptTool->derivePublicKey($privateKey); 28 | Common::l(Common::convertPublicKey(bin2hex($publicKey))); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Messages/TextMessage.php: -------------------------------------------------------------------------------- 1 | text = $text; 23 | } 24 | 25 | /** 26 | * @return string text 27 | */ 28 | public function getText() { 29 | return $this->text; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | function __toString() { 36 | return 'text message'; 37 | } 38 | 39 | /** 40 | * Get the message type code of this message. 41 | * 42 | * @return int message type code 43 | */ 44 | public function getTypeCode() { 45 | return self::TYPE_CODE; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Capability.php: -------------------------------------------------------------------------------- 1 | threemaId = $threemaId; 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | function getParams() { 29 | return array(); 30 | } 31 | 32 | function getPath() { 33 | return 'capabilities/'.urlencode($this->threemaId); 34 | } 35 | 36 | /** 37 | * @param int $httpCode 38 | * @param object $res 39 | * @return CapabilityResult 40 | */ 41 | function parseResult($httpCode, $res){ 42 | return new CapabilityResult($httpCode, $res); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/CreditsResult.php: -------------------------------------------------------------------------------- 1 | credits = intval($response, 10); 21 | } 22 | 23 | /** 24 | * @return int 25 | */ 26 | public function getCredits() { 27 | return $this->credits; 28 | } 29 | 30 | /** 31 | * @param int $httpCode 32 | * @return string 33 | */ 34 | protected function getErrorMessageByErrorCode($httpCode) { 35 | switch($httpCode) { 36 | case 401: 37 | return 'API identity or secret incorrect'; 38 | case 500: 39 | return 'A temporary internal server error has occurred'; 40 | default: 41 | return 'Unknown error'; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /source/bootstrap.php: -------------------------------------------------------------------------------- 1 | validate(); 32 | -------------------------------------------------------------------------------- /samples/testSendSimple.php: -------------------------------------------------------------------------------- 1 | sendSimple($receiver, "This is a Test Message"); 30 | if($result->isSuccess()) { 31 | echo 'Message ID: '.$result->getMessageId() . "\n"; 32 | } 33 | else { 34 | echo 'Error: '.$result->getErrorMessage() . "\n"; 35 | } 36 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/FetchPublicKey.php: -------------------------------------------------------------------------------- 1 | threemaId = $threemaId; 23 | } 24 | 25 | 26 | /** 27 | * @return array 28 | */ 29 | function getParams() { 30 | return array(); 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | function getPath() { 37 | return 'pubkeys/'.urlencode($this->threemaId); 38 | } 39 | 40 | /** 41 | * @param int $httpCode 42 | * @param object $res 43 | * @return FetchPublicKeyResult 44 | */ 45 | function parseResult($httpCode, $res){ 46 | return new FetchPublicKeyResult($httpCode, $res); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/DownloadFile.php: -------------------------------------------------------------------------------- 1 | blobId = $blobId; 24 | } 25 | 26 | /** 27 | * @return array 28 | */ 29 | function getParams() { 30 | return array(); 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | function getPath() { 37 | return 'blobs/'.$this->blobId; 38 | } 39 | 40 | /** 41 | * @param int $httpCode 42 | * @param object $res 43 | * @return UploadFileResult 44 | */ 45 | function parseResult($httpCode, $res){ 46 | return new DownloadFileResult($httpCode, $res); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tests/Constants.php: -------------------------------------------------------------------------------- 1 | sendTextMessage("TEST1234", "This is an end-to-end encrypted message"); 27 | 28 | if(true === $result->isSuccess()) { 29 | echo 'Message ID: '.$result->getMessageId() . "\n"; 30 | } 31 | else { 32 | echo 'Error: '.$result->getErrorMessage() . "\n"; 33 | } 34 | -------------------------------------------------------------------------------- /samples/testSendE2EFile.php: -------------------------------------------------------------------------------- 1 | sendFileMessage("TEST1234", $filePath); 28 | 29 | if(true === $result->isSuccess()) { 30 | echo 'File Message ID: '.$result->getMessageId() . "\n"; 31 | } 32 | else { 33 | echo 'Error: '.$result->getErrorMessage() . "\n"; 34 | } 35 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/DownloadFileResult.php: -------------------------------------------------------------------------------- 1 | data = $data; 21 | } 22 | 23 | /** 24 | * the generated blob id 25 | * 26 | * @return string 27 | */ 28 | public function getData() { 29 | return $this->data; 30 | } 31 | 32 | /** 33 | * @param int $httpCode 34 | * @return string 35 | */ 36 | protected function getErrorMessageByErrorCode($httpCode) { 37 | switch($httpCode) { 38 | case 401: 39 | return 'API identity or secret incorrect'; 40 | case 404: 41 | return 'Invalid blob id'; 42 | case 500: 43 | return 'A temporary internal server error has occurred'; 44 | default: 45 | return 'Unknown error'; 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/FetchPublicKeyResult.php: -------------------------------------------------------------------------------- 1 | publicKey = (string)$response; 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function getPublicKey() { 27 | return $this->publicKey; 28 | } 29 | 30 | /** 31 | * @param int $httpCode 32 | * @return string 33 | */ 34 | protected function getErrorMessageByErrorCode($httpCode) { 35 | switch($httpCode) { 36 | case 401: 37 | return 'API identity or secret incorrect'; 38 | case 404: 39 | return 'No matching ID found'; 40 | case 500: 41 | return 'A temporary internal server error has occurred'; 42 | default: 43 | return 'Unknown error'; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/GenerateKeyPair.php: -------------------------------------------------------------------------------- 1 | generateKeyPair(); 22 | 23 | $privateKeyHex = bin2hex($keyPair->privateKey); 24 | $publicKeyHex = bin2hex($keyPair->publicKey); 25 | 26 | file_put_contents($this->getArgument(self::argPrivateKeyFile), Common::convertPrivateKey($privateKeyHex)."\n"); 27 | file_put_contents($this->getArgument(self::argPublicKeyFile), Common::convertPublicKey($publicKeyHex)."\n"); 28 | 29 | Common::l('key pair generated'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/LookupIdResult.php: -------------------------------------------------------------------------------- 1 | id = (string)$response; 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function getId() { 27 | return $this->id; 28 | } 29 | 30 | /** 31 | * @param int $httpCode 32 | * @return string 33 | */ 34 | protected function getErrorMessageByErrorCode($httpCode) { 35 | switch($httpCode) { 36 | case 400: 37 | return 'Hash length is wrong'; 38 | case 401: 39 | return 'API identity or secret incorrect'; 40 | case 404: 41 | return 'No matching ID found'; 42 | case 500: 43 | return 'A temporary internal server error has occurred'; 44 | default: 45 | return 'Unknown error'; 46 | } 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tests/CommonTests.php: -------------------------------------------------------------------------------- 1 | assertEquals($realPrivateKey, Constants::myPrivateKeyExtract, 'getPrivateKey failed'); 18 | } 19 | 20 | public function testGetPublicKey() { 21 | $realPublicKey = Common::getPublicKey(Constants::myPublicKey); 22 | $this->assertEquals($realPublicKey, Constants::myPublicKeyExtract, 'myPublicKey failed'); 23 | } 24 | 25 | public function testConvertPrivateKey() { 26 | $p = Common::convertPrivateKey('PRIVKEYSTRING'); 27 | $this->assertEquals($p, 'private:PRIVKEYSTRING', 'convertPrivateKey failed'); 28 | } 29 | 30 | public function testConvertPublicKey() { 31 | $p = Common::convertPublicKey('PUBKEYSTRING'); 32 | $this->assertEquals($p, 'public:PUBKEYSTRING', 'convertPublicKey failed'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/UploadFileResult.php: -------------------------------------------------------------------------------- 1 | blobId = (string)$blobId; 21 | } 22 | 23 | /** 24 | * the generated blob id 25 | * 26 | * @return string 27 | */ 28 | public function getBlobId() { 29 | return $this->blobId; 30 | } 31 | 32 | /** 33 | * @param int $httpCode 34 | * @return string 35 | */ 36 | protected function getErrorMessageByErrorCode($httpCode) { 37 | switch($httpCode) { 38 | case 401: 39 | return 'API identity or secret incorrect or file is empty'; 40 | case 402: 41 | return 'No credits remain'; 42 | case 413: 43 | return 'File is too long'; 44 | case 500: 45 | return 'A temporary internal server error has occurred'; 46 | default: 47 | return 'Unknown error'; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tools/FileAnalysisTool.php: -------------------------------------------------------------------------------- 1 | encryptedFileData = $encryptedFileData; 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | function getParams() { 29 | return array(); 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | function getPath() { 36 | return 'upload_blob'; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | function getData() { 43 | return $this->encryptedFileData; 44 | } 45 | 46 | /** 47 | * @param int $httpCode 48 | * @param object $res 49 | * @return UploadFileResult 50 | */ 51 | function parseResult($httpCode, $res){ 52 | return new UploadFileResult($httpCode, $res); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/SendE2EResult.php: -------------------------------------------------------------------------------- 1 | messageId = (string)$response; 21 | } 22 | 23 | public function getMessageId() { 24 | return $this->messageId; 25 | } 26 | 27 | /** 28 | * @param int $httpCode 29 | * @return string 30 | */ 31 | protected function getErrorMessageByErrorCode($httpCode) { 32 | switch($httpCode) { 33 | case 400: 34 | return 'The recipient identity is invalid or the account is not set up for E2E mode'; 35 | case 401: 36 | return 'API identity or secret incorrect'; 37 | case 402: 38 | return 'No credits remain'; 39 | case 413: 40 | return 'Message is too long'; 41 | case 500: 42 | return 'A temporary internal server error has occurred'; 43 | default: 44 | return 'Unknown error'; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tools/FileAnalysisResult.php: -------------------------------------------------------------------------------- 1 | mimeType = $mimeType; 33 | $this->size = $size; 34 | $this->path = realpath($path); 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getMimeType() { 41 | return $this->mimeType; 42 | } 43 | 44 | /** 45 | * @return int 46 | */ 47 | public function getSize() { 48 | return $this->size; 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function getPath() { 55 | return $this->path; 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getFileName() { 62 | return basename($this->path); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/Decrypt.php: -------------------------------------------------------------------------------- 1 | getArgumentPrivateKey(self::argPrivateKey); 22 | $publicKey = $this->getArgumentPublicKey(self::argPublicKey); 23 | $nonce = hex2bin($this->getArgument(self::argNonce)); 24 | $input = hex2bin($this->readStdIn()); 25 | 26 | Common::required($privateKey, $publicKey, $nonce, $input); 27 | $cryptTool = CryptTool::getInstance(); 28 | $message = $cryptTool->decryptMessage($input, $privateKey, $publicKey, $nonce); 29 | 30 | Common::l((String)$message); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/LookupPhone.php: -------------------------------------------------------------------------------- 1 | phoneNumber = $phoneNumber; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getPhoneNumber() { 30 | return $this->phoneNumber; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | function getParams() { 37 | return array(); 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | function getPath() { 44 | return 'lookup/phone_hash/'.urlencode(CryptTool::getInstance()->hashPhoneNo($this->phoneNumber)); 45 | } 46 | 47 | /** 48 | * @param int $httpCode 49 | * @param object $res 50 | * @return LookupIdResult 51 | */ 52 | function parseResult($httpCode, $res){ 53 | return new LookupIdResult($httpCode, $res); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/LookupEmail.php: -------------------------------------------------------------------------------- 1 | emailAddress = $emailAddress; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getEmailAddress() { 30 | return $this->emailAddress; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | function getParams() { 37 | return array(); 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | function getPath() { 44 | return 'lookup/email_hash/'.urlencode(CryptTool::getInstance()->hashEmail($this->emailAddress)); 45 | } 46 | 47 | /** 48 | * @param int $httpCode 49 | * @param object $res 50 | * @return LookupIdResult 51 | */ 52 | function parseResult($httpCode, $res){ 53 | return new LookupIdResult($httpCode, $res); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/SendSimpleResult.php: -------------------------------------------------------------------------------- 1 | messageId = (string)$response; 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function getMessageId() { 27 | return $this->messageId; 28 | } 29 | 30 | /** 31 | * @param int $httpCode 32 | * @return string 33 | */ 34 | protected function getErrorMessageByErrorCode($httpCode) { 35 | switch($httpCode) { 36 | case 400: 37 | return 'The recipient identity is invalid or the account is not set up for simple mode'; 38 | case 401: 39 | return 'API identity or secret incorrect'; 40 | case 402: 41 | return 'No credits remain'; 42 | case 404: 43 | return 'Phone or email could not be found'; 44 | case 413: 45 | return 'Message is too long'; 46 | case 500: 47 | return 'A temporary internal server error has occurred'; 48 | default: 49 | return 'Unknown error'; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/SendSimple.php: -------------------------------------------------------------------------------- 1 | text = $text; 30 | $this->receiver = $receiver; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getText() { 37 | return $this->text; 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | function getParams() { 44 | $p = $this->receiver->getParams(); 45 | $p['text'] = $this->getText(); 46 | return $p; 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | function getPath() { 53 | return 'send_simple'; 54 | } 55 | 56 | /** 57 | * @param int $httpCode 58 | * @param object $res 59 | * @return SendSimpleResult 60 | */ 61 | function parseResult($httpCode, $res){ 62 | return new SendSimpleResult($httpCode, $res); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Threema GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | 24 | Third party software used in this product: 25 | 26 | "Salt" - NaCl cryptography library for PHP 27 | https://github.com/devi/Salt 28 | Copyright (c) 2014-2015 Devi Mandiri. 29 | Used with permission from the author. -------------------------------------------------------------------------------- /source/Threema/MsgApi/Messages/ImageMessage.php: -------------------------------------------------------------------------------- 1 | blobId = $blobId; 35 | $this->length = $length; 36 | $this->nonce = $nonce; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | public function getBlobId() { 43 | return $this->blobId; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getLength() { 50 | return $this->length; 51 | } 52 | 53 | /** 54 | * @return int 55 | */ 56 | public function getNonce() { 57 | return $this->nonce; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | function __toString() { 64 | return 'image message'; 65 | } 66 | 67 | /** 68 | * Get the message type code of this message. 69 | * 70 | * @return int message type code 71 | */ 72 | public final function getTypeCode() { 73 | return self::TYPE_CODE; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/Credits.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 29 | } 30 | 31 | function doRun() { 32 | $from = $this->getArgumentThreemaId(self::argFrom); 33 | $secret = $this->getArgument(self::argSecret); 34 | 35 | Common::required($from, $secret); 36 | 37 | //define connection settings 38 | $settings = new ConnectionSettings($from, $secret); 39 | 40 | //create a connection 41 | $connector = new Connection($settings, $this->publicKeyStore); 42 | 43 | $result = $connector->credits(); 44 | Common::required($result); 45 | if($result->isSuccess()) { 46 | Common::l("remaining credits: ".$result->getCredits()); 47 | } 48 | else { 49 | Common::e($result->getErrorMessage()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/Encrypt.php: -------------------------------------------------------------------------------- 1 | getArgumentPrivateKey(self::argPrivateKey); 29 | $publicKey = $this->getArgumentPublicKey(self::argPublicKey); 30 | $textToEncrypt = $this->readStdIn(); 31 | Common::required($publicKey, $publicKey, $textToEncrypt); 32 | 33 | $cryptTool = CryptTool::getInstance(); 34 | //create a random nonce 35 | $newNonce = $cryptTool->randomNonce(); 36 | $encryptedMessageText = $cryptTool->encryptMessageText($textToEncrypt, $privateKey, $publicKey, $newNonce); 37 | 38 | Common::ln(bin2hex($newNonce)); 39 | //output encrypted text 40 | Common::ln(bin2hex($encryptedMessageText)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tools/EncryptResult.php: -------------------------------------------------------------------------------- 1 | data = $data; 44 | $this->key = $key; 45 | $this->nonce = $nonce; 46 | $this->size = $size; 47 | } 48 | 49 | /** 50 | * @return int 51 | */ 52 | public function getSize() { 53 | return $this->size; 54 | } 55 | 56 | /** 57 | * @return string (binary) 58 | */ 59 | public function getKey() { 60 | return $this->key; 61 | } 62 | 63 | /** 64 | * @return string (binary) 65 | */ 66 | public function getNonce() { 67 | return $this->nonce; 68 | } 69 | 70 | /** 71 | * @return string (binary) 72 | */ 73 | public function getData() { 74 | return $this->data; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/Result.php: -------------------------------------------------------------------------------- 1 | httpCode = $httpCode; 27 | $this->processResponse($response); 28 | $this->response = $response; 29 | } 30 | 31 | final public function isSuccess() { 32 | return $this->httpCode == 200; 33 | } 34 | 35 | /** 36 | * @return null|int 37 | */ 38 | final public function getErrorCode() { 39 | if(false === $this->isSuccess()) { 40 | return $this->httpCode; 41 | } 42 | return null; 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | final public function getErrorMessage() { 49 | return $this->getErrorMessageByErrorCode($this->getErrorCode()); 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | final public function getRawResponse() { 56 | return $this->response; 57 | } 58 | 59 | /** 60 | * @param int $httpCode 61 | * @return string 62 | */ 63 | abstract protected function getErrorMessageByErrorCode($httpCode); 64 | 65 | /** 66 | * @param string $response 67 | */ 68 | abstract protected function processResponse($response); 69 | } 70 | -------------------------------------------------------------------------------- /source/Threema/Core/AssocArray.php: -------------------------------------------------------------------------------- 1 | data = null !== $data ? $data : array(); 23 | } 24 | 25 | /** 26 | * @param string $key 27 | * @param null $defaultValue 28 | * @return mixed|null return the key value or the default value 29 | */ 30 | public function getValue($key, $defaultValue = null) { 31 | if(false === array_key_exists($key, $this->data)) { 32 | return $defaultValue; 33 | } 34 | 35 | return $this->data[$key]; 36 | } 37 | 38 | /** 39 | * @param $string 40 | * @param array|null $requiredKeys 41 | * @return AssocArray 42 | * @throws Exception 43 | */ 44 | public static final function byJsonString($string, array $requiredKeys = null) { 45 | $v = json_decode($string, true); 46 | if(null === $v || false === $v) { 47 | throw new Exception('invalid json string'); 48 | } 49 | 50 | //validate first 51 | if(null !== $requiredKeys) { 52 | //validate array first 53 | foreach($requiredKeys as $requiredKey) { 54 | if(false === array($v)) { 55 | throw new Exception('required key '.$requiredKey.' failed'); 56 | } 57 | } 58 | } 59 | return new AssocArray($v); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/LookupPublicKeyById.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 29 | } 30 | 31 | function doRun() { 32 | $id = $this->getArgumentThreemaId(self::argThreemaId); 33 | $from = $this->getArgumentThreemaId(self::argFrom); 34 | $secret = $this->getArgument(self::argSecret); 35 | 36 | Common::required($id, $from, $secret); 37 | 38 | //define connection settings 39 | $settings = new ConnectionSettings($from, $secret); 40 | 41 | //create a connection 42 | $connector = new Connection($settings, $this->publicKeyStore); 43 | 44 | $result = $connector->fetchPublicKey($id); 45 | if($result->isSuccess()) { 46 | Common::l(Common::convertPublicKey($result->getPublicKey())); 47 | } 48 | else { 49 | Common::e($result->getErrorMessage()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/SendE2E.php: -------------------------------------------------------------------------------- 1 | nonce = $nonce; 35 | $this->box = $box; 36 | $this->threemaId = $threemaId; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | public function getNonce() { 43 | return $this->nonce; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getBox() { 50 | return $this->box; 51 | } 52 | 53 | /** 54 | * @return array 55 | */ 56 | function getParams() { 57 | $p['to'] = $this->threemaId; 58 | $p['nonce'] = bin2hex($this->getNonce()); 59 | $p['box'] = bin2hex($this->getBox()); 60 | return $p; 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | function getPath() { 67 | return 'send_e2e'; 68 | } 69 | 70 | /** 71 | * @param int $httpCode 72 | * @param object $res 73 | * @return SendE2EResult 74 | */ 75 | function parseResult($httpCode, $res){ 76 | return new SendE2EResult($httpCode, $res); 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/LookupIdByEmail.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 31 | } 32 | 33 | function doRun() { 34 | $email = $this->getArgument(self::argEmail); 35 | $from = $this->getArgumentThreemaId(self::argFrom); 36 | $secret = $this->getArgument(self::argSecret); 37 | 38 | Common::required($email, $from, $secret); 39 | 40 | //define connection settings 41 | $settings = new ConnectionSettings($from, $secret); 42 | 43 | //create a connection 44 | $connector = new Connection($settings, $this->publicKeyStore); 45 | 46 | $result = $connector->keyLookupByEmail($email);; 47 | Common::required($result); 48 | if($result->isSuccess()) { 49 | Common::l($result->getId()); 50 | } 51 | else { 52 | Common::e($result->getErrorMessage()); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/LookupIdByPhoneNo.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 31 | } 32 | 33 | function doRun() { 34 | $phoneNo = $this->getArgument(self::argPhoneNo); 35 | $from = $this->getArgumentThreemaId(self::argFrom); 36 | $secret = $this->getArgument(self::argSecret); 37 | 38 | Common::required($phoneNo, $from, $secret); 39 | 40 | //define connection settings 41 | $settings = new ConnectionSettings($from, $secret); 42 | 43 | //create a connection 44 | $connector = new Connection($settings, $this->publicKeyStore); 45 | 46 | $result = $connector->keyLookupByPhoneNumber($phoneNo);; 47 | Common::required($result); 48 | if($result->isSuccess()) { 49 | Common::l($result->getId()); 50 | } 51 | else { 52 | Common::e($result->getErrorMessage()); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/PublicKeyStore.php: -------------------------------------------------------------------------------- 1 | publicKey cache 20 | * @var array 21 | */ 22 | private $cache = array(); 23 | 24 | /** 25 | * return null if the public key not found in the store 26 | * @param string $threemaId 27 | * @return string|null 28 | */ 29 | public final function getPublicKey($threemaId) { 30 | if(array_key_exists($threemaId, $this->cache)) { 31 | return $this->cache[$threemaId]; 32 | } 33 | 34 | $publicKey = $this->findPublicKey($threemaId); 35 | if(null !== $publicKey) { 36 | $this->cache[$threemaId] = $publicKey; 37 | } 38 | return $publicKey; 39 | } 40 | 41 | /** 42 | * return null if the public key not found in the store 43 | * @param string $threemaId 44 | * @return string|null 45 | */ 46 | abstract protected function findPublicKey($threemaId); 47 | 48 | /** 49 | * set and save a public key 50 | * @param string $threemaId 51 | * @param string $publicKey 52 | * @return bool 53 | */ 54 | final public function setPublicKey($threemaId, $publicKey) { 55 | $this->cache[$threemaId] = $publicKey; 56 | return $this->savePublicKey($threemaId, $publicKey); 57 | } 58 | /** 59 | * save a public key 60 | * @param string $threemaId 61 | * @param string $publicKey 62 | * @return bool 63 | */ 64 | abstract protected function savePublicKey($threemaId, $publicKey); 65 | } 66 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/Capability.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 30 | } 31 | 32 | function doRun() { 33 | $threemaId = $this->getArgumentThreemaId(self::argThreemaId); 34 | $from = $this->getArgumentThreemaId(self::argFrom); 35 | $secret = $this->getArgument(self::argSecret); 36 | 37 | Common::required($threemaId, $from, $secret); 38 | 39 | if(strlen($threemaId) != 8) { 40 | throw new Exception('invalid threema id'); 41 | } 42 | //define connection settings 43 | $settings = new ConnectionSettings($from, $secret); 44 | 45 | //create a connection 46 | $connector = new Connection($settings, $this->publicKeyStore); 47 | 48 | $result = $connector->keyCapability($threemaId); 49 | Common::required($result); 50 | if($result->isSuccess()) { 51 | Common::l(implode("\n", $result->getCapabilities())); 52 | } 53 | else { 54 | Common::e($result->getErrorMessage()); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/SendSimple.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 30 | } 31 | 32 | function doRun() { 33 | $to = $this->getArgument(self::argThreemaId); 34 | $from = $this->getArgument(self::argFrom); 35 | $secret = $this->getArgument(self::argSecret); 36 | Common::required($to, $from, $secret); 37 | 38 | $message = $this->readStdIn(); 39 | if(strlen($message) === 0) { 40 | throw new \InvalidArgumentException('please define a message'); 41 | } 42 | 43 | $settings = new ConnectionSettings( 44 | $from, 45 | $secret 46 | ); 47 | 48 | $connector = new Connection($settings, $this->publicKeyStore); 49 | $receiver = new Receiver($to, Receiver::TYPE_ID); 50 | 51 | $result = $connector->sendSimple($receiver, $message); 52 | if($result->isSuccess()) { 53 | Common::l('Message ID: '.$result->getMessageId()); 54 | } 55 | else { 56 | Common::l('Error: '.$result->getErrorMessage()); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Helpers/ReceiveMessageResult.php: -------------------------------------------------------------------------------- 1 | threemaMessage = $threemaMessage; 39 | $this->messageId = $messageId; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function getMessageId() { 46 | return $this->messageId; 47 | } 48 | 49 | /** 50 | * @param $message 51 | * @return $this 52 | */ 53 | public function addError($message) { 54 | $this->errors[] = $message; 55 | return $this; 56 | } 57 | 58 | /** 59 | * @return bool 60 | */ 61 | public function isSuccess() { 62 | return null === $this->errors || count($this->errors) == 0; 63 | } 64 | 65 | /** 66 | * @param string $key 67 | * @param string $file 68 | * @return $this 69 | */ 70 | public function addFile($key, $file) { 71 | $this->files[$key] = $file; 72 | return $this; 73 | } 74 | 75 | /** 76 | * @return \string[] 77 | */ 78 | public function getErrors() { 79 | return $this->errors; 80 | } 81 | 82 | /** 83 | * @return ThreemaMessage 84 | */ 85 | public function getThreemaMessage() { 86 | return $this->threemaMessage; 87 | } 88 | 89 | /** 90 | * @return \string[] 91 | */ 92 | public function getFiles() { 93 | return $this->files; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/PublicKeyStores/File.php: -------------------------------------------------------------------------------- 1 | file = $file; 32 | } 33 | 34 | /** 35 | * return null if the public key not found in the store 36 | * @param string $threemaId 37 | * @return null|string 38 | * @throws Exception 39 | */ 40 | function findPublicKey($threemaId) { 41 | $storeHandle = fopen($this->file, 'r'); 42 | if(false === $storeHandle) { 43 | throw new Exception('could not open file '.$this->file); 44 | } 45 | else { 46 | $threemaId = strtoupper($threemaId); 47 | $publicKey = null; 48 | while (!feof($storeHandle)) { 49 | $buffer = fgets($storeHandle, 4096); 50 | if(substr($buffer, 0, 8) == $threemaId) { 51 | $publicKey = str_replace("\n", '', substr($buffer, 8)); 52 | continue; 53 | } 54 | // Process buffer here.. 55 | } 56 | fclose($storeHandle); 57 | return $publicKey; 58 | } 59 | } 60 | 61 | /** 62 | * save a public key 63 | * @param string $threemaId 64 | * @param string $publicKey 65 | * @return bool 66 | */ 67 | function savePublicKey($threemaId, $publicKey) { 68 | return file_put_contents($this->file, $threemaId.$publicKey."\n", FILE_APPEND) !== false; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/SendE2EImage.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 32 | } 33 | 34 | function doRun() { 35 | $threemaId = $this->getArgument(self::argThreemaId); 36 | $from = $this->getArgument(self::argFrom); 37 | $secret = $this->getArgument(self::argSecret); 38 | $privateKey = $this->getArgumentPrivateKey(self::argPrivateKey); 39 | 40 | $path = $this->getArgumentFile(self::argImageFile); 41 | 42 | Common::required($threemaId, $from, $secret, $privateKey, $path); 43 | 44 | $settings = new ConnectionSettings( 45 | $from, 46 | $secret 47 | ); 48 | 49 | $connector = new Connection($settings, $this->publicKeyStore); 50 | 51 | $helper = new E2EHelper($privateKey, $connector); 52 | $result = $helper->sendImageMessage($threemaId, $path); 53 | 54 | if($result->isSuccess()) { 55 | Common::l('Message ID: '.$result->getMessageId()); 56 | } 57 | else { 58 | Common::e('Error: '.$result->getErrorMessage()); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/SendE2EText.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 30 | } 31 | 32 | function doRun() { 33 | $threemaId = $this->getArgumentThreemaId(self::argThreemaId); 34 | $from = $this->getArgument(self::argFrom); 35 | $secret = $this->getArgument(self::argSecret); 36 | $privateKey = $this->getArgumentPrivateKey(self::argPrivateKey); 37 | 38 | Common::required($threemaId, $from, $secret, $privateKey); 39 | 40 | $message = $this->readStdIn(); 41 | if(strlen($message) === 0) { 42 | throw new \InvalidArgumentException('please define a message'); 43 | } 44 | 45 | $settings = new ConnectionSettings( 46 | $from, 47 | $secret 48 | ); 49 | 50 | $connector = new Connection($settings, $this->publicKeyStore); 51 | 52 | $helper = new E2EHelper($privateKey, $connector); 53 | $result = $helper->sendTextMessage($threemaId, $message); 54 | 55 | if($result->isSuccess()) { 56 | Common::l('Message ID: '.$result->getMessageId()); 57 | } 58 | else { 59 | Common::e('Error: '.$result->getErrorMessage()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Receiver.php: -------------------------------------------------------------------------------- 1 | setValue($value, $type); 31 | } 32 | 33 | /** 34 | * @param string $threemaId 35 | * @return $this 36 | */ 37 | public function setToThreemaId($threemaId) { 38 | return $this->setValue($threemaId, 39 | self::TYPE_ID); 40 | } 41 | 42 | /** 43 | * @param string $phoneNo 44 | * @return $this 45 | */ 46 | public function setToPhoneNo($phoneNo) { 47 | return $this->setValue($phoneNo, 48 | self::TYPE_PHONE); 49 | } 50 | 51 | /** 52 | * @param string $emailAddress 53 | * @return $this 54 | */ 55 | public function setToEmail($emailAddress) { 56 | return $this->setValue($emailAddress, 57 | self::TYPE_EMAIL); 58 | } 59 | 60 | /** 61 | * @param string $value 62 | * @param string $type 63 | * @return $this 64 | */ 65 | private function setValue($value, $type) { 66 | $this->value = $value; 67 | $this->type = $type; 68 | return $this; 69 | } 70 | 71 | /** 72 | * @return array 73 | * @throws \InvalidArgumentException 74 | */ 75 | public function getParams() { 76 | switch($this->type) { 77 | case self::TYPE_ID: 78 | $to = $this->type; 79 | $this->value = strtoupper(trim($this->value)); 80 | break; 81 | 82 | case self::TYPE_EMAIL: 83 | case self::TYPE_PHONE: 84 | $to = $this->type; 85 | break; 86 | default: 87 | throw new \InvalidArgumentException(); 88 | } 89 | 90 | return array( 91 | $to => $this->value 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/SendE2EFile.php: -------------------------------------------------------------------------------- 1 | publicKeyStore = $publicKeyStore; 34 | } 35 | 36 | function doRun() { 37 | $threemaId = $this->getArgument(self::argThreemaId); 38 | $from = $this->getArgument(self::argFrom); 39 | $secret = $this->getArgument(self::argSecret); 40 | $privateKey = $this->getArgumentPrivateKey(self::argPrivateKey); 41 | 42 | $path = $this->getArgumentFile(self::argFile); 43 | $thumbnailPath = $this->getArgument(self::argThumbnail); 44 | 45 | Common::required($threemaId, $from, $secret, $privateKey, $path); 46 | 47 | $settings = new ConnectionSettings( 48 | $from, 49 | $secret 50 | ); 51 | 52 | $connector = new Connection($settings, $this->publicKeyStore); 53 | $helper = new E2EHelper($privateKey, $connector); 54 | $result = $helper->sendFileMessage($threemaId, $path, $thumbnailPath); 55 | 56 | if($result->isSuccess()) { 57 | Common::l('Message ID: '.$result->getMessageId()); 58 | } 59 | else { 60 | Common::e('Error: '.$result->getErrorMessage()); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Commands/Results/CapabilityResult.php: -------------------------------------------------------------------------------- 1 | capabilities = 21 | array_unique(array_filter(explode(',', $response !== null && strlen($response) > 0 ? $response : ''))); 22 | } 23 | 24 | /** 25 | * @return string[] 26 | */ 27 | public function getCapabilities() { 28 | return $this->capabilities; 29 | } 30 | 31 | /** 32 | * the threema id can receive text 33 | * @return bool 34 | */ 35 | public function canText() { 36 | return $this->can('text'); 37 | } 38 | 39 | /** 40 | * the threema id can receive images 41 | * @return bool 42 | */ 43 | public function canImage() { 44 | return $this->can('image'); 45 | } 46 | 47 | /** 48 | * the threema id can receive videos 49 | * @return bool 50 | */ 51 | public function canVideo() { 52 | return $this->can('video'); 53 | } 54 | 55 | /** 56 | * the threema id can receive files 57 | * @return bool 58 | */ 59 | public function canAudio() { 60 | return $this->can('audio'); 61 | } 62 | 63 | /** 64 | * the threema id can receive files 65 | * @return bool 66 | */ 67 | public function canFile() { 68 | return $this->can('file'); 69 | } 70 | 71 | private function can($key) { 72 | return null !== $this->capabilities 73 | && true === in_array($key, $this->capabilities); 74 | } 75 | 76 | /** 77 | * @param int $httpCode 78 | * @return string 79 | */ 80 | protected function getErrorMessageByErrorCode($httpCode) { 81 | switch($httpCode) { 82 | case 401: 83 | return 'API identity or secret incorrect'; 84 | case 404: 85 | return 'No matching ID found'; 86 | case 500: 87 | return 'A temporary internal server error has occurred'; 88 | default: 89 | return 'Unknown error'; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /source/Salt/FieldElement.php: -------------------------------------------------------------------------------- 1 | 10 | * @link https://github.com/devi/Salt 11 | * 12 | */ 13 | class FieldElement extends SplFixedArray { 14 | 15 | public function toString() { 16 | $this->rewind(); 17 | $buf = ""; 18 | while ($this->valid()) { 19 | $buf .= chr($this->current()); 20 | $this->next(); 21 | } 22 | $this->rewind(); 23 | return $buf; 24 | } 25 | 26 | public function toHex() { 27 | $this->rewind(); 28 | $hextable = "0123456789abcdef"; 29 | $buf = ""; 30 | while ($this->valid()) { 31 | $c = $this->current(); 32 | $buf .= $hextable[$c>>4]; 33 | $buf .= $hextable[$c&0x0f]; 34 | $this->next(); 35 | } 36 | $this->rewind(); 37 | return $buf; 38 | } 39 | 40 | public function toBase64() { 41 | return base64_encode($this->toString()); 42 | } 43 | 44 | public function toJson() { 45 | return json_encode($this->toString()); 46 | } 47 | 48 | public function slice($offset, $length = null) { 49 | $length = $length ? $length : $this->getSize()-$offset; 50 | $slice = new FieldElement($length); 51 | for ($i = 0;$i < $length;++$i) { 52 | $slice[$i] = $this->offsetGet($i+$offset); 53 | } 54 | return $slice; 55 | } 56 | 57 | public function copy($src, $size, $offset = 0, $srcOffset = 0) { 58 | for ($i = 0;$i < $size;++$i) { 59 | $this->offsetSet($i+$offset, $src[$i+$srcOffset]); 60 | } 61 | } 62 | 63 | public static function fromArray($array, $save_indexes = true) { 64 | $l = count($array); 65 | $fe = new FieldElement($l); 66 | $array = $save_indexes ? $array : array_values($array); 67 | foreach ($array as $k => $v) $fe[$k] = $v; 68 | return $fe; 69 | } 70 | 71 | public static function fromString($str) { 72 | return static::fromArray(unpack("C*", $str), false); 73 | } 74 | 75 | public static function fromHex($hex) { 76 | $hex = preg_replace('/[^0-9a-f]/', '', $hex); 77 | return static::fromString(pack("H*", $hex)); 78 | } 79 | 80 | public static function fromBase64($base64) { 81 | return FieldElement::fromString(base64_decode($base64, true)); 82 | } 83 | 84 | public static function fromJson($json) { 85 | return FieldElement::fromArray(json_decode($json, true)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Messages/FileMessage.php: -------------------------------------------------------------------------------- 1 | blobId = $blobId; 53 | $this->thumbnailBlobId = $thumbnailBlobId; 54 | $this->encryptionKey = $encryptionKey; 55 | $this->mimeType = $mimeType; 56 | $this->filename = $filename; 57 | $this->size = $size; 58 | } 59 | 60 | /** 61 | * @return string 62 | */ 63 | public function getBlobId() { 64 | return $this->blobId; 65 | } 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getEncryptionKey() { 71 | return $this->encryptionKey; 72 | } 73 | 74 | /** 75 | * @return string 76 | */ 77 | public function getFilename() { 78 | return $this->filename; 79 | } 80 | 81 | /** 82 | * @return string 83 | */ 84 | public function getMimeType() { 85 | return $this->mimeType; 86 | } 87 | 88 | /** 89 | * @return int 90 | */ 91 | public function getSize() { 92 | return $this->size; 93 | } 94 | 95 | /** 96 | * @return string 97 | */ 98 | public function getThumbnailBlobId() { 99 | return $this->thumbnailBlobId; 100 | } 101 | 102 | /** 103 | * @return string 104 | */ 105 | function __toString() { 106 | return 'file message'; 107 | } 108 | 109 | /** 110 | * Get the message type code of this message. 111 | * 112 | * @return int message type code 113 | */ 114 | public final function getTypeCode() { 115 | return self::TYPE_CODE; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Messages/DeliveryReceipt.php: -------------------------------------------------------------------------------- 1 | text 15 | * 16 | * @var array 17 | */ 18 | private static $receiptTypesToNames = array( 19 | 1 => 'received', 20 | 2 => 'read', 21 | 3 => 'userack'); 22 | 23 | /** 24 | * the type of this receipt 25 | * @var int 26 | */ 27 | private $receiptType; 28 | 29 | /** 30 | * list of message IDs acknowledged by this delivery receipt 31 | * @var string[] 32 | */ 33 | private $ackedMessageIds; 34 | 35 | /** 36 | * create instance 37 | * @param int $receiptType the type of this receipt 38 | * @param array $ackedMessageIds list of message IDs acknowledged by this delivery receipt 39 | */ 40 | function __construct($receiptType, array $ackedMessageIds) { 41 | $this->receiptType = $receiptType; 42 | $this->ackedMessageIds = $ackedMessageIds; 43 | } 44 | 45 | /** 46 | * Get the type of this delivery receipt as a numeric code (e.g. 1, 2, 3). 47 | * 48 | * @return int 49 | */ 50 | public function getReceiptType() { 51 | return $this->receiptType; 52 | } 53 | 54 | /** 55 | * Get the type of this delivery receipt as a string (e.g. 'received', 'read', 'userack'). 56 | * 57 | * @return string 58 | */ 59 | public function getReceiptTypeName() { 60 | if(true === array_key_exists($this->receiptType, self::$receiptTypesToNames)) { 61 | return self::$receiptTypesToNames[$this->receiptType]; 62 | } 63 | return null; 64 | } 65 | 66 | /** 67 | * Get the acknowledged message ids 68 | * @return array 69 | */ 70 | public function getAckedMessageIds() { 71 | return $this->ackedMessageIds; 72 | } 73 | 74 | /** 75 | * Convert to string 76 | * 77 | * @return string 78 | */ 79 | function __toString() { 80 | $str = "Delivery receipt (" . $this->getReceiptTypeName() . "): "; 81 | $hexMessageIds = array(); 82 | foreach ($this->ackedMessageIds as $messageId) { 83 | $hexMessageIds[] = bin2hex($messageId); 84 | } 85 | $str .= join(", ", $hexMessageIds); 86 | return $str; 87 | } 88 | 89 | /** 90 | * Get the message type code of this message. 91 | * 92 | * @return int message type code 93 | */ 94 | public final function getTypeCode() { 95 | return self::TYPE_CODE; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/ConnectionSettings.php: -------------------------------------------------------------------------------- 1 | threemaId = $threemaId; 44 | $this->secret = $secret; 45 | if ($host === null) { 46 | $host = 'https://msgapi.threema.ch'; 47 | } 48 | $this->host = $host; 49 | 50 | // TLS options 51 | if(null !== $tlsOptions && is_array($tlsOptions)) { 52 | if(true === array_key_exists(self::tlsOptionForceHttps, $tlsOptions)) { 53 | $this->tlsOptions[self::tlsOptionForceHttps] = $tlsOptions[self::tlsOptionForceHttps] === true; 54 | } 55 | 56 | if(true === array_key_exists(self::tlsOptionVersion, $tlsOptions)) { 57 | $this->tlsOptions[self::tlsOptionVersion] = $tlsOptions[self::tlsOptionVersion]; 58 | } 59 | 60 | if(true === array_key_exists(self::tlsOptionCipher, $tlsOptions)) { 61 | $this->tlsOptions[self::tlsOptionCipher] = $tlsOptions[self::tlsOptionCipher]; 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getThreemaId() { 70 | return $this->threemaId; 71 | } 72 | 73 | /** 74 | * @return string 75 | */ 76 | public function getSecret() { 77 | return $this->secret; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getHost() { 84 | return $this->host; 85 | } 86 | 87 | /** 88 | * @return array 89 | */ 90 | public function getTlsOptions() { 91 | return $this->tlsOptions; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function getTlsOption($option, $default = null) { 98 | return true === array_key_exists($option, $this->tlsOptions) ? $this->tlsOptions[$option] : $default; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/ReceiveMessage.php: -------------------------------------------------------------------------------- 1 | folder', 32 | array(self::argOutputFolder)); 33 | $this->publicKeyStore = $publicKeyStore; 34 | } 35 | 36 | function doRun() { 37 | $sendersThreemaId = $this->getArgumentThreemaId(self::argThreemaId); 38 | $id = $this->getArgumentThreemaId(self::argFrom); 39 | $secret = $this->getArgument(self::argSecret); 40 | $privateKey = $this->getArgumentPrivateKey(self::argPrivateKey); 41 | $nonce = hex2bin($this->getArgument(self::argNonce)); 42 | $messageId = $this->getArgument(self::argMessageId); 43 | $outputFolder = $this->getArgument(self::argOutputFolder); 44 | 45 | $box = hex2bin($this->readStdIn()); 46 | 47 | Common::required($box, $id, $secret, $privateKey, $nonce); 48 | 49 | $settings = new ConnectionSettings( 50 | $id, 51 | $secret 52 | ); 53 | 54 | $connector = new Connection($settings, $this->publicKeyStore); 55 | $helper = new E2EHelper($privateKey, $connector); 56 | $message = $helper->receiveMessage( 57 | $sendersThreemaId, 58 | $messageId, 59 | $box, 60 | $nonce, 61 | $outputFolder 62 | ); 63 | 64 | if(null === $message) { 65 | Common::e('invalid message'); 66 | return; 67 | } 68 | 69 | if($message->isSuccess()) { 70 | Common::l($message->getMessageId().' - '.$message->getThreemaMessage()); 71 | foreach($message->getFiles() as $fileName => $filePath) { 72 | Common::l(' received file '.$fileName.' in '.$filePath); 73 | } 74 | } 75 | else { 76 | Common::e('Error: '.implode("\n", $message->getErrors())); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /source/Threema/Core/Url.php: -------------------------------------------------------------------------------- 1 | path = $path; 33 | $this->host = $host; 34 | } 35 | 36 | /** 37 | * @param string $key 38 | * @param string $value 39 | * @return $this 40 | */ 41 | public function setValue($key, $value){ 42 | $this->values[$key] = $value; 43 | return $this; 44 | } 45 | 46 | /** 47 | * Add a path to the current url 48 | * 49 | * @param string $path 50 | * @return $this 51 | */ 52 | public function addPath($path) { 53 | while(substr($this->path, strlen($this->path)-1) == '/') { 54 | $this->path = substr($this->path, 0, strlen($this->path)-1); 55 | } 56 | 57 | $realPath = ''; 58 | foreach(explode('/', $path) as $c => $pathPiece) { 59 | if($c > 0) { 60 | $realPath .= '/'; 61 | } 62 | $realPath .= urlencode($pathPiece); 63 | } 64 | while(substr($path, 0, 1) == '/') { 65 | $path = substr($path, 1); 66 | } 67 | 68 | $this->path .= '/'.$path; 69 | return $this; 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getPath() { 76 | $p = $this->path; 77 | if(count($this->values) > 0) { 78 | $s = http_build_query($this->values); 79 | if(strlen($s) > 0) { 80 | $p .= '?'.$s; 81 | } 82 | } 83 | 84 | return $p; 85 | } 86 | 87 | 88 | function __toString() { 89 | return $this->getPath(); 90 | } 91 | 92 | /** 93 | * @return string 94 | */ 95 | public function getFullPath() { 96 | return $this->host.(substr($this->getPath(), 0, 1) == '/' ? '' : '/').$this->getPath(); 97 | } 98 | 99 | public static function parametersToArray($urlParameter) 100 | { 101 | $result = array(); 102 | 103 | while(strlen($urlParameter) > 0) { 104 | // name 105 | $keyPosition= strpos($urlParameter,'='); 106 | $keyValue = substr($urlParameter,0,$keyPosition); 107 | // value 108 | $valuePosition = strpos($urlParameter,'&') ? strpos($urlParameter,'&'): strlen($urlParameter); 109 | $valueValue = substr($urlParameter,$keyPosition+1,$valuePosition-$keyPosition-1); 110 | 111 | // decoding the response 112 | $result[$keyValue] = urldecode($valueValue); 113 | $urlParameter = substr($urlParameter,$valuePosition+1,strlen($urlParameter)); 114 | } 115 | 116 | return $result; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /source/Threema/Console/Common.php: -------------------------------------------------------------------------------- 1 | = 0.2.0 21 | */ 22 | class CryptToolSodiumDep extends CryptTool { 23 | /** 24 | * @param string $data 25 | * @param string $nonce 26 | * @param string $senderPrivateKey 27 | * @param string $recipientPublicKey 28 | * @return string encrypted box 29 | */ 30 | protected function makeBox($data, $nonce, $senderPrivateKey, $recipientPublicKey) { 31 | /** @noinspection PhpUndefinedClassInspection */ 32 | $kp = Sodium::crypto_box_keypair_from_secretkey_and_publickey($senderPrivateKey, $recipientPublicKey); 33 | /** @noinspection PhpUndefinedClassInspection */ 34 | return Sodium::crypto_box($data, $nonce, $kp); 35 | } 36 | 37 | /** 38 | * make a secret box 39 | * 40 | * @param $data 41 | * @param $nonce 42 | * @param $key 43 | * @return mixed 44 | */ 45 | protected function makeSecretBox($data, $nonce, $key) { 46 | /** @noinspection PhpUndefinedClassInspection */ 47 | return Sodium::crypto_secretbox($data, $nonce, $key); 48 | } 49 | 50 | 51 | /** 52 | * @param string $box 53 | * @param string $recipientPrivateKey 54 | * @param string $senderPublicKey 55 | * @param string $nonce 56 | * @return null|string 57 | */ 58 | protected function openBox($box, $recipientPrivateKey, $senderPublicKey, $nonce) { 59 | /** @noinspection PhpUndefinedClassInspection */ 60 | $kp = Sodium::crypto_box_keypair_from_secretkey_and_publickey($recipientPrivateKey, $senderPublicKey); 61 | /** @noinspection PhpUndefinedClassInspection */ 62 | return Sodium::crypto_box_open($box, $nonce, $kp); 63 | } 64 | 65 | /** 66 | * decrypt a secret box 67 | * 68 | * @param string $box as binary 69 | * @param string $nonce as binary 70 | * @param string $key as binary 71 | * @return string as binary 72 | */ 73 | protected function openSecretBox($box, $nonce, $key) { 74 | /** @noinspection PhpUndefinedClassInspection */ 75 | return Sodium::crypto_secretbox_open($box, $nonce, $key); 76 | } 77 | 78 | 79 | /** 80 | * Generate a new key pair. 81 | * 82 | * @return KeyPair the new key pair 83 | */ 84 | final public function generateKeyPair() { 85 | /** @noinspection PhpUndefinedClassInspection */ 86 | $kp = Sodium::crypto_box_keypair(); 87 | /** @noinspection PhpUndefinedClassInspection */ 88 | return new KeyPair(Sodium::crypto_box_secretkey($kp), Sodium::crypto_box_publickey($kp)); 89 | } 90 | 91 | /** 92 | * @param int $size 93 | * @return string 94 | */ 95 | protected function createRandom($size) { 96 | /** @noinspection PhpUndefinedClassInspection */ 97 | return Sodium::randombytes_buf($size); 98 | } 99 | 100 | /** 101 | * Derive the public key 102 | * 103 | * @param string $privateKey in binary 104 | * @return string public key as binary 105 | */ 106 | final public function derivePublicKey($privateKey) { 107 | /** @noinspection PhpUndefinedClassInspection */ 108 | return Sodium::crypto_box_publickey_from_secretkey($privateKey); 109 | } 110 | 111 | /** 112 | * Check if implementation supported 113 | * @return bool 114 | */ 115 | public function isSupported() { 116 | return true === extension_loaded("libsodium") 117 | && method_exists('Sodium', 'sodium_version_string'); 118 | } 119 | 120 | /** 121 | * Validate crypt tool 122 | * 123 | * @return bool 124 | * @throws Exception 125 | */ 126 | public function validate() { 127 | if(false === $this->isSupported()) { 128 | throw new Exception('Sodium implementation not supported'); 129 | } 130 | return true; 131 | } 132 | 133 | /** 134 | * @return string 135 | */ 136 | public function getName() { 137 | return 'Sodium'; 138 | } 139 | 140 | /** 141 | * Description of the CryptTool 142 | * @return string 143 | */ 144 | public function getDescription() { 145 | /** @noinspection PhpUndefinedClassInspection */ 146 | return 'Sodium '.Sodium::sodium_version_string().' (deprecated, please try to update libsodium to version 0.2.0 or higher)'; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/PublicKeyStores/PhpFile.php: -------------------------------------------------------------------------------- 1 | file = $file; 52 | } 53 | 54 | /** 55 | * return null if the public key not found in the store 56 | * 57 | * @param string $threemaId 58 | * @return null|string 59 | */ 60 | public function findPublicKey($threemaId) 61 | { 62 | $threemaId = strtoupper($threemaId); 63 | $publicKey = null; 64 | 65 | //Pre-check loaded array 66 | if (array_key_exists($threemaId, $this->keystore)) { 67 | $publicKey = $this->keystore[$threemaId]; 68 | return $publicKey; 69 | } 70 | 71 | //Parse file 72 | $keystore = array(); 73 | $isMsgApiKeystore = true; 74 | require_once $this->file; 75 | 76 | //Update cache 77 | $this->keystore = $keystore; 78 | 79 | //Check file content 80 | if (array_key_exists($threemaId, $keystore)) { 81 | $publicKey = $keystore[$threemaId]; 82 | } 83 | 84 | return $publicKey; 85 | } 86 | 87 | /** 88 | * save a public key 89 | * 90 | * @param string $threemaId 91 | * @param string $publicKey 92 | * @throws Exception 93 | * @return bool 94 | */ 95 | public function savePublicKey($threemaId, $publicKey) 96 | { 97 | //create file if needed 98 | $this->initFile(); 99 | 100 | //check for key 101 | if (array_key_exists($threemaId, $this->keystore)) { 102 | return false; 103 | } 104 | 105 | //add key 106 | $this->keystore[$threemaId] = $publicKey; 107 | $content = '$keystore[\'' . $threemaId . '\'] = \'' . $publicKey . '\';' . PHP_EOL; 108 | 109 | //write content 110 | $fileadd = file_put_contents($this->file, $content, FILE_APPEND); 111 | if (!$fileadd) { 112 | throw new Exception('could not write to file ' . $this->file); 113 | } 114 | 115 | return true; 116 | } 117 | 118 | /** 119 | * initiate a php file 120 | * 121 | * @param null|resource file handle for file opened in r+ mode 122 | * @return bool 123 | * @throws Exception 124 | */ 125 | private function initFile($fileHandle = null) 126 | { 127 | //check if file does already contain content 128 | if (filesize($this->file) != 0) { 129 | return true; 130 | } 131 | 132 | //manually open file if no file handle is given 133 | $fileopened = null; 134 | if (null === $fileHandle) { 135 | $fileHandle = fopen($this->file, 'r+'); 136 | $fileopened = true; 137 | 138 | //check for success 139 | if (null === $fileHandle) { 140 | throw new Exception('could not open file ' . $this->file); 141 | } 142 | } 143 | 144 | //create content 145 | $content = $this->fileBlocker . PHP_EOL; 146 | $content .= PHP_EOL; 147 | $content .= '//Threema MsgApi phpfile keystore' . PHP_EOL; 148 | $content .= '//DO NOT EDIT THIS FILE!' . PHP_EOL; 149 | $content .= PHP_EOL; 150 | 151 | //write file 152 | $fwrite = fwrite($fileHandle, $content); 153 | if (!$fwrite) { 154 | throw new Exception('could not write to file ' . $this->file); 155 | } 156 | 157 | //close file if necessary 158 | if ($fileopened) { 159 | $fclose = fclose($fileHandle); 160 | if (!$fclose) { 161 | throw new Exception('error while processing file ' . $this->file); 162 | } 163 | } 164 | return true; 165 | } 166 | 167 | /** 168 | * Initialize a new PhpFile Public Key Store 169 | * @param string $path the file will be created it it does not exist 170 | * @return PhpFile 171 | */ 172 | public static function create($path) { 173 | if(false === file_exists($path)) { 174 | //touch 175 | touch($path); 176 | } 177 | 178 | return new PhpFile($path); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tools/CryptToolSodium.php: -------------------------------------------------------------------------------- 1 | = 0.2.0 (Namespaces) 16 | * 17 | * @package Threema\Core 18 | */ 19 | class CryptToolSodium extends CryptTool { 20 | /** 21 | * @param string $data 22 | * @param string $nonce 23 | * @param string $senderPrivateKey 24 | * @param string $recipientPublicKey 25 | * @return string encrypted box 26 | */ 27 | protected function makeBox($data, $nonce, $senderPrivateKey, $recipientPublicKey) { 28 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 29 | $kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($senderPrivateKey, $recipientPublicKey); 30 | 31 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 32 | return \Sodium\crypto_box($data, $nonce, $kp); 33 | } 34 | 35 | /** 36 | * make a secret box 37 | * 38 | * @param $data 39 | * @param $nonce 40 | * @param $key 41 | * @return mixed 42 | */ 43 | protected function makeSecretBox($data, $nonce, $key) { 44 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 45 | return \Sodium\crypto_secretbox($data, $nonce, $key); 46 | } 47 | 48 | 49 | /** 50 | * @param string $box 51 | * @param string $recipientPrivateKey 52 | * @param string $senderPublicKey 53 | * @param string $nonce 54 | * @return null|string 55 | */ 56 | protected function openBox($box, $recipientPrivateKey, $senderPublicKey, $nonce) { 57 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 58 | $kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey($recipientPrivateKey, $senderPublicKey); 59 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 60 | return \Sodium\crypto_box_open($box, $nonce, $kp); 61 | } 62 | 63 | /** 64 | * decrypt a secret box 65 | * 66 | * @param string $box as binary 67 | * @param string $nonce as binary 68 | * @param string $key as binary 69 | * @return string as binary 70 | */ 71 | protected function openSecretBox($box, $nonce, $key) { 72 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 73 | return \Sodium\crypto_secretbox_open($box, $nonce, $key); 74 | } 75 | 76 | /** 77 | * Generate a new key pair. 78 | * 79 | * @return KeyPair the new key pair 80 | */ 81 | final public function generateKeyPair() { 82 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 83 | $kp = \Sodium\crypto_box_keypair(); 84 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 85 | return new KeyPair(\Sodium\crypto_box_secretkey($kp), \Sodium\crypto_box_publickey($kp)); 86 | } 87 | 88 | /** 89 | * @param int $size 90 | * @return string 91 | */ 92 | protected function createRandom($size) { 93 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 94 | return \Sodium\randombytes_buf($size); 95 | } 96 | 97 | /** 98 | * Derive the public key 99 | * 100 | * @param string $privateKey in binary 101 | * @return string public key as binary 102 | */ 103 | final public function derivePublicKey($privateKey) { 104 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 105 | return \Sodium\crypto_box_publickey_from_secretkey($privateKey); 106 | } 107 | 108 | /** 109 | * Check if implementation supported 110 | * @return bool 111 | */ 112 | public function isSupported() { 113 | return true === extension_loaded('libsodium') 114 | && false === method_exists('Sodium', 'sodium_version_string'); 115 | } 116 | 117 | /** 118 | * Validate crypt tool 119 | * 120 | * @return bool 121 | * @throws Exception 122 | */ 123 | public function validate() { 124 | if(false === $this->isSupported()) { 125 | throw new Exception('Sodium implementation not supported'); 126 | } 127 | return true; 128 | } 129 | 130 | /** 131 | * @return string 132 | */ 133 | public function getName() { 134 | return 'sodium'; 135 | } 136 | 137 | /** 138 | * Description of the CryptTool 139 | * @return string 140 | */ 141 | public function getDescription() { 142 | /** @noinspection PhpUndefinedNamespaceInspection @noinspection PhpUndefinedFunctionInspection */ 143 | return 'Sodium implementation '.\Sodium\version_string(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tools/CryptToolSalt.php: -------------------------------------------------------------------------------- 1 | slice(16)->toString(); 30 | } 31 | 32 | /** 33 | * @param string $data 34 | * @param string $nonce 35 | * @param string $key 36 | * @return string encrypted secret box 37 | */ 38 | protected function makeSecretBox($data, $nonce, $key) { 39 | return Salt::secretbox($data, $nonce, $key) 40 | ->slice(16)->toString(); 41 | } 42 | 43 | /** 44 | * @param string $box 45 | * @param string $recipientPrivateKey 46 | * @param string $senderPublicKey 47 | * @param string $nonce 48 | * @return null|string 49 | */ 50 | protected function openBox($box, $recipientPrivateKey, $senderPublicKey, $nonce) { 51 | $boxPad = str_repeat("\x00", 16) . $box; 52 | try { 53 | $data = Salt::box_open($boxPad, $recipientPrivateKey, $senderPublicKey, $nonce); 54 | } catch (\SaltException $e) { 55 | $data = null; 56 | } 57 | 58 | if ($data) { 59 | return substr($data->toString(), 32); 60 | } 61 | 62 | return null; 63 | } 64 | 65 | /** 66 | * decrypt a secret box 67 | * 68 | * @param string $box as binary 69 | * @param string $nonce as binary 70 | * @param string $key as binary 71 | * @return string as binary 72 | */ 73 | protected function openSecretBox($box, $nonce, $key) { 74 | $boxPad = str_repeat("\x00", 16) . $box; 75 | $data = Salt::secretbox_open($boxPad, $nonce, $key); 76 | 77 | if ($data) { 78 | return substr($data->toString(), 32); 79 | } 80 | return null; 81 | } 82 | 83 | 84 | /** 85 | * Generate a new key pair. 86 | * 87 | * @return KeyPair the new key pair 88 | */ 89 | final public function generateKeyPair() { 90 | list($privateKeyObject, $publicKeyObject) = Salt::box_keypair(); 91 | return new KeyPair($privateKeyObject->toString(), $publicKeyObject->toString()); 92 | } 93 | 94 | /** 95 | * @param int $size 96 | * @return string 97 | */ 98 | protected function createRandom($size) { 99 | return Salt::randombytes($size); 100 | } 101 | 102 | /** 103 | * Derive the public key 104 | * 105 | * @param string $privateKey in binary 106 | * @return string public key as binary 107 | */ 108 | final public function derivePublicKey($privateKey) { 109 | $privateKeyElement = \FieldElement::fromString($privateKey); 110 | return Salt::instance()->crypto_scalarmult_base($privateKeyElement)->toString(); 111 | } 112 | 113 | /** 114 | * @param $imageData 115 | * @param $recipientPublicKey 116 | * @param $senderPrivateKey 117 | * @throws \Threema\Core\Exception 118 | * @return EncryptResult 119 | */ 120 | function encryptImageData($imageData, $recipientPublicKey, $senderPrivateKey) { 121 | $message = Salt::decodeInput($imageData); 122 | $nonce = $this->randomNonce(); 123 | $salt = Salt::instance(); 124 | 125 | //secret key 126 | $key = $salt->scalarmult($senderPrivateKey, $recipientPublicKey); 127 | $data = $salt->encrypt( 128 | $message, 129 | $message->getSize(), 130 | Salt::decodeInput($nonce), 131 | $key); 132 | 133 | if($data === false) { 134 | throw new Exception('encryption failed'); 135 | } 136 | 137 | return new EncryptResult($data->toString(), $senderPrivateKey, $nonce, strlen($data->toString())); 138 | } 139 | 140 | /** 141 | * Check if implementation supported 142 | * @return bool 143 | */ 144 | public function isSupported() { 145 | return class_exists('Salt'); 146 | } 147 | 148 | /** 149 | * Validate crypt tool 150 | * 151 | * @return bool 152 | * @throws Exception 153 | */ 154 | public function validate() { 155 | if(false === $this->isSupported()) { 156 | throw new Exception('SALT implementation not supported'); 157 | } 158 | 159 | if(PHP_INT_SIZE < 8) { 160 | throw new Exception('Pure PHP Crypto implementation requires 64Bit PHP. Please install the libsodium PHP extension.'); 161 | } 162 | return true; 163 | } 164 | 165 | /** 166 | * @return string 167 | */ 168 | public function getName() { 169 | return 'Salt'; 170 | } 171 | 172 | /** 173 | * Description of the CryptTool 174 | * @return string 175 | */ 176 | public function getDescription() { 177 | return 'Pure PHP implementation, please try to install and use the libsodium PHP extension for a better performance'; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tests/CryptToolTest.php: -------------------------------------------------------------------------------- 1 | doTest(function(CryptTool $cryptTool, $prefix) { 22 | $this->assertNotNull($cryptTool, $prefix.' could not instance crypto tool'); 23 | $keyPair = $cryptTool->generateKeyPair(); 24 | $this->assertNotNull($keyPair, $prefix.': invalid key pair'); 25 | $this->assertNotNull($keyPair->privateKey, $prefix.': private key is null'); 26 | $this->assertNotNull($keyPair->publicKey, $prefix.': public key is null'); 27 | }); 28 | } 29 | 30 | /** 31 | * test generating random nonce 32 | */ 33 | public function testRandomNonce() { 34 | $this->doTest(function(CryptTool $cryptTool, $prefix) { 35 | $randomNonce = $cryptTool->randomNonce(); 36 | $this->assertEquals(24, strlen($randomNonce), $prefix.': random nonce size not 24'); 37 | }); 38 | } 39 | 40 | public function testDecrypt() { 41 | /** @noinspection PhpUnusedParameterInspection */ 42 | $this->doTest(function(CryptTool $cryptTool, $prefix) { 43 | $nonce = '0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0'; 44 | $box = '45181c7aed95a1c100b1b559116c61b43ce15d04014a805288b7d14bf3a993393264fe554794ce7d6007233e8ef5a0f1ccdd704f34e7c7b77c72c239182caf1d061d6fff6ffbbfe8d3b8f3475c2fe352e563aa60290c666b2e627761e32155e62f048b52ef2f39c13ac229f393c67811749467396ecd09f42d32a4eb419117d0451056ac18fac957c52b0cca67568e2d97e5a3fd829a77f914a1ad403c5909fd510a313033422ea5db71eaf43d483238612a54cb1ecfe55259b1de5579e67c6505df7d674d34a737edf721ea69d15b567bc2195ec67e172f3cb8d6842ca88c29138cc33e9351dbc1e4973a82e1cf428c1c763bb8f3eb57770f914a'; 45 | 46 | $privateKey = Common::getPrivateKey(Constants::otherPrivateKey); 47 | $this->assertNotNull($privateKey); 48 | 49 | $publicKey = Common::getPublicKey(Constants::myPublicKey); 50 | $this->assertNotNull($publicKey); 51 | 52 | $message = $cryptTool->decryptMessage(hex2bin($box), 53 | hex2bin($privateKey), 54 | hex2bin($publicKey), 55 | hex2bin($nonce)); 56 | 57 | $this->assertNotNull($message); 58 | $this->assertTrue($message instanceof TextMessage); 59 | if($message instanceof TextMessage) { 60 | $this->assertEquals($message->getText(), 'Dies ist eine Testnachricht. äöü'); 61 | } 62 | }); 63 | } 64 | 65 | public function testEncrypt() { 66 | /** @noinspection PhpUnusedParameterInspection */ 67 | $this->doTest(function(CryptTool $cryptTool, $prefix) { 68 | $text = 'Dies ist eine Testnachricht. äöü'; 69 | $nonce = '0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0'; 70 | 71 | $privateKey = Common::getPrivateKey(Constants::myPrivateKey); 72 | $this->assertNotNull($privateKey); 73 | 74 | $publicKey = Common::getPublicKey(Constants::otherPublicKey); 75 | $this->assertNotNull($publicKey); 76 | 77 | $message = $cryptTool->encryptMessageText($text, 78 | hex2bin($privateKey), 79 | hex2bin($publicKey), 80 | hex2bin($nonce)); 81 | 82 | $this->assertNotNull($message); 83 | 84 | $box = $cryptTool->decryptMessage($message, 85 | hex2bin(Common::getPrivateKey(Constants::otherPrivateKey)), 86 | hex2bin(Common::getPublicKey(Constants::myPublicKey)), 87 | hex2bin($nonce)); 88 | 89 | $this->assertNotNull($box); 90 | }); 91 | } 92 | 93 | 94 | public function testDerivePublicKey() { 95 | $this->doTest(function(CryptTool $cryptTool, $prefix){ 96 | $publicKey = $cryptTool->derivePublicKey(hex2bin(Common::getPrivateKey(Constants::myPrivateKey))); 97 | $myPublicKey = hex2bin(Common::getPublicKey(Constants::myPublicKey)); 98 | 99 | $this->assertEquals($publicKey, $myPublicKey, $prefix.' derive public key failed'); 100 | }); 101 | } 102 | 103 | public function testEncryptImage() { 104 | $threemaIconContent = file_get_contents(dirname(__FILE__).'/threema.jpg'); 105 | 106 | /** @noinspection PhpUnusedParameterInspection */ 107 | $this->doTest(function(CryptTool $cryptTool, $prefix) use($threemaIconContent) { 108 | $privateKey = hex2bin(Common::getPrivateKey(Constants::myPrivateKey)); 109 | $publicKey = hex2bin(Common::getPublicKey(Constants::myPublicKey)); 110 | 111 | $otherPrivateKey = hex2bin(Common::getPrivateKey(Constants::otherPrivateKey)); 112 | $otherPublicKey = hex2bin(Common::getPublicKey(Constants::otherPublicKey)); 113 | 114 | $result = $cryptTool->encryptImage($threemaIconContent, $privateKey, $otherPublicKey); 115 | 116 | $decryptedImage = $cryptTool->decryptImage($result->getData(), $publicKey, $otherPrivateKey, $result->getNonce()); 117 | 118 | $this->assertEquals($decryptedImage, $threemaIconContent, 'decryption of image failed'); 119 | 120 | }); 121 | } 122 | private function doTest(\Closure $c) { 123 | foreach(array( 124 | 'Salt' => CryptTool::createInstance(CryptTool::TYPE_SALT), 125 | 'Sodium' => CryptTool::createInstance(CryptTool::TYPE_SODIUM) 126 | ) as $key => $instance) { 127 | 128 | if($instance === null) { 129 | echo $key.": could not instance crypt tool\n"; 130 | break; 131 | } 132 | /** @noinspection PhpUndefinedMethodInspection */ 133 | $this->assertTrue($instance->isSupported(), $key.' not supported'); 134 | $c->__invoke($instance, $key); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /source/Salt/Salsa20/Salsa20.php: -------------------------------------------------------------------------------- 1 | >= 8; 36 | $x[1+$offset] = $u & 0xff; $u >>= 8; 37 | $x[2+$offset] = $u & 0xff; $u >>= 8; 38 | $x[3+$offset] = $u & 0xff; 39 | } 40 | 41 | function rotate(&$x, $y, $z, $c) { 42 | $u = $y + $z; 43 | $u &= 0xffffffff; 44 | $x ^= ($u << $c) | ($u >> (32 - $c)); 45 | $x &= 0xffffffff; 46 | } 47 | 48 | function core($out, $in, $k, $c, $salsa20 = true) { 49 | $x0 = $this->load($c, 0); 50 | $x1 = $this->load($k, 0); 51 | $x2 = $this->load($k, 4); 52 | $x3 = $this->load($k, 8); 53 | $x4 = $this->load($k, 12); 54 | $x5 = $this->load($c, 4); 55 | $x6 = $this->load($in, 0); 56 | $x7 = $this->load($in, 4); 57 | $x8 = $this->load($in, 8); 58 | $x9 = $this->load($in, 12); 59 | $x10 = $this->load($c, 8); 60 | $x11 = $this->load($k, 16); 61 | $x12 = $this->load($k, 20); 62 | $x13 = $this->load($k, 24); 63 | $x14 = $this->load($k, 28); 64 | $x15 = $this->load($c, 12); 65 | 66 | if ($salsa20) { 67 | $j0 = $x0; $j1 = $x1; $j2 = $x2; $j3 = $x3; $j4 = $x4; 68 | $j5 = $x5; $j6 = $x6; $j7 = $x7; $j8 = $x8; $j9 = $x9; 69 | $j10 = $x10; $j11 = $x11; $j12 = $x12; $j13 = $x13; 70 | $j14 = $x14; $j15 = $x15; 71 | } 72 | 73 | for ($i = 0; $i < 20; $i += 2) { 74 | $this->rotate($x4, $x0, $x12, 7); 75 | $this->rotate($x8, $x4, $x0, 9); 76 | $this->rotate($x12, $x8, $x4, 13); 77 | $this->rotate($x0, $x12, $x8, 18); 78 | 79 | $this->rotate($x9, $x5, $x1, 7); 80 | $this->rotate($x13, $x9, $x5, 9); 81 | $this->rotate($x1, $x13, $x9, 13); 82 | $this->rotate($x5, $x1, $x13, 18); 83 | 84 | $this->rotate($x14, $x10, $x6, 7); 85 | $this->rotate($x2, $x14, $x10, 9); 86 | $this->rotate($x6, $x2, $x14, 13); 87 | $this->rotate($x10, $x6, $x2, 18); 88 | 89 | $this->rotate($x3, $x15, $x11, 7); 90 | $this->rotate($x7, $x3, $x15, 9); 91 | $this->rotate($x11, $x7, $x3, 13); 92 | $this->rotate($x15, $x11, $x7, 18); 93 | 94 | $this->rotate($x1, $x0, $x3, 7); 95 | $this->rotate($x2, $x1, $x0, 9); 96 | $this->rotate($x3, $x2, $x1, 13); 97 | $this->rotate($x0, $x3, $x2, 18); 98 | 99 | $this->rotate($x6, $x5, $x4, 7); 100 | $this->rotate($x7, $x6, $x5, 9); 101 | $this->rotate($x4, $x7, $x6, 13); 102 | $this->rotate($x5, $x4, $x7, 18); 103 | 104 | $this->rotate($x11, $x10, $x9, 7); 105 | $this->rotate($x8, $x11, $x10, 9); 106 | $this->rotate($x9, $x8, $x11, 13); 107 | $this->rotate($x10, $x9, $x8, 18); 108 | 109 | $this->rotate($x12, $x15, $x14, 7); 110 | $this->rotate($x13, $x12, $x15, 9); 111 | $this->rotate($x14, $x13, $x12, 13); 112 | $this->rotate($x15, $x14, $x13, 18); 113 | } 114 | 115 | if ($salsa20) { 116 | $x0 += $j0; $x1 += $j1; $x2 += $j2; $x3 += $j3; $x4 += $j4; 117 | $x5 += $j5; $x6 += $j6; $x7 += $j7; $x8 += $j8; $x9 += $j9; 118 | $x10 += $j10; $x11 += $j11; $x12 += $j12; $x13 += $j13; 119 | $x14 += $j14; $x15 += $j15; 120 | 121 | $this->store($out, 0, $x0); 122 | $this->store($out, 4, $x1); 123 | $this->store($out, 8, $x2); 124 | $this->store($out, 12, $x3); 125 | $this->store($out, 16, $x4); 126 | $this->store($out, 20, $x5); 127 | $this->store($out, 24, $x6); 128 | $this->store($out, 28, $x7); 129 | $this->store($out, 32, $x8); 130 | $this->store($out, 36, $x9); 131 | $this->store($out, 40, $x10); 132 | $this->store($out, 44, $x11); 133 | $this->store($out, 48, $x12); 134 | $this->store($out, 52, $x13); 135 | $this->store($out, 56, $x14); 136 | $this->store($out, 60, $x15); 137 | } else { 138 | $this->store($out, 0, $x0); 139 | $this->store($out, 4, $x5); 140 | $this->store($out, 8, $x10); 141 | $this->store($out, 12, $x15); 142 | $this->store($out, 16, $x6); 143 | $this->store($out, 20, $x7); 144 | $this->store($out, 24, $x8); 145 | $this->store($out, 28, $x9); 146 | } 147 | } 148 | 149 | function stream($out, $m, $mlen ,$n, $k) { 150 | if (!$mlen) return false; 151 | $z = new SplFixedArray(16); 152 | $x = new SplFixedArray(64); 153 | 154 | for ($i = 0;$i < 8;++$i) $z[$i] = $n[$i]; 155 | 156 | if ($m) { 157 | $l = count($m); 158 | if ($l < $mlen) { 159 | for ($i = $l;$i < $mlen;++$i) $m[$i] = 0; 160 | } 161 | $mpos = 0; 162 | } 163 | 164 | $outpos = 0; 165 | while ($mlen >= 64) { 166 | $this->core($x, $z, $k, Salsa20::$sigma); 167 | for ($i = 0;$i < 64;++$i) { 168 | $out[$i+$outpos] = ($m?$m[$i+$mpos]:0) ^ $x[$i]; 169 | } 170 | $u = 1; 171 | for ($i = 8;$i < 16;++$i) { 172 | $u += $z[$i]; 173 | $z[$i] = $u & 0xff; 174 | $u >>= 8; 175 | } 176 | 177 | $mlen -= 64; 178 | $outpos += 64; 179 | if ($m) $mpos += 64; 180 | } 181 | 182 | if ($mlen) { 183 | $this->core($x, $z, $k, Salsa20::$sigma); 184 | for ($i = 0;$i < $mlen;++$i) { 185 | $out[$i+$outpos] = ($m?$m[$i+$mpos]:0) ^ $x[$i]; 186 | } 187 | } 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /source/Threema/Console/Command/Base.php: -------------------------------------------------------------------------------- 1 | subject = $subject; 55 | $this->requiredArguments = $requiredArguments; 56 | $this->description = $description; 57 | $this->optionalArguments = $optionalArguments; 58 | } 59 | 60 | /** 61 | * @return int 62 | */ 63 | public function getRequiredArgumentCount() { 64 | return null !== $this->requiredArguments ? count($this->requiredArguments) : 0; 65 | } 66 | 67 | /** 68 | * @return int 69 | */ 70 | public function getAllArgumentsCount() { 71 | return $this->getRequiredArgumentCount() + (null !== $this->optionalArguments ? count($this->optionalArguments) : 0); 72 | } 73 | 74 | /** 75 | * @param string $pos 76 | * @return int 77 | * @throws \Threema\Core\Exception 78 | */ 79 | private function getIndexByPos($pos){ 80 | $i = array_search($pos, $this->requiredArguments); 81 | if(false === $i) { 82 | $i = array_search($pos, $this->optionalArguments); 83 | if(false !== $i) { 84 | $i += count($this->requiredArguments); 85 | } 86 | } 87 | if(false === $i) { 88 | throw new Exception('argument '.$pos.' not found'); 89 | } 90 | 91 | return $i; 92 | } 93 | 94 | /** 95 | * @param string $pos 96 | * @return null|string 97 | * @throws \Threema\Core\Exception 98 | */ 99 | public function getArgument($pos) { 100 | $i = $this->getIndexByPos($pos); 101 | $required = $i < count($this->requiredArguments); 102 | 103 | if(false === array_key_exists($i, $this->arguments)) { 104 | if(true === $required) { 105 | throw new Exception('no argument at position '.$i.' ('.$pos.')'); 106 | } 107 | else { 108 | return null; 109 | } 110 | } 111 | return $this->arguments[$i]; 112 | } 113 | 114 | /** 115 | * return a valid file path 116 | * 117 | * @param string $pos 118 | * @return null|string 119 | * @throws \Threema\Core\Exception 120 | */ 121 | public function getArgumentFile($pos) { 122 | $i = $this->getIndexByPos($pos); 123 | 124 | if(false === is_file($this->arguments[$i])) { 125 | throw new Exception('argument '.$i.' is not a file'); 126 | } 127 | 128 | return $this->arguments[$i]; 129 | } 130 | 131 | /** 132 | * @param string $pos 133 | * @return null|string 134 | */ 135 | public function getArgumentPrivateKey($pos) { 136 | $content = Common::getPrivateKey($this->getArgumentStringOrFileContent($pos)); 137 | if(null !== $content) { 138 | return hex2bin($content); 139 | } 140 | return null; 141 | } 142 | 143 | /** 144 | * @param string $pos 145 | * @return null|string 146 | */ 147 | public function getArgumentPublicKey($pos) { 148 | $content = Common::getPublicKey($this->getArgumentStringOrFileContent($pos)); 149 | if(null !== $content) { 150 | return hex2bin($content); 151 | } 152 | return null; 153 | } 154 | 155 | /** 156 | * @param string $pos 157 | * @return null|string 158 | */ 159 | private function getArgumentStringOrFileContent($pos) { 160 | $content = $this->getArgument($pos); 161 | if(file_exists($content)) { 162 | $content = trim(file_get_contents($content)); 163 | } 164 | return $content; 165 | } 166 | 167 | /** 168 | * @param string $pos 169 | * @return null|string 170 | */ 171 | public function getArgumentThreemaId($pos) { 172 | $content = $this->getArgument($pos); 173 | if(null !== $content && strlen($content) == 8) { 174 | return strtoupper($content); 175 | } 176 | return null; 177 | } 178 | 179 | /** 180 | * @return string 181 | */ 182 | protected function readStdIn() { 183 | $f = fopen( 'php://stdin', 'r' ); 184 | 185 | $lines = array(); 186 | while( $line = trim(fgets($f)) ) { 187 | 188 | if(strlen($line) == 0 || $line == "\n") { 189 | continue; 190 | } 191 | 192 | $lines[] = $line; 193 | } 194 | 195 | return implode("\n", $lines); 196 | } 197 | 198 | /** 199 | * @param array $arguments 200 | * @throws \Threema\Core\Exception 201 | */ 202 | final function run(array $arguments) { 203 | $this->arguments = $arguments; 204 | 205 | $argCount = null !== $this->arguments ? count($this->arguments) : 0; 206 | 207 | if($argCount < count($this->requiredArguments)) { 208 | throw new Exception('invalid count of arguments'); 209 | } 210 | $this->doRun(); 211 | } 212 | 213 | /** 214 | * @param bool $shellColors 215 | * @return string 216 | */ 217 | final public function help($shellColors = true) { 218 | $args = array_merge($this->requiredArguments, $this->optionalArguments); 219 | 220 | if(count($args) > 0) { 221 | $colorPrefix = ''; 222 | $colorPostfix = ''; 223 | if(true === $shellColors) { 224 | $colorPrefix = "\033[0;36m\033[40m"; 225 | $colorPostfix = "\033[0m"; 226 | } 227 | return $colorPrefix.'<'.implode('> <', $args).'>'.$colorPostfix; 228 | } 229 | return ''; 230 | } 231 | 232 | /** 233 | * @return string 234 | */ 235 | final public function description() { 236 | return $this->description; 237 | } 238 | 239 | /** 240 | * @return string 241 | */ 242 | final public function subject() { 243 | return $this->subject; 244 | } 245 | 246 | abstract function doRun(); 247 | } 248 | -------------------------------------------------------------------------------- /source/Threema/Console/Run.php: -------------------------------------------------------------------------------- 1 | arguments = $arguments; 63 | $this->scriptName = basename(array_shift($this->arguments)); 64 | $this->publicKeyStore = $publicKeyStore; 65 | 66 | $this->registerSubject('Local operations (no network communication)'); 67 | $this->register('-e', new Encrypt()); 68 | $this->register('-D', new Decrypt()); 69 | $this->register(array('-h', '-e'), new HashEmail()); 70 | $this->register(array('-h', '-p'), new HashPhone()); 71 | $this->register('-g', new GenerateKeyPair()); 72 | $this->register('-d', new DerivePublicKey()); 73 | 74 | $this->registerSubject('Network operations'); 75 | //network operations 76 | $this->register('-s', new SendSimple($this->publicKeyStore)); 77 | $this->register('-S', new SendE2EText($this->publicKeyStore)); 78 | $this->register(array('-S', '-i'), new SendE2EImage($this->publicKeyStore)); 79 | $this->register(array('-S', '-f'), new SendE2EFile($this->publicKeyStore)); 80 | $this->register(array('-l', '-e'), new LookupIdByEmail($this->publicKeyStore)); 81 | $this->register(array('-l', '-p'), new LookupIdByPhoneNo($this->publicKeyStore)); 82 | $this->register(array('-l', '-k'), new LookupPublicKeyById($this->publicKeyStore)); 83 | $this->register(array('-c'), new Capability($this->publicKeyStore)); 84 | $this->register(array('-r'), new ReceiveMessage($this->publicKeyStore)); 85 | $this->register(array('-C'), new Credits($this->publicKeyStore)); 86 | } 87 | 88 | private function register($argumentKey, Base $command) { 89 | if(is_scalar($argumentKey)) { 90 | $argumentKey = array($argumentKey); 91 | } 92 | 93 | //check for existing commands with the same arguments 94 | foreach($this->commands as $commandValues) { 95 | $ex = $commandValues[0]; 96 | if(null !== $ex && is_array($ex)) { 97 | if(count($ex) == count($argumentKey) 98 | && count(array_diff($ex, $argumentKey)) == 0) { 99 | throw new Exception('arguments '.implode($argumentKey).' already used'); 100 | } 101 | } 102 | } 103 | $this->commands[] = array($argumentKey, $command); 104 | return $this; 105 | } 106 | 107 | private function registerSubject($name) { 108 | $this->commands[] = $name; 109 | } 110 | 111 | public function run() { 112 | $found = null; 113 | $argumentLength = 0; 114 | 115 | //find the correct command by arguments and arguments count 116 | foreach($this->commands as $data) { 117 | if(is_scalar($data)) { 118 | continue; 119 | } 120 | 121 | list($keys, $command) = $data; 122 | if(array_slice($this->arguments, 0, count($keys)) == $keys) { 123 | $argCount = count($this->arguments)-count($keys); 124 | 125 | /** @noinspection PhpUndefinedMethodInspection */ 126 | if($argCount >= $command->getRequiredArgumentCount() 127 | && $argCount <= $command->getAllArgumentsCount()) { 128 | $found = $command; 129 | $argumentLength = count($keys); 130 | break; 131 | } 132 | } 133 | } 134 | 135 | if($argumentLength > 0) { 136 | array_splice($this->arguments, 0, $argumentLength); 137 | } 138 | 139 | if(null === $found) { 140 | $this->help(); 141 | } 142 | else { 143 | 144 | 145 | try { 146 | $found->run($this->arguments); 147 | } 148 | catch(Exception $x) { 149 | Common::l(); 150 | Common::e('ERROR: '.$x->getMessage()); 151 | Common::e(get_class($x)); 152 | Common::l(); 153 | } 154 | } 155 | } 156 | 157 | private function help() { 158 | $defaultCryptTool = CryptTool::getInstance(); 159 | 160 | Common::l(); 161 | Common::l('Threema PHP MsgApi Tool'); 162 | Common::l('Version: '.MSGAPI_SDK_VERSION); 163 | Common::l('CryptTool: '.$defaultCryptTool->getName().' '.$defaultCryptTool->getDescription()); 164 | Common::l(str_repeat('.', 40)); 165 | Common::l(); 166 | foreach($this->commands as $data) { 167 | if(is_scalar($data)) { 168 | Common::l($data); 169 | Common::l(str_repeat('-', strlen($data))); 170 | Common::l(); 171 | } 172 | else { 173 | list($key, $command) = $data; 174 | Common::ln($this->scriptName.' '."\033[1;33m".implode(' ', $key)."\033[0m".' '.$command->help()); 175 | Common::l(); 176 | /** @noinspection PhpUndefinedMethodInspection */ 177 | Common::l($command->description(), 1); 178 | Common::l(); 179 | } 180 | } 181 | } 182 | 183 | public function writeHelp(\Closure $writer) { 184 | if(null !== $writer) { 185 | 186 | foreach($this->commands as $data) { 187 | if(is_scalar($data)) { 188 | $writer->__invoke($data, null, null, false); 189 | } 190 | else { 191 | list($key, $command) = $data; 192 | $writer->__invoke($command->subject(false), $this->scriptName.' '.implode(' ', $key).' '.$command->help(false), $command->description(), true); 193 | } 194 | } 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /source/Salt/Poly1305/Poly1305.php: -------------------------------------------------------------------------------- 1 | > 8) & 0xff; 23 | } 24 | 25 | public function init($key) { 26 | $ctx = new SplFixedArray(6); 27 | $ctx[0] = new SplFixedArray(16); // buffer 28 | $ctx[1] = 0; // leftover 29 | $ctx[2] = new SplFixedArray(10); // r 30 | $ctx[3] = new SplFixedArray(10); // h 31 | $ctx[4] = new SplFixedArray(8); // pad 32 | $ctx[5] = 0; // final 33 | 34 | $t = new SplFixedArray(8); 35 | 36 | for ($i = 8; $i--;) $t[$i] = $this->U8TO16($key, $i*2); 37 | 38 | $ctx[2][0] = $t[0] & 0x1fff; 39 | $ctx[2][1] = (($t[0] >> 13) | ($t[1] << 3)) & 0x1fff; 40 | $ctx[2][2] = (($t[1] >> 10) | ($t[2] << 6)) & 0x1f03; 41 | $ctx[2][3] = (($t[2] >> 7) | ($t[3] << 9)) & 0x1fff; 42 | $ctx[2][4] = (($t[3] >> 4) | ($t[4] << 12)) & 0x00ff; 43 | $ctx[2][5] = ($t[4] >> 1) & 0x1ffe; 44 | $ctx[2][6] = (($t[4] >> 14) | ($t[5] << 2)) & 0x1fff; 45 | $ctx[2][7] = (($t[5] >> 11) | ($t[6] << 5)) & 0x1f81; 46 | $ctx[2][8] = (($t[6] >> 8) | ($t[7] << 8)) & 0x1fff; 47 | $ctx[2][9] = ($t[7] >> 5) & 0x007f; 48 | 49 | for ($i = 8; $i--;) { 50 | $ctx[3][$i] = 0; 51 | $ctx[4][$i] = $this->U8TO16($key, 16+(2*$i)); 52 | } 53 | 54 | $ctx[3][8] = 0; 55 | $ctx[3][9] = 0; 56 | $ctx[1] = 0; 57 | $ctx[5] = 0; 58 | 59 | return $ctx; 60 | } 61 | 62 | protected function blocks($ctx, $m, $mpos, $bytes) { 63 | $hibit = $ctx[5] ? 0 : (1 << 11); 64 | $t = new SplFixedArray(8); 65 | $d = new SplFixedArray(10); 66 | $c = 0; 67 | 68 | while ($bytes >= 16) { 69 | for ($i = 8; $i--;) $t[$i] = $this->U8TO16($m, $i*2+$mpos); 70 | 71 | $ctx[3][0] += $t[0] & 0x1fff; 72 | $ctx[3][1] += (($t[0] >> 13) | ($t[1] << 3)) & 0x1fff; 73 | $ctx[3][2] += (($t[1] >> 10) | ($t[2] << 6)) & 0x1fff; 74 | $ctx[3][3] += (($t[2] >> 7) | ($t[3] << 9)) & 0x1fff; 75 | $ctx[3][4] += (($t[3] >> 4) | ($t[4] << 12)) & 0x1fff; 76 | $ctx[3][5] += ($t[4] >> 1) & 0x1fff; 77 | $ctx[3][6] += (($t[4] >> 14) | ($t[5] << 2)) & 0x1fff; 78 | $ctx[3][7] += (($t[5] >> 11) | ($t[6] << 5)) & 0x1fff; 79 | $ctx[3][8] += (($t[6] >> 8) | ($t[7] << 8)) & 0x1fff; 80 | $ctx[3][9] += ($t[7] >> 5) | $hibit; 81 | 82 | for ($i = 0, $c = 0; $i < 10; $i++) { 83 | $d[$i] = $c; 84 | for ($j = 0; $j < 10; $j++) { 85 | $d[$i] += ($ctx[3][$j] & 0xffffffff) * (($j <= $i) ? $ctx[2][$i-$j] : (5 * $ctx[2][$i+10-$j])); 86 | if ($j === 4) { 87 | $c = ($d[$i] >> 13); 88 | $d[$i] &= 0x1fff; 89 | } 90 | } 91 | $c += ($d[$i] >> 13); 92 | $d[$i] &= 0x1fff; 93 | } 94 | $c = (($c << 2) + $c); 95 | $c += $d[0]; 96 | $d[0] = (($c & 0xffff) & 0x1fff); 97 | $c = ($c >> 13); 98 | $d[1] += $c; 99 | 100 | for ($i = 10; $i--;) $ctx[3][$i] = $d[$i] & 0xffff; 101 | 102 | $mpos += 16; 103 | $bytes -= 16; 104 | } 105 | } 106 | 107 | public function update($ctx, $m, $bytes) { 108 | $want = 0; $mpos = 0; 109 | 110 | if ($ctx[1]) { 111 | $want = 16 - $ctx[1]; 112 | if ($want > $bytes) $want = $bytes; 113 | for ($i = $want; $i--;) { 114 | $ctx[0][$ctx[1]+$i] = $m[$i+$mpos]; 115 | } 116 | $bytes -= $want; 117 | $mpos += $want; 118 | $ctx[1] += $want; 119 | if ($ctx[1] < 16) return; 120 | $this->blocks($ctx, $ctx[0], 0, 16); 121 | $ctx[1] = 0; 122 | } 123 | 124 | if ($bytes >= 16) { 125 | $want = ($bytes & ~(16 - 1)); 126 | $this->blocks($ctx, $m, $mpos, $want); 127 | $mpos += $want; 128 | $bytes -= $want; 129 | } 130 | 131 | if ($bytes) { 132 | for ($i = $bytes; $i--;) { 133 | $ctx[0][$ctx[1]+$i] = $m[$i+$mpos]; 134 | } 135 | $ctx[1] += $bytes; 136 | } 137 | } 138 | 139 | public function finish($ctx, $mac) { 140 | $g = new SplFixedArray(10); 141 | 142 | if ($ctx[1]) { 143 | $i = $ctx[1]; 144 | $ctx[0][$i++] = 1; 145 | for (; $i < 16; $i++) 146 | $ctx[0][$i] = 0; 147 | $ctx[5] = 1; 148 | $this->blocks($ctx, $ctx[0], 0, 16); 149 | } 150 | 151 | $c = $ctx[3][1] >> 13; 152 | $ctx[3][1] &= 0x1fff; 153 | for ($i = 2; $i < 10; $i++) { 154 | $ctx[3][$i] += $c; 155 | $c = $ctx[3][$i] >> 13; 156 | $ctx[3][$i] &= 0x1fff; 157 | } 158 | $ctx[3][0] += ($c * 5); 159 | $c = $ctx[3][0] >> 13; 160 | $ctx[3][0] &= 0x1fff; 161 | $ctx[3][1] += $c; 162 | $c = $ctx[3][1] >> 13; 163 | $ctx[3][1] &= 0x1fff; 164 | $ctx[3][2] += $c; 165 | 166 | $g[0] = $ctx[3][0] + 5; 167 | $c = $g[0] >> 13; 168 | $g[0] &= 0x1fff; 169 | for ($i = 1; $i < 10; $i++) { 170 | $g[$i] = $ctx[3][$i] + $c; 171 | $c = $g[$i] >> 13; 172 | $g[$i] &= 0x1fff; 173 | } 174 | $g[9] -= (1 << 13); 175 | $g[9] &= 0xffff; 176 | 177 | $mask = ($g[9] >> 15) - 1; 178 | for ($i = 10; $i--;) $g[$i] &= $mask; 179 | $mask = ~$mask; 180 | for ($i = 10; $i--;) $ctx[3][$i] = ($ctx[3][$i] & $mask) | $g[$i]; 181 | 182 | $ctx[3][0] = (($ctx[3][0] ) | ($ctx[3][1] << 13)) & 0xffff; 183 | $ctx[3][1] = (($ctx[3][1] >> 3) | ($ctx[3][2] << 10)) & 0xffff; 184 | $ctx[3][2] = (($ctx[3][2] >> 6) | ($ctx[3][3] << 7)) & 0xffff; 185 | $ctx[3][3] = (($ctx[3][3] >> 9) | ($ctx[3][4] << 4)) & 0xffff; 186 | $ctx[3][4] = (($ctx[3][4] >> 12) | ($ctx[3][5] << 1) | ($ctx[3][6] << 14)) & 0xffff; 187 | $ctx[3][5] = (($ctx[3][6] >> 2) | ($ctx[3][7] << 11)) & 0xffff; 188 | $ctx[3][6] = (($ctx[3][7] >> 5) | ($ctx[3][8] << 8)) & 0xffff; 189 | $ctx[3][7] = (($ctx[3][8] >> 8) | ($ctx[3][9] << 5)) & 0xffff; 190 | 191 | $f = ($ctx[3][0] & 0xffffffff) + $ctx[4][0]; 192 | $ctx[3][0] = $f & 0xffff; 193 | for ($i = 1; $i < 8; $i++) { 194 | $f = ($ctx[3][$i] & 0xffffffff) + $ctx[4][$i] + ($f >> 16); 195 | $ctx[3][$i] = $f & 0xffff; 196 | } 197 | 198 | for ($i = 8; $i--;) { 199 | $this->U16TO8($mac, $i*2, $ctx[3][$i]); 200 | $ctx[4][$i] = 0; 201 | } 202 | for ($i = 10; $i--;) { 203 | $ctx[3][$i] = 0; 204 | $ctx[2][$i] = 0; 205 | } 206 | } 207 | 208 | protected static $_instance; 209 | 210 | public static function instance() { 211 | if (!isset(static::$_instance)) { 212 | static::$_instance = new Poly1305(); 213 | } 214 | return static::$_instance; 215 | } 216 | 217 | public static function auth($mac, $m, $bytes, $key) { 218 | $p = Poly1305::instance(); 219 | $ctx = $p->init($key); 220 | $p->update($ctx, $m, $bytes); 221 | $p->finish($ctx, $mac); 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Dear visitor** 2 | **This repository is no longer maintained.** 3 | **To download the latest version of the Threema Getway SDK please go to:** 4 | https://gateway.threema.ch/en/developer/sdk-php 5 | 6 | **A fork of this repository is maintained here: https://github.com/rugk/threema-msgapi-sdk-php** 7 | 8 | ____ 9 | 10 | 11 | # msgapi-sdk-php 12 | Version: 1.1.2 13 | 14 | ## Installation 15 | - Install PHP 5.4 or later: [https://secure.php.net/manual/en/install.php](https://secure.php.net/manual/en/install.php) 16 | - For better encryption performance, install the [libsodium PHP extension](https://github.com/jedisct1/libsodium-php). 17 | 18 | This step is optional; if the libsodium PHP extension is not available, the SDK will automatically fall back to (slower) pure PHP code for ECC encryption (file and image sending not supported). 19 | 20 | A 64bit version of PHP is required for pure PHP encryption. 21 | 22 | To install the libsodium PHP extension: 23 | 24 | ```shell 25 | pecl install libsodium 26 | ``` 27 | 28 | Then add the following line to your php.ini file: 29 | 30 | ```ini 31 | extension=libsodium.so 32 | ``` 33 | 34 | If you want to check whether your server meets the requirements and everything is configured properly you can execute `threema-msgapi-tool.php` without any parameters on the console or point your browser to the location where it is saved on your server. 35 | 36 | ## SDK usage 37 | ### Creating a connection 38 | 39 | ```php 40 | use Threema\MsgApi\Connection; 41 | use Threema\MsgApi\ConnectionSettings; 42 | use Threema\MsgApi\Receiver; 43 | 44 | require_once('lib/bootstrap.php'); 45 | 46 | //define your connection settings 47 | $settings = new ConnectionSettings( 48 | '*THREEMA', 49 | 'THISISMYSECRET' 50 | ); 51 | 52 | //simple php file to store the public keys 53 | $publicKeyStore = new Threema\MsgApi\PublicKeyStores\PhpFile('/path/to/my/keystore.php'); 54 | 55 | //create a connection 56 | $connector = new Connection($settings, $publicKeyStore); 57 | ``` 58 | 59 | ### Creating a connection with advanced options 60 | **Attention:** This settings change internal values of the TLS connection. Choosing wrong settings can weaken the TLS connection or prevent connecting to the server. Use this options with care! 61 | 62 | Each of the additional options shown below is optional. You can leave it out or use `null` to use the default value for this option. 63 | 64 | ```php 65 | use Threema\MsgApi\Connection; 66 | use Threema\MsgApi\ConnectionSettings; 67 | use Threema\MsgApi\Receiver; 68 | 69 | require_once('lib/bootstrap.php'); 70 | 71 | //define your connection settings 72 | $settings = new ConnectionSettings( 73 | '*THREEMA', 74 | 'THISISMYSECRET' 75 | null, //the host to be use, set to null for default (recommend) 76 | [ 77 | 'forceHttps' => true, //set to true to force HTTPS, default: false 78 | 'tlsVersion' => '1.2', //set the version of TLS to be used, default: null 79 | 'tlsCipher' => 'ECDHE-RSA-AES128-GCM-SHA256' //choose a cipher or a list of ciphers, default: null 80 | ] 81 | ); 82 | 83 | //simple php file to store the public keys 84 | $publicKeyStore = new Threema\MsgApi\PublicKeyStores\PhpFile('/path/to/my/keystore.php'); 85 | 86 | //create a connection 87 | $connector = new Connection($settings, $publicKeyStore); 88 | ``` 89 | 90 | If you want to get a list of all ciphers you can use have a look at the [SSLLabs scan](https://www.ssllabs.com/ssltest/analyze.html?d=msgapi.threema.ch) and at the list of all available [OpenSSL ciphers](https://www.openssl.org/docs/manmaster/apps/ciphers.html). 91 | 92 | ### Sending a text message to a Threema ID (Simple Mode) 93 | 94 | ```php 95 | //create the connection 96 | //(...) 97 | //create a receiver 98 | $receiver = new Receiver('ABCD1234', Receiver::TYPE_ID); 99 | 100 | $result = $connector->sendSimple($receiver, "This is a Test Message"); 101 | if($result->isSuccess()) { 102 | echo 'new id created '.$result->getMessageId(); 103 | } 104 | else { 105 | echo 'error '.$result->getErrorMessage(); 106 | } 107 | ``` 108 | 109 | ### Sending a text message to a Threema ID (E2E Mode) 110 | 111 | ```php 112 | //create the connection 113 | //(...) 114 | 115 | $e2eHelper = new \Threema\MsgApi\Helpers\E2EHelper($senderPrivateKey,$connector); 116 | $result = $e2eHelper->sendTextMessage("TEST1234", "This is an end-to-end encrypted message"); 117 | 118 | if(true === $result->isSuccess()) { 119 | echo 'Message ID: '.$result->getMessageId() . "\n"; 120 | } 121 | else { 122 | echo 'Error: '.$result->getErrorMessage() . "\n"; 123 | } 124 | ``` 125 | 126 | ### Sending a file message to a Threema ID (E2E Mode) 127 | 128 | ```php 129 | //create the connection 130 | //(...) 131 | 132 | $senderPrivateKey = "MY_PUBLIC_KEY_IN_BIN"; 133 | $filePath = "/path/to/my/file.pdf"; 134 | 135 | $e2eHelper = new \Threema\MsgApi\Helpers\E2EHelper($senderPrivateKey,$connector); 136 | $result = $e2eHelper->sendFileMessage("TEST1234", $filePath); 137 | 138 | if(true === $result->isSuccess()) { 139 | echo 'File Message ID: '.$result->getMessageId() . "\n"; 140 | } 141 | else { 142 | echo 'Error: '.$result->getErrorMessage() . "\n"; 143 | } 144 | ``` 145 | 146 | ## Console client usage 147 | ### Local operations (no network communication) 148 | #### Encrypt 149 | 150 | ```shell 151 | threema-msgapi-tool.php -e 152 | ``` 153 | 154 | Encrypt standard input using the given sender private key and recipient public key. two lines to standard output: first the nonce (hex), and then the box (hex). 155 | 156 | #### Decrypt 157 | 158 | ```shell 159 | threema-msgapi-tool.php -D 160 | ``` 161 | 162 | Decrypt standard input using the given recipient private key and sender public key. The nonce must be given on the command line, and the box (hex) on standard input. Prints the decrypted message to standard output. 163 | 164 | #### Hash Email Address 165 | 166 | ```shell 167 | threema-msgapi-tool.php -h -e 168 | ``` 169 | 170 | Hash an email address for identity lookup. Prints the hash in hex. 171 | 172 | #### Hash Phone Number 173 | 174 | ```shell 175 | threema-msgapi-tool.php -h -p 176 | ``` 177 | 178 | Hash a phone number for identity lookup. Prints the hash in hex. 179 | 180 | #### Generate Key Pair 181 | 182 | ```shell 183 | threema-msgapi-tool.php -g 184 | ``` 185 | 186 | Generate a new key pair and write the private and public keys to the respective files (in hex). 187 | 188 | #### Derive Public Key 189 | 190 | ```shell 191 | threema-msgapi-tool.php -d 192 | ``` 193 | 194 | Derive the public key that corresponds with the given private key. 195 | 196 | ### Network operations 197 | #### Send Simple Message 198 | 199 | ```shell 200 | threema-msgapi-tool.php -s 201 | ``` 202 | 203 | Send a message from standard input with server-side encryption to the given ID. is the API identity and 'secret' is the API secret. the message ID on success. 204 | 205 | #### Send End-to-End Encrypted Text Message 206 | 207 | ```shell 208 | threema-msgapi-tool.php -S 209 | ``` 210 | 211 | Encrypt standard input and send the text message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success. 212 | 213 | #### Send a End-to-End Encrypted Image Message 214 | 215 | ```shell 216 | threema-msgapi-tool.php -S -i 217 | ``` 218 | 219 | Encrypt the image file and send the message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success. 220 | 221 | #### Send a End-to-End Encrypted File Message 222 | 223 | ```shell 224 | threema-msgapi-tool.php -S -f 225 | ``` 226 | 227 | Encrypt the file (and thumbnail if given) and send the message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success. 228 | 229 | #### ID-Lookup By Email Address 230 | 231 | ```shell 232 | threema-msgapi-tool.php -l -e 233 | ``` 234 | 235 | Lookup the ID linked to the given email address (will be hashed locally). 236 | 237 | #### ID-Lookup By Phone Number 238 | 239 | ```shell 240 | threema-msgapi-tool.php -l -p 241 | ``` 242 | 243 | Lookup the ID linked to the given phone number (will be hashed locally). 244 | 245 | #### Fetch Public Key 246 | 247 | ```shell 248 | threema-msgapi-tool.php -l -k 249 | ``` 250 | 251 | Lookup the public key for the given ID. 252 | 253 | #### Fetch Capability 254 | 255 | ```shell 256 | threema-msgapi-tool.php -c 257 | ``` 258 | 259 | Fetch the capabilities of a Threema ID 260 | 261 | #### Decrypt a Message and download the Files 262 | 263 | ```shell 264 | threema-msgapi-tool.php -r 265 | ``` 266 | 267 | Decrypt a box (must be provided on stdin) message and download (if the message is an image or file message) the file(s) to the given folder 268 | 269 | #### Remaining credits 270 | 271 | ```shell 272 | threema-msgapi-tool.php -C 273 | ``` 274 | 275 | Fetch remaining credits 276 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Connection.php: -------------------------------------------------------------------------------- 1 | setting = $setting; 54 | $this->publicKeyStore = $publicKeyStore; 55 | } 56 | 57 | /** 58 | * @param Receiver $receiver 59 | * @param $text 60 | * @return SendSimpleResult 61 | */ 62 | public function sendSimple(Receiver $receiver, $text) { 63 | $command = new SendSimple($receiver, $text); 64 | return $this->post($command); 65 | } 66 | 67 | /** 68 | * @param string $threemaId 69 | * @param string $nonce 70 | * @param string $box 71 | * @return SendE2EResult 72 | */ 73 | public function sendE2E($threemaId, $nonce, $box) { 74 | $command = new SendE2E($threemaId, $nonce, $box); 75 | return $this->post($command); 76 | } 77 | 78 | /** 79 | * @param $encryptedFileData (binary string) 80 | * @return UploadFileResult 81 | */ 82 | public function uploadFile($encryptedFileData) { 83 | $command = new UploadFile($encryptedFileData); 84 | return $this->postMultiPart($command); 85 | } 86 | 87 | 88 | /** 89 | * @param $blobId 90 | * @param callable $progress 91 | * @return DownloadFileResult 92 | */ 93 | public function downloadFile($blobId, \Closure $progress = null) { 94 | $command = new DownloadFile($blobId); 95 | return $this->get($command, $progress); 96 | } 97 | 98 | /** 99 | * @param $phoneNumber 100 | * @return LookupIdResult 101 | */ 102 | public function keyLookupByPhoneNumber($phoneNumber) { 103 | $command = new LookupPhone($phoneNumber); 104 | return $this->get($command); 105 | } 106 | 107 | /** 108 | * @param string $email 109 | * @return LookupIdResult 110 | */ 111 | public function keyLookupByEmail($email) { 112 | $command = new LookupEmail($email); 113 | return $this->get($command); 114 | } 115 | 116 | /** 117 | * @param string $threemaId valid threema id (8 Chars) 118 | * @return CapabilityResult 119 | */ 120 | public function keyCapability($threemaId) { 121 | return $this->get(new Capability($threemaId)); 122 | } 123 | 124 | 125 | /** 126 | * @return CreditsResult 127 | */ 128 | public function credits() { 129 | return $this->get(new Credits()); 130 | } 131 | 132 | /** 133 | * @param $threemaId 134 | * @return FetchPublicKeyResult 135 | */ 136 | public function fetchPublicKey($threemaId) { 137 | $publicKey = null; 138 | 139 | if (null !== $this->publicKeyStore) { 140 | $publicKey = $this->publicKeyStore->getPublicKey($threemaId); 141 | } 142 | 143 | if (null === $publicKey) { 144 | $command = new FetchPublicKey($threemaId); 145 | $result = $this->get($command); 146 | if (false === $result->isSuccess()) { 147 | return $result; 148 | } 149 | $publicKey = $result->getRawResponse(); 150 | 151 | if (null !== $this->publicKeyStore) { 152 | $this->publicKeyStore->setPublicKey($threemaId, $publicKey); 153 | } 154 | } 155 | 156 | //create a key result 157 | return new FetchPublicKeyResult(200, $publicKey); 158 | } 159 | 160 | /** 161 | * @param callable $progress 162 | * @return array 163 | */ 164 | private function createDefaultOptions(\Closure $progress = null) { 165 | $options = array( 166 | CURLOPT_RETURNTRANSFER => true 167 | ); 168 | 169 | //no progress 170 | if (null !== $progress) { 171 | $options[CURLOPT_NOPROGRESS] = false; 172 | $options[CURLOPT_PROGRESSFUNCTION] = $progress; 173 | } 174 | 175 | //tls settings 176 | 177 | if (true === $this->setting->getTlsOption(ConnectionSettings::tlsOptionForceHttps, false)) { 178 | //limit allowed protocols to HTTPS 179 | $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTPS; 180 | } 181 | if ($tlsVersion = $this->setting->getTlsOption(ConnectionSettings::tlsOptionVersion)) { 182 | if (is_int($tlsVersion)) { 183 | //if number is given use it 184 | $options[CURLOPT_SSLVERSION] = $tlsVersion; 185 | } else { 186 | //interpret strings as TLS versions 187 | switch ($tlsVersion) { 188 | case '1.0': 189 | $options[CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1_0; 190 | break; 191 | case '1.1': 192 | $options[CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1_1; 193 | break; 194 | case '1.2': 195 | $options[CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1_2; 196 | break; 197 | default: 198 | $options[CURLOPT_SSLVERSION] = CURL_SSLVERSION_DEFAULT; 199 | break; 200 | } 201 | } 202 | } 203 | if ($tlsCipher = $this->setting->getTlsOption(ConnectionSettings::tlsOptionCipher, null)) { 204 | if(true === is_string($tlsCipher)) { 205 | $options[CURLOPT_SSL_CIPHER_LIST] = $tlsCipher; 206 | } 207 | } 208 | return $options; 209 | } 210 | 211 | /** 212 | * @param array $params 213 | * @return array 214 | */ 215 | private function processRequestParams(array $params) { 216 | if (null === $params) { 217 | $params = array(); 218 | } 219 | 220 | $params['from'] = $this->setting->getThreemaId(); 221 | $params['secret'] = $this->setting->getSecret(); 222 | 223 | return $params; 224 | } 225 | 226 | /** 227 | * @param CommandInterface $command 228 | * @param callable $progress 229 | * @return Result 230 | */ 231 | protected function get(CommandInterface $command, \Closure $progress = null) { 232 | $params = $this->processRequestParams($command->getParams()); 233 | return $this->call($command->getPath(), 234 | $this->createDefaultOptions($progress), 235 | $params, 236 | function ($httpCode, $response) use ($command) { 237 | return $command->parseResult($httpCode, $response); 238 | }); 239 | } 240 | 241 | /** 242 | * @param CommandInterface $command 243 | * @return Result 244 | */ 245 | protected function post(CommandInterface $command) { 246 | $options = $this->createDefaultOptions(); 247 | $params = $this->processRequestParams($command->getParams()); 248 | 249 | $options[CURLOPT_POST] = true; 250 | $options[CURLOPT_POSTFIELDS] = http_build_query($params); 251 | $options[CURLOPT_HTTPHEADER] = array( 252 | 'Content-Type: application/x-www-form-urlencoded'); 253 | 254 | return $this->call($command->getPath(), $options, null, function ($httpCode, $response) use ($command) { 255 | return $command->parseResult($httpCode, $response); 256 | }); 257 | } 258 | 259 | /** 260 | * @param MultiPartCommandInterface $command 261 | * @return Result 262 | */ 263 | protected function postMultiPart(MultiPartCommandInterface $command) { 264 | $options = $this->createDefaultOptions(); 265 | $params = $this->processRequestParams($command->getParams()); 266 | 267 | $options[CURLOPT_POST] = true; 268 | $options[CURLOPT_HTTPHEADER] = array('Content-Type: multipart/form-data'); 269 | $options[CURLOPT_SAFE_UPLOAD] = true; 270 | $options[CURLOPT_POSTFIELDS] = array( 271 | 'blob' => $command->getData() 272 | ); 273 | 274 | return $this->call($command->getPath(), $options, $params, function ($httpCode, $response) use ($command) { 275 | return $command->parseResult($httpCode, $response); 276 | }); 277 | } 278 | 279 | /** 280 | * @param string $path 281 | * @param array $curlOptions 282 | * @param array $parameters 283 | * @param callable $result 284 | * @return mixed 285 | * @throws \Threema\Core\Exception 286 | */ 287 | private function call($path, array $curlOptions, array $parameters = null, \Closure $result = null) { 288 | $fullPath = new Url('', $this->setting->getHost()); 289 | $fullPath->addPath($path); 290 | 291 | if (null !== $parameters && count($parameters)) { 292 | foreach ($parameters as $key => $value) { 293 | $fullPath->setValue($key, $value); 294 | } 295 | } 296 | $session = curl_init($fullPath->getFullPath()); 297 | curl_setopt_array($session, $curlOptions); 298 | 299 | $response = curl_exec($session); 300 | if (false === $response) { 301 | throw new Exception($path . ' ' . curl_error($session)); 302 | } 303 | 304 | $httpCode = curl_getinfo($session, CURLINFO_HTTP_CODE); 305 | if (null === $result && $httpCode != 200) { 306 | throw new Exception($httpCode); 307 | } 308 | 309 | if (null !== $result) { 310 | return $result->__invoke($httpCode, $response); 311 | } else { 312 | return $response; 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Helpers/E2EHelper.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 41 | $this->cryptTool = $cryptTool; 42 | $this->privateKey = $privateKey; 43 | 44 | if(null === $this->cryptTool) { 45 | $this->cryptTool = CryptTool::getInstance(); 46 | } 47 | } 48 | 49 | /** 50 | * Crypt a text message and send it to the threemaId 51 | * 52 | * @param string $threemaId 53 | * @param string $text 54 | * @throws \Threema\Core\Exception 55 | * @return \Threema\MsgApi\Commands\Results\SendE2EResult 56 | */ 57 | public final function sendTextMessage($threemaId, $text) { 58 | //random nonce first 59 | $nonce = $this->cryptTool->randomNonce(); 60 | 61 | //fetch the public key 62 | $receiverPublicKey = $this->fetchPublicKeyAndCheckCapability($threemaId, null); 63 | 64 | //create a box 65 | $textMessage = $this->cryptTool->encryptMessageText( 66 | $text, 67 | $this->privateKey, 68 | $receiverPublicKey, 69 | $nonce); 70 | 71 | return $this->connection->sendE2E($threemaId, $nonce, $textMessage); 72 | } 73 | 74 | /** 75 | * Crypt a image file, upload the blob and send the image message to the threemaId 76 | * 77 | * @param string $threemaId 78 | * @param string $imagePath 79 | * @return \Threema\MsgApi\Commands\Results\SendE2EResult 80 | * @throws \Threema\Core\Exception 81 | */ 82 | public final function sendImageMessage($threemaId, $imagePath) { 83 | //analyse the file 84 | $fileAnalyzeResult = FileAnalysisTool::analyse($imagePath); 85 | 86 | if(null === $fileAnalyzeResult) { 87 | throw new Exception('could not analyze the file'); 88 | } 89 | 90 | if(false === in_array($fileAnalyzeResult->getMimeType(), array( 91 | 'image/jpg', 92 | 'image/jpeg', 93 | 'image/png' ))) { 94 | throw new Exception('file is not a jpg or png'); 95 | } 96 | 97 | //fetch the public key 98 | $receiverPublicKey = $this->fetchPublicKeyAndCheckCapability($threemaId, function(CapabilityResult $capabilityResult) { 99 | return true === $capabilityResult->canImage(); 100 | }); 101 | 102 | //encrypt the image file 103 | $encryptionResult = $this->cryptTool->encryptImage(file_get_contents($imagePath), $this->privateKey, $receiverPublicKey); 104 | $uploadResult = $this->connection->uploadFile($encryptionResult->getData()); 105 | 106 | if($uploadResult == null || !$uploadResult->isSuccess()) { 107 | throw new Exception('could not upload the image ('.$uploadResult->getErrorCode().' '.$uploadResult->getErrorMessage().') '.$uploadResult->getRawResponse()); 108 | } 109 | 110 | $nonce = $this->cryptTool->randomNonce(); 111 | 112 | //create a image message box 113 | $imageMessage = $this->cryptTool->encryptImageMessage( 114 | $uploadResult, 115 | $encryptionResult, 116 | $this->privateKey, 117 | $receiverPublicKey, 118 | $nonce); 119 | 120 | return $this->connection->sendE2E($threemaId, $nonce, $imageMessage); 121 | } 122 | 123 | /** 124 | * Crypt a file (and thumbnail if given), upload the blob and send it to the given threemaId 125 | * 126 | * @param string $threemaId 127 | * @param string $filePath 128 | * @param null|string $thumbnailPath 129 | * @throws \Threema\Core\Exception 130 | * @return \Threema\MsgApi\Commands\Results\SendE2EResult 131 | */ 132 | public final function sendFileMessage($threemaId, $filePath, $thumbnailPath = null) { 133 | //analyse the file 134 | $fileAnalyzeResult = FileAnalysisTool::analyse($filePath); 135 | 136 | if(null === $fileAnalyzeResult) { 137 | throw new Exception('could not analyze the file'); 138 | } 139 | 140 | //fetch the public key 141 | $receiverPublicKey = $this->fetchPublicKeyAndCheckCapability($threemaId, function(CapabilityResult $capabilityResult) { 142 | return true === $capabilityResult->canFile(); 143 | }); 144 | 145 | //encrypt the main file 146 | $encryptionResult = $this->cryptTool->encryptFile(file_get_contents($filePath)); 147 | $uploadResult = $this->connection->uploadFile($encryptionResult->getData()); 148 | 149 | if($uploadResult == null || !$uploadResult->isSuccess()) { 150 | throw new Exception('could not upload the file ('.$uploadResult->getErrorCode().' '.$uploadResult->getErrorMessage().') '.$uploadResult->getRawResponse()); 151 | } 152 | 153 | $thumbnailUploadResult = null; 154 | 155 | //encrypt the thumbnail file (if exists) 156 | if(strlen($thumbnailPath) > 0 && true === file_exists($thumbnailPath)) { 157 | //encrypt the main file 158 | $thumbnailEncryptionResult = $this->cryptTool->encryptFileThumbnail(file_get_contents($thumbnailPath), $encryptionResult->getKey()); 159 | $thumbnailUploadResult = $this->connection->uploadFile($thumbnailEncryptionResult->getData()); 160 | 161 | if($thumbnailUploadResult == null || !$thumbnailUploadResult->isSuccess()) { 162 | throw new Exception('could not upload the thumbnail file ('.$thumbnailUploadResult->getErrorCode().' '.$thumbnailUploadResult->getErrorMessage().') '.$thumbnailUploadResult->getRawResponse()); 163 | } 164 | } 165 | 166 | $nonce = $this->cryptTool->randomNonce(); 167 | 168 | //create a file message box 169 | $fileMessage = $this->cryptTool->encryptFileMessage( 170 | $uploadResult, 171 | $encryptionResult, 172 | $thumbnailUploadResult, 173 | $fileAnalyzeResult, 174 | $this->privateKey, 175 | $receiverPublicKey, 176 | $nonce); 177 | 178 | return $this->connection->sendE2E($threemaId, $nonce, $fileMessage); 179 | } 180 | 181 | /** 182 | * Encrypt a message and download the files of the message to the $outputFolder 183 | * 184 | * @param string $threemaId 185 | * @param string $messageId 186 | * @param string $box box as binary string 187 | * @param string $nonce nonce as binary string 188 | * @param string|null $outputFolder folder for storing the files 189 | * @throws \Threema\Core\Exception 190 | * @return ReceiveMessageResult 191 | */ 192 | public final function receiveMessage($threemaId, $messageId, $box, $nonce, $outputFolder = null) { 193 | 194 | if($outputFolder == null || strlen($outputFolder) == 0) { 195 | $outputFolder = '.'; 196 | } 197 | 198 | //fetch the public key 199 | $receiverPublicKey = $this->connection->fetchPublicKey($threemaId); 200 | 201 | if(null === $receiverPublicKey || !$receiverPublicKey->isSuccess()) { 202 | throw new Exception('Invalid threema id'); 203 | } 204 | 205 | $message = $this->cryptTool->decryptMessage( 206 | $box, 207 | $this->privateKey, 208 | hex2bin($receiverPublicKey->getPublicKey()), 209 | $nonce 210 | ); 211 | 212 | if(null === $message || false === is_object($message)) { 213 | throw new Exception('Could not encrypt box'); 214 | } 215 | 216 | $receiveResult = new ReceiveMessageResult($messageId, $message); 217 | 218 | if($message instanceof ImageMessage) { 219 | $result = $this->connection->downloadFile($message->getBlobId()); 220 | if(null === $result || false === $result->isSuccess()) { 221 | throw new Exception('could not download the image with blob id '.$message->getBlobId()); 222 | } 223 | 224 | $image = $this->cryptTool->decryptImage( 225 | $result->getData(), 226 | hex2bin($receiverPublicKey->getPublicKey()), 227 | $this->privateKey, 228 | $message->getNonce() 229 | ); 230 | 231 | if(null === $image) { 232 | throw new Exception('decryption of image failed'); 233 | } 234 | //save file 235 | $filePath = $outputFolder.'/'.$messageId.'.jpg'; 236 | $f = fopen($filePath, 'w+'); 237 | fwrite($f, $image); 238 | fclose($f); 239 | 240 | $receiveResult->addFile('image', $filePath); 241 | } 242 | else if($message instanceof FileMessage) { 243 | $result = $this->connection->downloadFile($message->getBlobId()); 244 | if(null === $result || false === $result->isSuccess()) { 245 | throw new Exception('could not download the file with blob id '.$message->getBlobId()); 246 | } 247 | 248 | $file = $this->cryptTool->decryptFile( 249 | $result->getData(), 250 | hex2bin($message->getEncryptionKey())); 251 | 252 | if(null === $file) { 253 | throw new Exception('file decryption failed'); 254 | } 255 | 256 | //save file 257 | $filePath = $outputFolder.'/'.$messageId.'-'.$message->getFilename(); 258 | file_put_contents($filePath, $file); 259 | 260 | $receiveResult->addFile('file', $filePath); 261 | 262 | if(null !== $message->getThumbnailBlobId() && strlen($message->getThumbnailBlobId()) > 0) { 263 | $result = $this->connection->downloadFile($message->getThumbnailBlobId()); 264 | if(null !== $result && true === $result->isSuccess()) { 265 | $file = $this->cryptTool->decryptFileThumbnail( 266 | $result->getData(), 267 | hex2bin($message->getEncryptionKey())); 268 | 269 | if(null === $file) { 270 | throw new Exception('thumbnail decryption failed'); 271 | } 272 | //save file 273 | $filePath = $outputFolder.'/'.$messageId.'-thumbnail-'.$message->getFilename(); 274 | file_put_contents($filePath, $file); 275 | 276 | $receiveResult->addFile('thumbnail', $filePath); 277 | } 278 | } 279 | } 280 | 281 | return $receiveResult; 282 | } 283 | 284 | /** 285 | * Fetch a public key and check the capability of the threemaId 286 | * 287 | * @param string $threemaId 288 | * @param callable $capabilityCheck 289 | * @return string Public key as binary 290 | * @throws \Threema\Core\Exception 291 | */ 292 | private final function fetchPublicKeyAndCheckCapability($threemaId, \Closure $capabilityCheck = null) { 293 | //fetch the public key 294 | $receiverPublicKey = $this->connection->fetchPublicKey($threemaId); 295 | 296 | if(null === $receiverPublicKey || !$receiverPublicKey->isSuccess()) { 297 | throw new Exception('Invalid threema id'); 298 | } 299 | 300 | if(null !== $capabilityCheck) { 301 | //check capability 302 | $capability = $this->connection->keyCapability($threemaId); 303 | if(null === $capability || false === $capabilityCheck->__invoke($capability)) { 304 | throw new Exception('threema id does not have the capability'); 305 | } 306 | } 307 | 308 | return hex2bin($receiverPublicKey->getPublicKey()); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /source/Threema/MsgApi/Tools/CryptTool.php: -------------------------------------------------------------------------------- 1 | __invoke(); 54 | if(null !== $i) { 55 | self::$instance = $i; 56 | break; 57 | } 58 | } 59 | } 60 | 61 | return self::$instance; 62 | } 63 | 64 | /** 65 | * @param string $type 66 | * @return null|CryptTool null on unknown type 67 | */ 68 | public static function createInstance($type) { 69 | switch($type) { 70 | case self::TYPE_SODIUM: 71 | $instance = new CryptToolSodium(); 72 | if(false === $instance->isSupported()) { 73 | //try to instance old version of sodium wrapper 74 | /** @noinspection PhpDeprecationInspection */ 75 | $instance = new CryptToolSodiumDep(); 76 | } 77 | return $instance->isSupported() ? $instance :null; 78 | case self::TYPE_SALT: 79 | $instance = new CryptToolSalt(); 80 | return $instance->isSupported() ? $instance :null; 81 | default: 82 | return null; 83 | } 84 | } 85 | 86 | const MESSAGE_ID_LEN = 8; 87 | const BLOB_ID_LEN = 16; 88 | const IMAGE_FILE_SIZE_LEN = 4; 89 | const IMAGE_NONCE_LEN = 24; 90 | 91 | const EMAIL_HMAC_KEY = "\x30\xa5\x50\x0f\xed\x97\x01\xfa\x6d\xef\xdb\x61\x08\x41\x90\x0f\xeb\xb8\xe4\x30\x88\x1f\x7a\xd8\x16\x82\x62\x64\xec\x09\xba\xd7"; 92 | const PHONENO_HMAC_KEY = "\x85\xad\xf8\x22\x69\x53\xf3\xd9\x6c\xfd\x5d\x09\xbf\x29\x55\x5e\xb9\x55\xfc\xd8\xaa\x5e\xc4\xf9\xfc\xd8\x69\xe2\x58\x37\x07\x23"; 93 | 94 | protected function __construct() {} 95 | 96 | /** 97 | * Encrypt a text message. 98 | * 99 | * @param string $text the text to be encrypted (max. 3500 bytes) 100 | * @param string $senderPrivateKey the private key of the sending ID 101 | * @param string $recipientPublicKey the public key of the receiving ID 102 | * @param string $nonce the nonce to be used for the encryption (usually 24 random bytes) 103 | * @return string encrypted box 104 | */ 105 | final public function encryptMessageText($text, $senderPrivateKey, $recipientPublicKey, $nonce) { 106 | /* prepend type byte (0x01) to message data */ 107 | $textBytes = "\x01" . $text; 108 | 109 | /* determine random amount of PKCS7 padding */ 110 | $padbytes = mt_rand(1, 255); 111 | 112 | /* append padding */ 113 | $textBytes .= str_repeat(chr($padbytes), $padbytes); 114 | 115 | return $this->makeBox($textBytes, $nonce, $senderPrivateKey, $recipientPublicKey); 116 | } 117 | 118 | /** 119 | * @param UploadFileResult $uploadFileResult the result of the upload 120 | * @param EncryptResult $encryptResult the result of the image encryption 121 | * @param string $senderPrivateKey the private key of the sending ID (as binary) 122 | * @param string $recipientPublicKey the public key of the receiving ID (as binary) 123 | * @param string $nonce the nonce to be used for the encryption (usually 24 random bytes) 124 | * @return string 125 | */ 126 | final public function encryptImageMessage( 127 | UploadFileResult $uploadFileResult, 128 | EncryptResult $encryptResult, 129 | $senderPrivateKey, 130 | $recipientPublicKey, 131 | $nonce) { 132 | $message = "\x02" . hex2bin($uploadFileResult->getBlobId()); 133 | $message .= pack('V', $encryptResult->getSize()); 134 | $message .= $encryptResult->getNonce(); 135 | 136 | /* determine random amount of PKCS7 padding */ 137 | $padbytes = mt_rand(1, 255); 138 | 139 | /* append padding */ 140 | $message .= str_repeat(chr($padbytes), $padbytes); 141 | 142 | return $this->makeBox($message, $nonce, $senderPrivateKey, $recipientPublicKey); 143 | } 144 | 145 | final public function encryptFileMessage(UploadFileResult $uploadFileResult, 146 | EncryptResult $encryptResult, 147 | UploadFileResult $thumbnailUploadFileResult = null, 148 | FileAnalysisResult $fileAnalysisResult, 149 | $senderPrivateKey, 150 | $recipientPublicKey, 151 | $nonce) { 152 | 153 | 154 | $messageContent = array( 155 | 'b' => $uploadFileResult->getBlobId(), 156 | 'k' => bin2hex($encryptResult->getKey()), 157 | 'm' => $fileAnalysisResult->getMimeType(), 158 | 'n' => $fileAnalysisResult->getFileName(), 159 | 's' => $fileAnalysisResult->getSize(), 160 | 'i' => 0 161 | ); 162 | 163 | if($thumbnailUploadFileResult != null && strlen($thumbnailUploadFileResult->getBlobId()) > 0) { 164 | $messageContent['t'] = $thumbnailUploadFileResult->getBlobId(); 165 | } 166 | 167 | $message = "\x17" . json_encode($messageContent); 168 | 169 | /* determine random amount of PKCS7 padding */ 170 | $padbytes = mt_rand(1, 255); 171 | 172 | /* append padding */ 173 | $message .= str_repeat(chr($padbytes), $padbytes); 174 | 175 | return $this->makeBox($message, $nonce, $senderPrivateKey, $recipientPublicKey); 176 | } 177 | 178 | /** 179 | * make a box 180 | * 181 | * @param string $data 182 | * @param string $nonce 183 | * @param string $senderPrivateKey 184 | * @param string $recipientPublicKey 185 | * @return string encrypted box 186 | */ 187 | abstract protected function makeBox($data, $nonce, $senderPrivateKey, $recipientPublicKey); 188 | 189 | /** 190 | * make a secret box 191 | * 192 | * @param $data 193 | * @param $nonce 194 | * @param $key 195 | * @return mixed 196 | */ 197 | abstract protected function makeSecretBox($data, $nonce, $key); 198 | 199 | /** 200 | * decrypt a box 201 | * 202 | * @param string $box as binary 203 | * @param string $recipientPrivateKey as binary 204 | * @param string $senderPublicKey as binary 205 | * @param string $nonce as binary 206 | * @return string 207 | */ 208 | abstract protected function openBox($box, $recipientPrivateKey, $senderPublicKey, $nonce); 209 | 210 | /** 211 | * decrypt a secret box 212 | * 213 | * @param string $box as binary 214 | * @param string $nonce as binary 215 | * @param string $key as binary 216 | * @return string as binary 217 | */ 218 | abstract protected function openSecretBox($box, $nonce, $key); 219 | 220 | /** 221 | * @param string $box 222 | * @param string $recipientPrivateKey 223 | * @param string $senderPublicKey 224 | * @param string $nonce 225 | * @return ThreemaMessage the decrypted message 226 | * @throws BadMessageException 227 | * @throws DecryptionFailedException 228 | * @throws UnsupportedMessageTypeException 229 | */ 230 | final public function decryptMessage($box, $recipientPrivateKey, $senderPublicKey, $nonce) { 231 | 232 | $data = $this->openBox($box, $recipientPrivateKey, $senderPublicKey, $nonce); 233 | 234 | if (null === $data || strlen($data) == 0) { 235 | throw new DecryptionFailedException(); 236 | } 237 | 238 | /* remove padding */ 239 | $padbytes = ord($data[strlen($data)-1]); 240 | $realDataLength = strlen($data) - $padbytes; 241 | if ($realDataLength < 1) { 242 | throw new BadMessageException(); 243 | } 244 | $data = substr($data, 0, $realDataLength); 245 | 246 | /* first byte of data is type */ 247 | $type = ord($data[0]); 248 | 249 | $pos = 1; 250 | $piece = function($length) use(&$pos, $data) { 251 | $d = substr($data, $pos, $length); 252 | $pos += $length; 253 | return $d; 254 | }; 255 | 256 | switch ($type) { 257 | case TextMessage::TYPE_CODE: 258 | /* Text message */ 259 | if ($realDataLength < 2) { 260 | throw new BadMessageException(); 261 | } 262 | 263 | return new TextMessage(substr($data, 1)); 264 | case DeliveryReceipt::TYPE_CODE: 265 | /* Delivery receipt */ 266 | if ($realDataLength < (self::MESSAGE_ID_LEN-2) || (($realDataLength - 2) % self::MESSAGE_ID_LEN) != 0) { 267 | throw new BadMessageException(); 268 | } 269 | 270 | $receiptType = ord($data[1]); 271 | $messageIds = str_split(substr($data, 2), self::MESSAGE_ID_LEN); 272 | 273 | return new DeliveryReceipt($receiptType, $messageIds); 274 | case ImageMessage::TYPE_CODE: 275 | /* Image Message */ 276 | if ($realDataLength != 1 + self::BLOB_ID_LEN + self::IMAGE_FILE_SIZE_LEN + self::IMAGE_NONCE_LEN) { 277 | throw new BadMessageException(); 278 | } 279 | 280 | $blobId = $piece->__invoke(self::BLOB_ID_LEN); 281 | $length = $piece->__invoke(self::IMAGE_FILE_SIZE_LEN); 282 | $nonce = $piece->__invoke(self::IMAGE_NONCE_LEN); 283 | return new ImageMessage(bin2hex($blobId), bin2hex($length), $nonce); 284 | case FileMessage::TYPE_CODE: 285 | /* Image Message */ 286 | $decodeResult = json_decode(substr($data, 1), true); 287 | if(null === $decodeResult || false === $decodeResult) { 288 | throw new BadMessageException(); 289 | } 290 | 291 | $values = AssocArray::byJsonString(substr($data, 1), array('b', 't', 'k', 'm', 'n', 's')); 292 | if(null === $values) { 293 | throw new BadMessageException(); 294 | } 295 | 296 | return new FileMessage( 297 | $values->getValue('b'), 298 | $values->getValue('t'), 299 | $values->getValue('k'), 300 | $values->getValue('m'), 301 | $values->getValue('n'), 302 | $values->getValue('s')); 303 | default: 304 | throw new UnsupportedMessageTypeException(); 305 | } 306 | } 307 | 308 | /** 309 | * Generate a new key pair. 310 | * 311 | * @return KeyPair the new key pair 312 | */ 313 | abstract public function generateKeyPair(); 314 | 315 | /** 316 | * Hashes an email address for identity lookup. 317 | * 318 | * @param string $email the email address 319 | * @return string the email hash (hex) 320 | */ 321 | final public function hashEmail($email) { 322 | $emailClean = strtolower(trim($email)); 323 | return hash_hmac('sha256', $emailClean, self::EMAIL_HMAC_KEY); 324 | } 325 | 326 | /** 327 | * Hashes an phone number address for identity lookup. 328 | * 329 | * @param string $phoneNo the phone number (in E.164 format, no leading +) 330 | * @return string the phone number hash (hex) 331 | */ 332 | final public function hashPhoneNo($phoneNo) { 333 | $phoneNoClean = preg_replace("/[^0-9]/", "", $phoneNo); 334 | return hash_hmac('sha256', $phoneNoClean, self::PHONENO_HMAC_KEY); 335 | } 336 | 337 | abstract protected function createRandom($size); 338 | 339 | /** 340 | * Generate a random nonce. 341 | * 342 | * @return string random nonce 343 | */ 344 | final public function randomNonce() { 345 | return $this->createRandom(\Salt::box_NONCE); 346 | } 347 | 348 | /** 349 | * Generate a symmetric key 350 | * @return mixed 351 | */ 352 | final public function symmetricKey() { 353 | return $this->createRandom(32); 354 | } 355 | 356 | /** 357 | * Derive the public key 358 | * 359 | * @param string $privateKey as binary 360 | * @return string as binary 361 | */ 362 | abstract public function derivePublicKey($privateKey); 363 | 364 | /** 365 | * Check if implementation supported 366 | * @return bool 367 | */ 368 | abstract public function isSupported(); 369 | 370 | /** 371 | * Validate crypt tool 372 | * 373 | * @return bool 374 | * @throws Exception 375 | */ 376 | abstract public function validate(); 377 | 378 | /** 379 | * @param $data 380 | * @return EncryptResult 381 | */ 382 | public final function encryptFile($data) { 383 | $key = $this->symmetricKey(); 384 | $box = $this->makeSecretBox($data, self::FILE_NONCE, $key); 385 | return new EncryptResult($box, $key, self::FILE_NONCE, strlen($box)); 386 | } 387 | 388 | /** 389 | * @param string $data as binary 390 | * @param string $key as binary 391 | * @return null|string 392 | */ 393 | public final function decryptFile($data, $key) { 394 | $result = $this->openSecretBox($data, self::FILE_NONCE, $key); 395 | return false === $result ? null : $result; 396 | } 397 | 398 | /** 399 | * @param string $data 400 | * @param string $key 401 | * @return EncryptResult 402 | */ 403 | public final function encryptFileThumbnail($data, $key) { 404 | $box = $this->makeSecretBox($data, self::FILE_THUMBNAIL_NONCE, $key); 405 | return new EncryptResult($box, $key, self::FILE_THUMBNAIL_NONCE, strlen($box)); 406 | } 407 | 408 | public final function decryptFileThumbnail($data, $key) { 409 | $result = $this->openSecretBox($data, self::FILE_THUMBNAIL_NONCE, $key); 410 | return false === $result ? null : $result; 411 | } 412 | 413 | /** 414 | * @param string $imageData 415 | * @param string $privateKey as binary 416 | * @param string $publicKey as binary 417 | * @return EncryptResult 418 | */ 419 | public final function encryptImage($imageData, $privateKey, $publicKey) { 420 | $nonce = $this->randomNonce(); 421 | 422 | $box = $this->makeBox( 423 | $imageData, 424 | $nonce, 425 | $privateKey, 426 | $publicKey 427 | ); 428 | 429 | return new EncryptResult($box, null, $nonce, strlen($box)); 430 | } 431 | 432 | /** 433 | * @param string $data as binary 434 | * @param string $publicKey as binary 435 | * @param string $privateKey as binary 436 | * @param string $nonce as binary 437 | * @return string 438 | */ 439 | public final function decryptImage($data, $publicKey, $privateKey, $nonce) { 440 | return $this->openBox($data, 441 | $privateKey, 442 | $publicKey, 443 | $nonce); 444 | } 445 | 446 | function __toString() { 447 | return 'CryptTool '.$this->getName(); 448 | } 449 | 450 | /** 451 | * Name of the CryptTool 452 | * @return string 453 | */ 454 | abstract public function getName(); 455 | 456 | /** 457 | * Description of the CryptTool 458 | * @return string 459 | */ 460 | abstract public function getDescription(); 461 | } 462 | -------------------------------------------------------------------------------- /source/Salt/Curve25519/Curve25519.php: -------------------------------------------------------------------------------- 1 | > 26); $out[0] &= static::MASK26; 44 | $out[1] = 0x3fffffe + $a[1] - $b[1] + $c; $c = ($out[1] >> 25); $out[1] &= static::MASK25; 45 | $out[2] = 0x7fffffe + $a[2] - $b[2] + $c; $c = ($out[2] >> 26); $out[2] &= static::MASK26; 46 | $out[3] = 0x3fffffe + $a[3] - $b[3] + $c; $c = ($out[3] >> 25); $out[3] &= static::MASK25; 47 | $out[4] = 0x7fffffe + $a[4] - $b[4] + $c; $c = ($out[4] >> 26); $out[4] &= static::MASK26; 48 | $out[5] = 0x3fffffe + $a[5] - $b[5] + $c; $c = ($out[5] >> 25); $out[5] &= static::MASK25; 49 | $out[6] = 0x7fffffe + $a[6] - $b[6] + $c; $c = ($out[6] >> 26); $out[6] &= static::MASK26; 50 | $out[7] = 0x3fffffe + $a[7] - $b[7] + $c; $c = ($out[7] >> 25); $out[7] &= static::MASK25; 51 | $out[8] = 0x7fffffe + $a[8] - $b[8] + $c; $c = ($out[8] >> 26); $out[8] &= static::MASK26; 52 | $out[9] = 0x3fffffe + $a[9] - $b[9] + $c; $c = ($out[9] >> 25); $out[9] &= static::MASK25; 53 | $out[0] += 19 * $c; 54 | } 55 | 56 | function scalar_product($out, $in, $scalar) { 57 | $a = ($in[0] * $scalar) ; $out[0] = ($a & 0xffffffff) & static::MASK26; $c = ($a >> 26) & 0xffffffff; 58 | $a = ($in[1] * $scalar) + $c; $out[1] = ($a & 0xffffffff) & static::MASK25; $c = ($a >> 25) & 0xffffffff; 59 | $a = ($in[2] * $scalar) + $c; $out[2] = ($a & 0xffffffff) & static::MASK26; $c = ($a >> 26) & 0xffffffff; 60 | $a = ($in[3] * $scalar) + $c; $out[3] = ($a & 0xffffffff) & static::MASK25; $c = ($a >> 25) & 0xffffffff; 61 | $a = ($in[4] * $scalar) + $c; $out[4] = ($a & 0xffffffff) & static::MASK26; $c = ($a >> 26) & 0xffffffff; 62 | $a = ($in[5] * $scalar) + $c; $out[5] = ($a & 0xffffffff) & static::MASK25; $c = ($a >> 25) & 0xffffffff; 63 | $a = ($in[6] * $scalar) + $c; $out[6] = ($a & 0xffffffff) & static::MASK26; $c = ($a >> 26) & 0xffffffff; 64 | $a = ($in[7] * $scalar) + $c; $out[7] = ($a & 0xffffffff) & static::MASK25; $c = ($a >> 25) & 0xffffffff; 65 | $a = ($in[8] * $scalar) + $c; $out[8] = ($a & 0xffffffff) & static::MASK26; $c = ($a >> 26) & 0xffffffff; 66 | $a = ($in[9] * $scalar) + $c; $out[9] = ($a & 0xffffffff) & static::MASK25; $c = ($a >> 25) & 0xffffffff; 67 | $out[0] += $c * 19; 68 | } 69 | 70 | function mul($out, $a, $b) { 71 | $r0 = $b[0]; 72 | $r1 = $b[1]; 73 | $r2 = $b[2]; 74 | $r3 = $b[3]; 75 | $r4 = $b[4]; 76 | $r5 = $b[5]; 77 | $r6 = $b[6]; 78 | $r7 = $b[7]; 79 | $r8 = $b[8]; 80 | $r9 = $b[9]; 81 | 82 | $s0 = $a[0]; 83 | $s1 = $a[1]; 84 | $s2 = $a[2]; 85 | $s3 = $a[3]; 86 | $s4 = $a[4]; 87 | $s5 = $a[5]; 88 | $s6 = $a[6]; 89 | $s7 = $a[7]; 90 | $s8 = $a[8]; 91 | $s9 = $a[9]; 92 | 93 | $m1 = ($r0 * $s1) + ($r1 * $s0); 94 | $m3 = ($r0 * $s3) + ($r1 * $s2) + ($r2 * $s1) + ($r3 * $s0); 95 | $m5 = ($r0 * $s5) + ($r1 * $s4) + ($r2 * $s3) + ($r3 * $s2) + ($r4 * $s1) + ($r5 * $s0); 96 | $m7 = ($r0 * $s7) + ($r1 * $s6) + ($r2 * $s5) + ($r3 * $s4) + ($r4 * $s3) + ($r5 * $s2) + ($r6 * $s1) + ($r7 * $s0); 97 | $m9 = ($r0 * $s9) + ($r1 * $s8) + ($r2 * $s7) + ($r3 * $s6) + ($r4 * $s5) + ($r5 * $s4) + ($r6 * $s3) + ($r7 * $s2) + ($r8 * $s1) + ($r9 * $s0); 98 | 99 | $r1 *= 2; 100 | $r3 *= 2; 101 | $r5 *= 2; 102 | $r7 *= 2; 103 | 104 | $m0 = ($r0 * $s0); 105 | $m2 = ($r0 * $s2) + ($r1 * $s1) + ($r2 * $s0); 106 | $m4 = ($r0 * $s4) + ($r1 * $s3) + ($r2 * $s2) + ($r3 * $s1) + ($r4 * $s0); 107 | $m6 = ($r0 * $s6) + ($r1 * $s5) + ($r2 * $s4) + ($r3 * $s3) + ($r4 * $s2) + ($r5 * $s1) + ($r6 * $s0); 108 | $m8 = ($r0 * $s8) + ($r1 * $s7) + ($r2 * $s6) + ($r3 * $s5) + ($r4 * $s4) + ($r5 * $s3) + ($r6 * $s2) + ($r7 * $s1) + ($r8 * $s0); 109 | 110 | $r1 *= 19; 111 | $r2 *= 19; 112 | $r3 = ($r3 / 2) * 19; 113 | $r4 *= 19; 114 | $r5 = ($r5 / 2) * 19; 115 | $r6 *= 19; 116 | $r7 = ($r7 / 2) * 19; 117 | $r8 *= 19; 118 | $r9 *= 19; 119 | 120 | $m1 += (($r9 * $s2) + ($r8 * $s3) + ($r7 * $s4) + ($r6 * $s5) + ($r5 * $s6) + ($r4 * $s7) + ($r3 * $s8) + ($r2 * $s9)); 121 | $m3 += (($r9 * $s4) + ($r8 * $s5) + ($r7 * $s6) + ($r6 * $s7) + ($r5 * $s8) + ($r4 * $s9)); 122 | $m5 += (($r9 * $s6) + ($r8 * $s7) + ($r7 * $s8) + ($r6 * $s9)); 123 | $m7 += (($r9 * $s8) + ($r8 * $s9)); 124 | 125 | $r3 *= 2; 126 | $r5 *= 2; 127 | $r7 *= 2; 128 | $r9 *= 2; 129 | 130 | $m0 += (($r9 * $s1) + ($r8 * $s2) + ($r7 * $s3) + ($r6 * $s4) + ($r5 * $s5) + ($r4 * $s6) + ($r3 * $s7) + ($r2 * $s8) + ($r1 * $s9)); 131 | $m2 += (($r9 * $s3) + ($r8 * $s4) + ($r7 * $s5) + ($r6 * $s6) + ($r5 * $s7) + ($r4 * $s8) + ($r3 * $s9)); 132 | $m4 += (($r9 * $s5) + ($r8 * $s6) + ($r7 * $s7) + ($r6 * $s8) + ($r5 * $s9)); 133 | $m6 += (($r9 * $s7) + ($r8 * $s8) + ($r7 * $s9)); 134 | $m8 += ($r9 * $s9); 135 | 136 | $r0 = ($m0 & 0xffffffff) & static::MASK26; $c = ($m0 >> 26); 137 | $m1 += $c; $r1 = ($m1 & 0xffffffff) & static::MASK25; $c = ($m1 >> 25); 138 | $m2 += $c; $r2 = ($m2 & 0xffffffff) & static::MASK26; $c = ($m2 >> 26); 139 | $m3 += $c; $r3 = ($m3 & 0xffffffff) & static::MASK25; $c = ($m3 >> 25); 140 | $m4 += $c; $r4 = ($m4 & 0xffffffff) & static::MASK26; $c = ($m4 >> 26); 141 | $m5 += $c; $r5 = ($m5 & 0xffffffff) & static::MASK25; $c = ($m5 >> 25); 142 | $m6 += $c; $r6 = ($m6 & 0xffffffff) & static::MASK26; $c = ($m6 >> 26); 143 | $m7 += $c; $r7 = ($m7 & 0xffffffff) & static::MASK25; $c = ($m7 >> 25); 144 | $m8 += $c; $r8 = ($m8 & 0xffffffff) & static::MASK26; $c = ($m8 >> 26); 145 | $m9 += $c; $r9 = ($m9 & 0xffffffff) & static::MASK25; $p = ($m9 >> 25) & 0xffffffff; 146 | $m0 = $r0 + ($p * 19); $r0 = ($m0 & 0xffffffff) & static::MASK26; $p = ($m0 >> 26) & 0xffffffff; 147 | $r1 += $p; 148 | 149 | $out[0] = $r0; 150 | $out[1] = $r1; 151 | $out[2] = $r2; 152 | $out[3] = $r3; 153 | $out[4] = $r4; 154 | $out[5] = $r5; 155 | $out[6] = $r6; 156 | $out[7] = $r7; 157 | $out[8] = $r8; 158 | $out[9] = $r9; 159 | } 160 | 161 | function square($out, $in) { 162 | $r0 = $in[0]; 163 | $r1 = $in[1]; 164 | $r2 = $in[2]; 165 | $r3 = $in[3]; 166 | $r4 = $in[4]; 167 | $r5 = $in[5]; 168 | $r6 = $in[6]; 169 | $r7 = $in[7]; 170 | $r8 = $in[8]; 171 | $r9 = $in[9]; 172 | 173 | 174 | $m0 = ($r0 * $r0); 175 | $r0 *= 2; 176 | $m1 = ($r0 * $r1); 177 | $m2 = ($r0 * $r2) + ($r1 * $r1 * 2); 178 | $r1 *= 2; 179 | $m3 = ($r0 * $r3) + ($r1 * $r2 ); 180 | $m4 = ($r0 * $r4) + ($r1 * $r3 * 2) + ($r2 * $r2); 181 | $r2 *= 2; 182 | $m5 = ($r0 * $r5) + ($r1 * $r4 ) + ($r2 * $r3); 183 | $m6 = ($r0 * $r6) + ($r1 * $r5 * 2) + ($r2 * $r4) + ($r3 * $r3 * 2); 184 | $r3 *= 2; 185 | $m7 = ($r0 * $r7) + ($r1 * $r6 ) + ($r2 * $r5) + ($r3 * $r4 ); 186 | $m8 = ($r0 * $r8) + ($r1 * $r7 * 2) + ($r2 * $r6) + ($r3 * $r5 * 2) + ($r4 * $r4 ); 187 | $m9 = ($r0 * $r9) + ($r1 * $r8 ) + ($r2 * $r7) + ($r3 * $r6 ) + ($r4 * $r5 * 2); 188 | 189 | $d6 = $r6 * 19; 190 | $d7 = $r7 * 2 * 19; 191 | $d8 = $r8 * 19; 192 | $d9 = $r9 * 2 * 19; 193 | 194 | $m0 += (($d9 * $r1 ) + ($d8 * $r2 ) + ($d7 * $r3 ) + ($d6 * $r4 * 2) + ($r5 * $r5 * 2 * 19)); 195 | $m1 += (($d9 * $r2 / 2) + ($d8 * $r3 ) + ($d7 * $r4 ) + ($d6 * $r5 * 2)); 196 | $m2 += (($d9 * $r3 ) + ($d8 * $r4 * 2) + ($d7 * $r5 * 2) + ($d6 * $r6 )); 197 | $m3 += (($d9 * $r4 ) + ($d8 * $r5 * 2) + ($d7 * $r6 )); 198 | $m4 += (($d9 * $r5 * 2) + ($d8 * $r6 * 2) + ($d7 * $r7 )); 199 | $m5 += (($d9 * $r6 ) + ($d8 * $r7 * 2)); 200 | $m6 += (($d9 * $r7 * 2) + ($d8 * $r8 )); 201 | $m7 += ( $d9 * $r8 ); 202 | $m8 += ( $d9 * $r9 ); 203 | 204 | $r0 = ($m0 & 0xffffffff) & static::MASK26; $c = ($m0 >> 26); 205 | $m1 += $c; $r1 = ($m1 & 0xffffffff) & static::MASK25; $c = ($m1 >> 25); 206 | $m2 += $c; $r2 = ($m2 & 0xffffffff) & static::MASK26; $c = ($m2 >> 26); 207 | $m3 += $c; $r3 = ($m3 & 0xffffffff) & static::MASK25; $c = ($m3 >> 25); 208 | $m4 += $c; $r4 = ($m4 & 0xffffffff) & static::MASK26; $c = ($m4 >> 26); 209 | $m5 += $c; $r5 = ($m5 & 0xffffffff) & static::MASK25; $c = ($m5 >> 25); 210 | $m6 += $c; $r6 = ($m6 & 0xffffffff) & static::MASK26; $c = ($m6 >> 26); 211 | $m7 += $c; $r7 = ($m7 & 0xffffffff) & static::MASK25; $c = ($m7 >> 25); 212 | $m8 += $c; $r8 = ($m8 & 0xffffffff) & static::MASK26; $c = ($m8 >> 26); 213 | $m9 += $c; $r9 = ($m9 & 0xffffffff) & static::MASK25; $p = ($m9 >> 25) & 0xffffffff; 214 | $m0 = $r0 + ($p*19); $r0 = ($m0 & 0xffffffff) & static::MASK26; $p = ($m0 >> 26) & 0xffffffff; 215 | $r1 += $p; 216 | 217 | $out[0] = $r0; 218 | $out[1] = $r1; 219 | $out[2] = $r2; 220 | $out[3] = $r3; 221 | $out[4] = $r4; 222 | $out[5] = $r5; 223 | $out[6] = $r6; 224 | $out[7] = $r7; 225 | $out[8] = $r8; 226 | $out[9] = $r9; 227 | } 228 | 229 | function square_times($out, $in, $count) { 230 | $r0 = $in[0]; 231 | $r1 = $in[1]; 232 | $r2 = $in[2]; 233 | $r3 = $in[3]; 234 | $r4 = $in[4]; 235 | $r5 = $in[5]; 236 | $r6 = $in[6]; 237 | $r7 = $in[7]; 238 | $r8 = $in[8]; 239 | $r9 = $in[9]; 240 | 241 | do { 242 | $m0 = ($r0 * $r0); 243 | $r0 *= 2; 244 | $m1 = ($r0 * $r1); 245 | $m2 = ($r0 * $r2) + ($r1 * $r1 * 2); 246 | $r1 *= 2; 247 | $m3 = ($r0 * $r3) + ($r1 * $r2 ); 248 | $m4 = ($r0 * $r4) + ($r1 * $r3 * 2) + ($r2 * $r2); 249 | $r2 *= 2; 250 | $m5 = ($r0 * $r5) + ($r1 * $r4 ) + ($r2 * $r3); 251 | $m6 = ($r0 * $r6) + ($r1 * $r5 * 2) + ($r2 * $r4) + ($r3 * $r3 * 2); 252 | $r3 *= 2; 253 | $m7 = ($r0 * $r7) + ($r1 * $r6 ) + ($r2 * $r5) + ($r3 * $r4 ); 254 | $m8 = ($r0 * $r8) + ($r1 * $r7 * 2) + ($r2 * $r6) + ($r3 * $r5 * 2) + ($r4 * $r4 ); 255 | $m9 = ($r0 * $r9) + ($r1 * $r8 ) + ($r2 * $r7) + ($r3 * $r6 ) + ($r4 * $r5 * 2); 256 | 257 | $d6 = $r6 * 19; 258 | $d7 = $r7 * 2 * 19; 259 | $d8 = $r8 * 19; 260 | $d9 = $r9 * 2 * 19; 261 | 262 | $m0 += (($d9 * $r1 ) + ($d8 * $r2 ) + ($d7 * $r3 ) + ($d6 * $r4 * 2) + ($r5 * $r5 * 2 * 19)); 263 | $m1 += (($d9 * $r2 / 2) + ($d8 * $r3 ) + ($d7 * $r4 ) + ($d6 * $r5 * 2)); 264 | $m2 += (($d9 * $r3 ) + ($d8 * $r4 * 2) + ($d7 * $r5 * 2) + ($d6 * $r6 )); 265 | $m3 += (($d9 * $r4 ) + ($d8 * $r5 * 2) + ($d7 * $r6 )); 266 | $m4 += (($d9 * $r5 * 2) + ($d8 * $r6 * 2) + ($d7 * $r7 )); 267 | $m5 += (($d9 * $r6 ) + ($d8 * $r7 * 2)); 268 | $m6 += (($d9 * $r7 * 2) + ($d8 * $r8 )); 269 | $m7 += ( $d9 * $r8 ); 270 | $m8 += ( $d9 * $r9 ); 271 | 272 | $r0 = ($m0 & 0xffffffff) & static::MASK26; $c = ($m0 >> 26); 273 | $m1 += $c; $r1 = ($m1 & 0xffffffff) & static::MASK25; $c = ($m1 >> 25); 274 | $m2 += $c; $r2 = ($m2 & 0xffffffff) & static::MASK26; $c = ($m2 >> 26); 275 | $m3 += $c; $r3 = ($m3 & 0xffffffff) & static::MASK25; $c = ($m3 >> 25); 276 | $m4 += $c; $r4 = ($m4 & 0xffffffff) & static::MASK26; $c = ($m4 >> 26); 277 | $m5 += $c; $r5 = ($m5 & 0xffffffff) & static::MASK25; $c = ($m5 >> 25); 278 | $m6 += $c; $r6 = ($m6 & 0xffffffff) & static::MASK26; $c = ($m6 >> 26); 279 | $m7 += $c; $r7 = ($m7 & 0xffffffff) & static::MASK25; $c = ($m7 >> 25); 280 | $m8 += $c; $r8 = ($m8 & 0xffffffff) & static::MASK26; $c = ($m8 >> 26); 281 | $m9 += $c; $r9 = ($m9 & 0xffffffff) & static::MASK25; $p = ($m9 >> 25) & 0xffffffff; 282 | $m0 = $r0 + ($p*19); $r0 = ($m0 & 0xffffffff) & static::MASK26; $p = ($m0 >> 26) & 0xffffffff; 283 | $r1 += $p; 284 | } while (--$count); 285 | 286 | $out[0] = $r0; 287 | $out[1] = $r1; 288 | $out[2] = $r2; 289 | $out[3] = $r3; 290 | $out[4] = $r4; 291 | $out[5] = $r5; 292 | $out[6] = $r6; 293 | $out[7] = $r7; 294 | $out[8] = $r8; 295 | $out[9] = $r9; 296 | } 297 | 298 | function load32($in, $pos) { 299 | return $in[$pos] | ($in[$pos+1]<<8) | ($in[$pos+2]<<16) | ($in[$pos+3]<<24); 300 | } 301 | 302 | function expand($out, $in) { 303 | $x0 = $this->load32($in, 0); 304 | $x1 = $this->load32($in, 4); 305 | $x2 = $this->load32($in, 8); 306 | $x3 = $this->load32($in, 12); 307 | $x4 = $this->load32($in, 16); 308 | $x5 = $this->load32($in, 20); 309 | $x6 = $this->load32($in, 24); 310 | $x7 = $this->load32($in, 28); 311 | 312 | $out[0] = ( $x0 ) & static::MASK26; 313 | $out[1] = ((($x1 << 32) | $x0) >> 26) & static::MASK25; 314 | $out[2] = ((($x2 << 32) | $x1) >> 19) & static::MASK26; 315 | $out[3] = ((($x3 << 32) | $x2) >> 13) & static::MASK25; 316 | $out[4] = (( $x3) >> 6) & static::MASK26; 317 | $out[5] = ( $x4 ) & static::MASK25; 318 | $out[6] = ((($x5 << 32) | $x4) >> 25) & static::MASK26; 319 | $out[7] = ((($x6 << 32) | $x5) >> 19) & static::MASK25; 320 | $out[8] = ((($x7 << 32) | $x6) >> 12) & static::MASK26; 321 | $out[9] = (( $x7) >> 6) & static::MASK25; 322 | } 323 | 324 | function carry_pass($f) { 325 | $f[1] += $f[0] >> 26; $f[0] &= static::MASK26; 326 | $f[2] += $f[1] >> 25; $f[1] &= static::MASK25; 327 | $f[3] += $f[2] >> 26; $f[2] &= static::MASK26; 328 | $f[4] += $f[3] >> 25; $f[3] &= static::MASK25; 329 | $f[5] += $f[4] >> 26; $f[4] &= static::MASK26; 330 | $f[6] += $f[5] >> 25; $f[5] &= static::MASK25; 331 | $f[7] += $f[6] >> 26; $f[6] &= static::MASK26; 332 | $f[8] += $f[7] >> 25; $f[7] &= static::MASK25; 333 | $f[9] += $f[8] >> 26; $f[8] &= static::MASK26; 334 | } 335 | 336 | function carry_pass_full($f) { 337 | $this->carry_pass($f); 338 | $f[0] += 19 * ($f[9] >> 25); 339 | $f[9] &= static::MASK25; 340 | } 341 | 342 | function carry_pass_final($f) { 343 | $this->carry_pass($f); 344 | $f[9] &= static::MASK25; 345 | } 346 | 347 | function store32($out, $pos, $in) { 348 | $out[$pos] |= $in & 0xff; $in >>= 8; 349 | $out[$pos+1] = $in & 0xff; $in >>= 8; 350 | $out[$pos+2] = $in & 0xff; $in >>= 8; 351 | $out[$pos+3] = $in & 0xff; 352 | } 353 | 354 | function contract($out, $in) { 355 | $f = new SplFixedArray(10); 356 | 357 | $this->feCopy($f, $in); 358 | $this->carry_pass_full($f); 359 | $this->carry_pass_full($f); 360 | 361 | $f[0] += 19; 362 | $this->carry_pass_full($f); 363 | 364 | $f[0] += (1 << 26) - 19; 365 | $f[1] += static::MASK25; 366 | $f[2] += static::MASK26; 367 | $f[3] += static::MASK25; 368 | $f[4] += static::MASK26; 369 | $f[5] += static::MASK25; 370 | $f[6] += static::MASK26; 371 | $f[7] += static::MASK25; 372 | $f[8] += static::MASK26; 373 | $f[9] += static::MASK25; 374 | 375 | $this->carry_pass_final($f); 376 | 377 | $f[1] <<= 2; 378 | $f[2] <<= 3; 379 | $f[3] <<= 5; 380 | $f[4] <<= 6; 381 | $f[6] <<= 1; 382 | $f[7] <<= 3; 383 | $f[8] <<= 4; 384 | $f[9] <<= 6; 385 | 386 | $out[0] = 0; 387 | $out[16] = 0; 388 | 389 | $this->store32($out, 0, $f[0]); 390 | $this->store32($out, 3, $f[1]); 391 | $this->store32($out, 6, $f[2]); 392 | $this->store32($out, 9, $f[3]); 393 | $this->store32($out, 12, $f[4]); 394 | $this->store32($out, 16, $f[5]); 395 | $this->store32($out, 19, $f[6]); 396 | $this->store32($out, 22, $f[7]); 397 | $this->store32($out, 25, $f[8]); 398 | $this->store32($out, 28, $f[9]); 399 | } 400 | 401 | function swap_conditional($x, $qpx, $iswap) { 402 | $swap = -$iswap; 403 | 404 | for ($i = 0; $i < 10; $i++) { 405 | $t = $swap & ($x[$i] ^ $qpx[$i]); 406 | $x[$i] ^= $t; 407 | $qpx[$i] ^= $t; 408 | } 409 | } 410 | 411 | function pow_two5mtwo0_two250mtwo0($b) { 412 | $c = new SplFixedArray(16); 413 | $t0 = new SplFixedArray(16); 414 | 415 | $this->square_times($t0, $b, 5); 416 | $this->mul($b, $t0, $b); 417 | $this->square_times($t0, $b, 10); 418 | $this->mul($c, $t0, $b); 419 | $this->square_times($t0, $c, 20); 420 | $this->mul($t0, $t0, $c); 421 | $this->square_times($t0, $t0, 10); 422 | $this->mul($b, $t0, $b); 423 | $this->square_times($t0, $b, 50); 424 | $this->mul($c, $t0, $b); 425 | $this->square_times($t0, $c, 100); 426 | $this->mul($t0, $t0, $c); 427 | $this->square_times($t0, $t0, 50); 428 | $this->mul($b, $t0, $b); 429 | } 430 | 431 | function recip($out, $z) { 432 | $a = new SplFixedArray(16); 433 | $b = new SplFixedArray(16); 434 | $t0 = new SplFixedArray(16); 435 | 436 | $this->square($a, $z); 437 | $this->square_times($t0, $a, 2); 438 | $this->mul($b, $t0, $z); 439 | $this->mul($a, $b, $a); 440 | $this->square($t0, $a); 441 | $this->mul($b, $t0, $b); 442 | $this->pow_two5mtwo0_two250mtwo0($b); 443 | $this->square_times($b, $b, 5); 444 | $this->mul($out, $b, $a); 445 | } 446 | 447 | function scalarmult($secret, $basepoint) { 448 | $nqpqx = new SplFixedArray(10); $nqpqx[0] = 1; 449 | $nqpqz = new SplFixedArray(10); 450 | $nqz = new SplFixedArray(10); $nqz[0] = 1; 451 | $nqx = new SplFixedArray(10); 452 | $q = new SplFixedArray(10); 453 | $qx = new SplFixedArray(10); 454 | $qpqx = new SplFixedArray(10); 455 | $qqx = new SplFixedArray(10); 456 | $zzz = new SplFixedArray(10); 457 | $zmone = new SplFixedArray(10); 458 | $e = new SplFixedArray(32); 459 | $pk = new SplFixedArray(32); 460 | 461 | for ($i = 32; $i--;) $e[$i] = $secret[$i]; 462 | $e[0] &= 0xf8; 463 | $e[31] &= 0x7f; 464 | $e[31] |= 0x40; 465 | 466 | $this->expand($q, $basepoint); 467 | $this->feCopy($nqx, $q); 468 | 469 | /* $bit 255 is always 0, and $bit 254 is always 1, so skip $bit 255 and 470 | start pre-swapped on $bit 254 */ 471 | $lastbit = 1; 472 | 473 | /* we are doing $bits 254..3 in the loop, but are swapping in $bits 253..2 */ 474 | for ($i = 253; $i >= 2; $i--) { 475 | $this->add($qx, $nqx, $nqz); 476 | $this->sub($nqz, $nqx, $nqz); 477 | $this->add($qpqx, $nqpqx, $nqpqz); 478 | $this->sub($nqpqz, $nqpqx, $nqpqz); 479 | $this->mul($nqpqx, $qpqx, $nqz); 480 | $this->mul($nqpqz, $qx, $nqpqz); 481 | $this->add($qqx, $nqpqx, $nqpqz); 482 | $this->sub($nqpqz, $nqpqx, $nqpqz); 483 | $this->square($nqpqz, $nqpqz); 484 | $this->square($nqpqx, $qqx); 485 | $this->mul($nqpqz, $nqpqz, $q); 486 | $this->square($qx, $qx); 487 | $this->square($nqz, $nqz); 488 | $this->mul($nqx, $qx, $nqz); 489 | $this->sub($nqz, $qx, $nqz); 490 | $this->scalar_product($zzz, $nqz, 121665); 491 | $this->add($zzz, $zzz, $qx); 492 | $this->mul($nqz, $nqz, $zzz); 493 | 494 | $bit = ($e[$i/8] >> ($i & 7)) & 1; 495 | $this->swap_conditional($nqx, $nqpqx, $bit ^ $lastbit); 496 | $this->swap_conditional($nqz, $nqpqz, $bit ^ $lastbit); 497 | $lastbit = $bit; 498 | } 499 | 500 | /* the final 3 $bits are always zero, so we only need to double */ 501 | for ($i = 0; $i < 3; $i++) { 502 | $this->add($qx, $nqx, $nqz); 503 | $this->sub($nqz, $nqx, $nqz); 504 | $this->square($qx, $qx); 505 | $this->square($nqz, $nqz); 506 | $this->mul($nqx, $qx, $nqz); 507 | $this->sub($nqz, $qx, $nqz); 508 | $this->scalar_product($zzz, $nqz, 121665); 509 | $this->add($zzz, $zzz, $qx); 510 | $this->mul($nqz, $nqz, $zzz); 511 | } 512 | 513 | $this->recip($zmone, $nqz); 514 | $this->mul($nqz, $nqx, $zmone); 515 | $this->contract($pk, $nqz); 516 | return $pk; 517 | } 518 | 519 | 520 | function scalarbase($secret) { 521 | $basepoint = new SplFixedArray(32); 522 | $basepoint[0] = 9; 523 | return $this->scalarmult($secret, $basepoint); 524 | } 525 | 526 | } 527 | --------------------------------------------------------------------------------