Skip to content

Commit

Permalink
Forward compatibility with v5
Browse files Browse the repository at this point in the history
  • Loading branch information
marmichalski committed Feb 21, 2025
1 parent d8549c2 commit 4148a77
Show file tree
Hide file tree
Showing 11 changed files with 496 additions and 103 deletions.
4 changes: 3 additions & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<psalm name="Halite Psalm Configuration"
useDocblockTypes="true"
totallyTyped="true">
>
<projectFiles>
<directory name="./src" />
</projectFiles>
Expand All @@ -11,6 +11,8 @@
<RedundantCondition errorLevel="suppress" />
<RedundantConditionGivenDocblockType errorLevel="suppress" />

<TypeDoesNotContainType errorLevel="info" />
<ArgumentTypeCoercion errorLevel="info" />
<RedundantCast errorLevel="info" />
<NonInvariantDocblockPropertyType errorLevel="info" />
</issueHandlers>
Expand Down
102 changes: 102 additions & 0 deletions src/Asymmetric/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace ParagonIE\Halite\Asymmetric;

use ParagonIE\ConstantTime\Binary;
use ParagonIE\Halite\Alerts\InvalidMessage;
use ParagonIE\Halite\{
Config as BaseConfig,
Halite,
Util
};

/**
* Class Config
*
* This library makes heavy use of return-type declarations,
* which are a PHP 7 only feature. Read more about them here:
*
* @ref http://php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration
*
* @package ParagonIE\Halite\Asymmetric
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* @property string|bool ENCODING
* @property string HASH_DOMAIN_SEPARATION
* @property bool HASH_SCALARMULT
*/
final class Config extends BaseConfig
{
/**
* Get the configuration
*
* @param string $header
* @param string $mode
* @return self
*
* @throws InvalidMessage
*/
public static function getConfig(
string $header,
string $mode = 'encrypt'
): self {
if (Binary::safeStrlen($header) < Halite::VERSION_TAG_LEN) {
throw new InvalidMessage(
'Invalid version tag'
);
}
/*
* We can safely omit the check on the first two bytes since
* this is checked elsewhere. This is just a best-effort to
* obtain the asymmetric configuration
*/
$major = Util::chrToInt($header[2]);
$minor = Util::chrToInt($header[3]);
if ($mode === 'encrypt') {
return new Config(
self::getConfigEncrypt($major, $minor)
);
}
throw new InvalidMessage(
'Invalid configuration mode: '.$mode
);
}

/**
* Get the configuration for encrypt operations
*
* @param int $major
* @param int $minor
* @return array
* @throws InvalidMessage
*/
public static function getConfigEncrypt(int $major, int $minor): array
{
if ($major === 5) {
switch ($minor) {
case 0:
return [
'ENCODING' => Halite::ENCODE_BASE64URLSAFE,
'HASH_DOMAIN_SEPARATION' => 'HaliteVersion5X25519SharedSecret',
'HASH_SCALARMULT' => true,
];
}
}
if ($major === 4 || $major === 3) {
switch ($minor) {
case 0:
return [
'ENCODING' => Halite::ENCODE_BASE64URLSAFE,
'HASH_DOMAIN_SEPARATION' => '',
'HASH_SCALARMULT' => false,
];
}
}
throw new InvalidMessage(
'Invalid version tag'
);
}
}
82 changes: 69 additions & 13 deletions src/Asymmetric/Crypto.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ public static function encryptWithAd(
/** @var HiddenString $ss */
$ss = self::getSharedSecret(
$ourPrivateKey,
$theirPublicKey
$theirPublicKey,
false,
self::getAsymmetricConfig(Halite::HALITE_VERSION, true)
);
$sharedSecretKey = new EncryptionKey($ss);
$ciphertext = SymmetricCrypto::encryptWithAd(
Expand Down Expand Up @@ -186,7 +188,9 @@ public static function decryptWithAd(
/** @var HiddenString $ss */
$ss = self::getSharedSecret(
$ourPrivateKey,
$theirPublicKey
$theirPublicKey,
false,
self::getAsymmetricConfig($ciphertext, $encoding)
);
$sharedSecretKey = new EncryptionKey($ss);
$plaintext = SymmetricCrypto::decryptWithAd(
Expand All @@ -208,6 +212,7 @@ public static function decryptWithAd(
* @param EncryptionSecretKey $privateKey Private key (yours)
* @param EncryptionPublicKey $publicKey Public key (theirs)
* @param bool $get_as_object Get as a Key object?
* @param ?Config $config Asymmetric Config
* @return HiddenString|Key
*
* @throws InvalidKey
Expand All @@ -217,24 +222,38 @@ public static function decryptWithAd(
public static function getSharedSecret(
EncryptionSecretKey $privateKey,
EncryptionPublicKey $publicKey,
bool $get_as_object = false
bool $get_as_object = false,
?Config $config = null
): object {
if ($get_as_object) {
return new EncryptionKey(
new HiddenString(
\sodium_crypto_scalarmult(
$privateKey->getRawKeyMaterial(),
$publicKey->getRawKeyMaterial()
if (!is_null($config)) {
if ($config->HASH_SCALARMULT) {
$hiddenString = new HiddenString(
Util::hkdfBlake2b(
sodium_crypto_scalarmult(
$privateKey->getRawKeyMaterial(),
$publicKey->getRawKeyMaterial()
),
32,
(string) $config->HASH_DOMAIN_SEPARATION
)
)
);
);
if ($get_as_object) {
return new EncryptionKey($hiddenString);
}
return $hiddenString;
}
}
return new HiddenString(
\sodium_crypto_scalarmult(

$hiddenString = new HiddenString(
sodium_crypto_scalarmult(
$privateKey->getRawKeyMaterial(),
$publicKey->getRawKeyMaterial()
)
);
if ($get_as_object) {
return new EncryptionKey($hiddenString);
}
return $hiddenString;
}

/**
Expand Down Expand Up @@ -478,4 +497,41 @@ public static function verifyAndDecrypt(
}
return new HiddenString($message);
}

/**
* Get the Asymmetric configuration expected for this Halite version
*
* @param string $ciphertext
* @param string|bool $encoding
*
* @return Config
*
* @throws InvalidMessage
* @throws InvalidType
*/
public static function getAsymmetricConfig(
string $ciphertext,
string|bool $encoding = Halite::ENCODE_BASE64URLSAFE
): Config {
$decoder = Halite::chooseEncoder($encoding, true);
if (is_callable($decoder)) {
// We were given encoded data:
// @codeCoverageIgnoreStart
try {
/** @var string $ciphertext */
$ciphertext = $decoder($ciphertext);
} catch (\RangeException $ex) {
throw new InvalidMessage(
'Invalid character encoding'
);
}
// @codeCoverageIgnoreEnd
}
$version = Binary::safeSubstr(
$ciphertext,
0,
Halite::VERSION_TAG_LEN
);
return Config::getConfig($version, 'encrypt');
}
}
2 changes: 0 additions & 2 deletions src/Cookie.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@ protected static function getConfig(string $stored): SymmetricConfig
* @throws InvalidMessage
* @throws InvalidType
* @throws \TypeError
*
* @psalm-suppress InvalidArgument PHP version incompatibilities
* @psalm-suppress MixedArgument
*/
public function store(
Expand Down
Loading

0 comments on commit 4148a77

Please sign in to comment.