| 
<?phpdeclare(strict_types=1);
 namespace ParagonIE\Paserk\Operations;
 
 use ParagonIE\HiddenString\HiddenString;
 use ParagonIE\Paserk\Operations\PBKW\{
 PBKWv3,
 PBKWv4
 };
 use ParagonIE\Paserk\PaserkException;
 use ParagonIE\Paseto\Keys\{
 AsymmetricSecretKey,
 SymmetricKey
 };
 use ParagonIE\Paseto\ProtocolInterface;
 use TypeError;
 use function
 array_pop,
 explode,
 hash_equals,
 implode;
 
 /**
 * Class PBKW
 * @package ParagonIE\Paserk\Operations
 */
 class PBKW
 {
 const DOMAIN_SEPARATION_ENCRYPT = "\xff";
 const DOMAIN_SEPARATION_AUTH = "\xfe";
 
 protected PBKWInterface $wrapper;
 
 /**
 * PBKW constructor.
 * @param PBKWInterface $wrapper
 */
 public function __construct(PBKWInterface $wrapper)
 {
 $this->wrapper = $wrapper;
 }
 
 /**
 * @param ProtocolInterface $version
 * @return self
 * @throws PaserkException
 */
 public static function forVersion(ProtocolInterface $version): self
 {
 switch ($version::header()) {
 case 'v3':
 return new PBKW(new PBKWv3());
 case 'v4':
 return new PBKW(new PBKWv4());
 }
 throw new PaserkException('Unknown version');
 }
 
 /**
 * @param SymmetricKey $key
 * @param HiddenString $password
 * @param array $options
 * @return string
 */
 public function localPwWrap(
 SymmetricKey $key,
 HiddenString $password,
 array $options = []
 ): string {
 return $this->wrapper::localHeader() .
 $this->wrapper->wrapWithPassword($key, $password, $options);
 }
 
 /**
 * @param string $paserk
 * @param HiddenString $password
 * @return SymmetricKey
 * @throws PaserkException
 */
 public function localPwUnwrap(
 string $paserk,
 HiddenString $password
 ): SymmetricKey {
 $pieces = explode('.', $paserk);
 if (count($pieces) !== 3) {
 throw new PaserkException('Invalid wrapped key');
 }
 $payload = array_pop($pieces);
 
 $header = implode('.', $pieces) . '.';
 // Step 1: Algorithm lucidity
 $expect = $this->wrapper::localHeader();
 if (!hash_equals($expect, $header)) {
 throw new PaserkException('Invalid wrapped key');
 }
 
 $unwrapped = $this->wrapper->unwrapWithPassword(
 $header,
 $payload,
 $password
 );
 if (!($unwrapped instanceof SymmetricKey)) {
 throw new TypeError();
 }
 return $unwrapped;
 }
 
 /**
 * @param AsymmetricSecretKey $sk
 * @param HiddenString $password
 * @param array $options
 * @return string
 */
 public function secretPwWrap(
 AsymmetricSecretKey $sk,
 HiddenString $password,
 array $options
 ): string {
 return $this->wrapper::secretHeader() .
 $this->wrapper->wrapWithPassword($sk, $password, $options);
 }
 
 /**
 * @param string $paserk
 * @param HiddenString $password
 * @return AsymmetricSecretKey
 * @throws PaserkException
 */
 public function secretPwUnwrap(
 string $paserk,
 HiddenString $password
 ): AsymmetricSecretKey {
 $pieces = explode('.', $paserk);
 if (count($pieces) !== 3) {
 throw new PaserkException('Invalid wrapped key');
 }
 $payload = array_pop($pieces);
 
 $header = implode('.', $pieces) . '.';
 // Step 1: Algorithm lucidity
 $expect = $this->wrapper::secretHeader();
 if (!hash_equals($expect, $header)) {
 throw new PaserkException('Invalid wrapped key');
 }
 
 $unwrapped = $this->wrapper->unwrapWithPassword(
 $header,
 $payload,
 $password
 );
 if (!($unwrapped instanceof AsymmetricSecretKey)) {
 throw new TypeError();
 }
 return $unwrapped;
 }
 }
 
 |