Exemple #1
0
 /**
  * Seal a message, using only the recipient's public key.
  * 
  * @param string $message
  * @param EncryptionPublicKey $publicKey
  * @return string
  */
 public function sealAsymmetric($plaintext, EncryptionPublicKey $publicKey, array $options = []) : string
 {
     /**
      * 1. Generate a random DH private key.
      */
     list($eph_secret, $eph_public) = KeyFactory::generateEncryptionKeyPair(Common::DRIVER_OPENSSL);
     /**
      * 2. Calculate the shared secret.
      */
     $sharedSecret = $this->getSharedSecret($eph_secret, $publicKey);
     /**
      * 3. Encrypt with symmetric-key crypto.
      */
     // Build our header:
     // [VV][VV]:
     $message = \chr(Common::VERSION_MAJOR);
     $message .= \chr(Common::VERSION_MINOR);
     // [DD]: Make sure leftmost bit is 1 because we're sealing
     $message .= \chr(0x80 | self::DRIVER_ID & 0x7f);
     // [CC]:
     $message .= \chr(Common::VERSION_MAJOR ^ Common::VERSION_MINOR ^ (0x80 | self::DRIVER_ID & 0x7f));
     $message .= $eph_public->getRawBytes();
     // Salt:
     $salt = \random_bytes(Common::safeStrlen(\hash($this->config['hash'], '', true)));
     // Split keys:
     list($encKey, $authKey) = $this->splitSymmetricKey($sharedSecret, $salt);
     $message .= $salt;
     // HKDF salt
     // Nonce:
     $nonce = \random_bytes(\openssl_cipher_iv_length($this->config['cipher']));
     $message .= $nonce;
     // Nonce for the stream cipher
     // Encrypt:
     $message .= \openssl_encrypt($plaintext, $this->config['cipher'] . '-ctr', $encKey->getRawBytes(), OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $nonce);
     unset($encKey);
     /**
      * 4. Authenticate:
      */
     $message .= \hash_hmac($this->config['hash'], $message, $authKey->getRawBytes(), true);
     unset($authKey);
     // Return:
     return $message;
 }
 /**
  * Create a JSON Web Token string
  *
  * @param string            $keyName          name of the key to use to sign the token
  * @param array|Traversable $payload          traversable set of claims, claim => value
  * @param int               $expirationOffset seconds from now that token will expire. If not specified,
  *                                             an "exp" claim will not be added or updated
  *
  * @return string a token string returned from JsonWebToken::create()
  *
  * @throws \DomainException;
  * @throws \InvalidArgumentException;
  * @throws \UnexpectedValueException;
  */
 public static function build($keyName, $payload, $expirationOffset = 0)
 {
     $token = new JsonWebToken(KeyFactory::build($keyName));
     return $token->create($payload, $expirationOffset);
 }
Exemple #3
0
 /**
  * Seal the contents of a file.
  *
  * @param ReadOnlyFile $input
  * @param MutableFile $output
  * @param EncryptionPublicKey $publicKey
  * @return int
  */
 protected static function sealData(ReadOnlyFile $input, MutableFile $output, EncryptionPublicKey $publicKey) : int
 {
     // Generate a new keypair for this encryption
     $ephemeralKeyPair = KeyFactory::generateEncryptionKeyPair();
     $ephSecret = $ephemeralKeyPair->getSecretKey();
     $ephPublic = $ephemeralKeyPair->getPublicKey();
     unset($ephemeralKeyPair);
     // Calculate the shared secret key
     $sharedSecretKey = AsymmetricCrypto::getSharedSecret($ephSecret, $publicKey, true);
     // Destroy the secret key after we have the shared secret
     unset($ephSecret);
     // Load the configuration
     $config = self::getConfig(Halite::HALITE_VERSION_FILE, 'seal');
     // Generate a nonce as per crypto_box_seal
     $nonce = \Sodium\crypto_generichash($ephPublic->getRawKeyMaterial() . $publicKey->getRawKeyMaterial(), '', \Sodium\CRYPTO_STREAM_NONCEBYTES);
     // Generate a random HKDF salt
     $hkdfSalt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN);
     // Split the keys
     list($encKey, $authKey) = self::splitKeys($sharedSecretKey, $hkdfSalt, $config);
     // We no longer need the original key after we split it
     unset($key);
     // Write the header:
     $output->writeBytes(Halite::HALITE_VERSION_FILE, Halite::VERSION_TAG_LEN);
     $output->writeBytes($ephPublic->getRawKeyMaterial(), \Sodium\CRYPTO_BOX_PUBLICKEYBYTES);
     $output->writeBytes($hkdfSalt, $config->HKDF_SALT_LEN);
     // VERSION 2+
     $mac = \Sodium\crypto_generichash_init($authKey);
     // We no longer need $authKey after we set up the hash context
     \Sodium\memzero($authKey);
     \Sodium\crypto_generichash_update($mac, Halite::HALITE_VERSION_FILE);
     \Sodium\crypto_generichash_update($mac, $ephPublic->getRawKeyMaterial());
     \Sodium\crypto_generichash_update($mac, $hkdfSalt);
     unset($ephPublic);
     \Sodium\memzero($hkdfSalt);
     return self::streamEncrypt($input, $output, new EncryptionKey(new HiddenString($encKey)), $nonce, $mac, $config);
 }
Exemple #4
0
 /**
  * Execute the start command, which will start a new hangar session.
  *
  * @param array $args
  * @return bool
  * @throws \Error
  */
 public function fire(array $args = []) : bool
 {
     $file = $this->selectFile($args[0] ?? '');
     if (!isset($this->config['salt']) && \count($args) < 2) {
         throw new \Error('No salt configured or passed');
     }
     if (\count($args) > 2) {
         switch (\strtolower($args[2])) {
             case 'fast':
             case 'i':
             case 'interactive':
             case 'weak':
                 $level = KeyFactory::INTERACTIVE;
                 break;
             case 'm':
             case 'signing':
             case 'moderate':
                 $level = KeyFactory::MODERATE;
                 break;
             default:
                 $level = KeyFactory::SENSITIVE;
                 break;
         }
     } elseif (isset($this->config['keytype'])) {
         switch ($this->config['keytype']) {
             case 'fast':
             case 'i':
             case 'interactive':
             case 'weak':
                 $level = KeyFactory::INTERACTIVE;
                 break;
             case 'm':
             case 'signing':
             case 'moderate':
                 $level = KeyFactory::MODERATE;
                 break;
             default:
                 $level = KeyFactory::SENSITIVE;
                 break;
         }
     } else {
         $level = KeyFactory::SENSITIVE;
     }
     $salt = \Sodium\hex2bin($args[1] ?? $this->config['salt']);
     echo 'Generating a signature for: ', $file, "\n";
     $password = $this->silentPrompt('Enter password: '******'false' in version 2.0.0 (with Halite 3)
     $sign_kp = KeyFactory::deriveSignatureKeyPair($password, $salt, false, $level);
     if (!$sign_kp instanceof SignatureKeyPair) {
         throw new \Error('Error during key derivation');
     }
     $signature = File::sign($file, $sign_kp->getSecretKey());
     if (isset($this->history)) {
         $this->config['build_history']['signed'] = true;
     }
     \file_put_contents($file . '.sig', $signature);
     echo 'File signed: ' . $file . '.sig', "\n";
     echo 'Public key: ' . \Sodium\bin2hex($sign_kp->getPublicKey()->getRawKeyMaterial()), "\n";
     return true;
 }
Exemple #5
0
 /**
  * Common signing process. User selects key, provides password.
  *
  * @param array $manifest
  * @return SignatureSecretKey
  * @throws \Exception
  */
 protected function signPreamble(array $manifest) : SignatureSecretKey
 {
     $HTAB = \str_repeat(' ', \intdiv(self::TAB_SIZE, 2));
     $supplier_name = $manifest['supplier'];
     // Sanity checks:
     if (!\array_key_exists('suppliers', $this->config)) {
         echo 'You are not authenticated for any suppliers.', "\n";
         exit(255);
     }
     if (!\array_key_exists($supplier_name, $this->config['suppliers'])) {
         echo 'Check the supplier in the JSON file (', $supplier_name, ') for correctness.', 'Otherwise, you might need to log in.', "\n";
         exit(255);
     }
     $supplier = $this->config['suppliers'][$supplier_name];
     $numKeys = 0;
     if ($this->signWithMasterKeys) {
         $good_keys = [];
         // This should really not be used:
         $numKeys = \count($supplier['signing_keys']);
         foreach ($supplier['signing_keys'] as $k) {
             if (!empty($k['salt'])) {
                 $good_keys[] = $k;
                 ++$numKeys;
             }
         }
     } else {
         // This should be used instead:
         $good_keys = [];
         foreach ($supplier['signing_keys'] as $k) {
             if ($k['type'] === 'signing' && !empty($k['salt'])) {
                 $good_keys[] = $k;
                 ++$numKeys;
             }
         }
     }
     if ($numKeys > 1) {
         echo 'You have more than one signing key available.', "\n";
         $n = 1;
         $size = (int) \floor(\log($numKeys, 10));
         $key_associations = $HTAB . "ID\t Public Key " . \str_repeat(' ', 33) . "\t Type\n";
         foreach ($supplier['signing_keys'] as $sign_key) {
             if (!$this->signWithMasterKeys && $sign_key['type'] === 'master') {
                 continue;
             }
             $_n = \str_pad($n, $size, ' ', STR_PAD_LEFT);
             // Short format:
             $pk = Base64UrlSafe::encode(\Sodium\hex2bin($sign_key['public_key']));
             $key_associations .= $HTAB . $_n . $HTAB . $pk . $HTAB . $sign_key['type'] . "\n";
             ++$n;
         }
         // Let's ascertain the user's key selection
         do {
             echo $key_associations;
             $choice = (int) $this->prompt('Enter the ID for the key you wish to use: ');
             if ($choice < 1 || $choice > $numKeys) {
                 $choice = null;
             }
         } while (empty($choice));
         $supplierKey = $good_keys[$choice - 1];
         echo "\n";
     } else {
         $supplierKey = $good_keys[0];
     }
     // The above !empty($k['salt']) check should have rendered this check redundant:
     if (empty($supplierKey['salt'])) {
         echo 'Salt not found for this key. It is not possible to reproduce it.', "\n";
         exit(255);
     }
     // Short format:
     $pk = Base64UrlSafe::encode(\Sodium\hex2bin($supplierKey['public_key']));
     // Color coded: Master keys are red, since they take longer.
     // We don't support signing packages with a master key, but
     // this decision could be undone in the future.
     $c = $supplierKey['type'] === 'master' ? $this->c['red'] : $this->c['yellow'];
     echo 'Selected ', $supplierKey['type'], ' key: ', $c, $pk, $this->c[''], "\n";
     $password = $this->silentPrompt('Enter Password for Signing Key:');
     // Derive and split the SignatureKeyPair from your password and salt
     $salt = \Sodium\hex2bin($supplierKey['salt']);
     switch ($supplierKey['type']) {
         case 'signing':
             $type = KeyFactory::MODERATE;
             echo 'Verifying (this may take a second or two)...';
             break;
         case 'master':
             $type = KeyFactory::SENSITIVE;
             echo 'Verifying (this may take a few seconds)...';
             break;
         default:
             $type = KeyFactory::INTERACTIVE;
             echo 'Verifying...';
     }
     $keyPair = KeyFactory::deriveSignatureKeyPair($password, $salt, false, $type);
     $sign_secret = $keyPair->getSecretKey();
     $sign_public = $keyPair->getPublicKey();
     echo ' Done.', "\n";
     // We don't need this anymore.
     \Sodium\memzero($password);
     // Check that the public key we derived from the password matches the one on file
     $pubKey = \Sodium\bin2hex($sign_public->getRawKeyMaterial());
     if (!\hash_equals($supplierKey['public_key'], $pubKey)) {
         // Zero the memory ASAP
         unset($sign_secret);
         unset($sign_public);
         echo 'Invalid password for selected key', "\n";
         exit(255);
     }
     // Zero the memory ASAP
     unset($sign_public);
     return $sign_secret;
 }