<?php
 
declare(strict_types=1);
 
namespace ParagonIE\Paseto\Keys;
 
 
use ParagonIE\ConstantTime\{
 
    Base64UrlSafe,
 
    Binary
 
};
 
use ParagonIE\Paseto\{
 
    SendingKey,
 
    ProtocolInterface
 
};
 
use ParagonIE\Paseto\Protocol\{
 
    Version1,
 
    Version2
 
};
 
 
/**
 
 * Class AsymmetricSecretKey
 
 * @package ParagonIE\Paseto\Keys
 
 */
 
class AsymmetricSecretKey implements SendingKey
 
{
 
    /** @var string $key */
 
    protected $key;
 
 
    /** @var ProtocolInterface $protocol */
 
    protected $protocol;
 
 
    /**
 
     * AsymmetricSecretKey constructor.
 
     *
 
     * @param string $keyData
 
     * @param ProtocolInterface $protocol
 
     * @throws \Exception
 
     * @throws \TypeError
 
     */
 
    public function __construct(
 
        string $keyData,
 
        ProtocolInterface $protocol = null
 
    ) {
 
        $protocol = $protocol ?? new Version2;
 
 
        if (\hash_equals($protocol::header(), Version2::HEADER)) {
 
            $len = Binary::safeStrlen($keyData);
 
            if ($len === SODIUM_CRYPTO_SIGN_KEYPAIRBYTES) {
 
                $keyData = Binary::safeSubstr($keyData, 0, 64);
 
            } elseif ($len !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
 
                if ($len !== SODIUM_CRYPTO_SIGN_SEEDBYTES) {
 
                    throw new \Exception(
 
                        'Secret keys must be 32 or 64 bytes long; ' . $len . ' given.'
 
                    );
 
                }
 
                $keypair = \sodium_crypto_sign_seed_keypair($keyData);
 
                $keyData = Binary::safeSubstr($keypair, 0, 64);
 
            }
 
        }
 
        $this->key = $keyData;
 
        $this->protocol = $protocol;
 
    }
 
 
    /**
 
     * @param string $keyMaterial
 
     *
 
     * @return self
 
     * @throws \Exception
 
     * @throws \TypeError
 
     */
 
    public static function v1(string $keyMaterial): self
 
    {
 
        return new self($keyMaterial, new Version1());
 
    }
 
 
    /**
 
     * @param string $keyMaterial
 
     *
 
     * @return self
 
     * @throws \Exception
 
     * @throws \TypeError
 
     */
 
    public static function v2(string $keyMaterial): self
 
    {
 
        return new self($keyMaterial, new Version2());
 
    }
 
 
    /**
 
     * @param ProtocolInterface $protocol
 
     * @return self
 
     * @throws \Exception
 
     * @throws \TypeError
 
     */
 
    public static function generate(ProtocolInterface $protocol = null): self
 
    {
 
        $protocol = $protocol ?? new Version2;
 
 
        if (\hash_equals($protocol::header(), Version1::HEADER)) {
 
            $rsa = Version1::getRsa();
 
            /** @var array<string, string> $keypair */
 
            $keypair = $rsa->createKey(2048);
 
            return new self($keypair['privatekey'], $protocol);
 
        }
 
        return new self(
 
            \sodium_crypto_sign_secretkey(
 
                \sodium_crypto_sign_keypair()
 
            ),
 
            $protocol
 
        );
 
    }
 
 
    /**
 
     * @return string
 
     * @throws \TypeError
 
     */
 
    public function encode(): string
 
    {
 
        return Base64UrlSafe::encodeUnpadded($this->key);
 
    }
 
 
    /**
 
     * @param string $encoded
 
     * @param ProtocolInterface|null $version
 
     * @return self
 
     * @throws \Exception
 
     * @throws \TypeError
 
     */
 
    public static function fromEncodedString(string $encoded, ProtocolInterface $version = null): self
 
    {
 
        $decoded = Base64UrlSafe::decode($encoded);
 
        return new self($decoded, $version);
 
    }
 
 
    /**
 
     * @return ProtocolInterface
 
     */
 
    public function getProtocol(): ProtocolInterface
 
    {
 
        return $this->protocol;
 
    }
 
 
    /**
 
     * @return AsymmetricPublicKey
 
     * @throws \Exception
 
     * @throws \TypeError
 
     */
 
    public function getPublicKey(): AsymmetricPublicKey
 
    {
 
        switch ($this->protocol::header()) {
 
            case Version1::HEADER:
 
                return new AsymmetricPublicKey(
 
                    Version1::RsaGetPublicKey($this->key),
 
                    $this->protocol
 
                );
 
            default:
 
                return new AsymmetricPublicKey(
 
                    \sodium_crypto_sign_publickey_from_secretkey($this->key),
 
                    $this->protocol
 
                );
 
        }
 
    }
 
 
    /**
 
     * @return string
 
     */
 
    public function raw()
 
    {
 
        return $this->key;
 
    }
 
 
    /**
 
     * @return array
 
     */
 
    public function __debugInfo()
 
    {
 
        return [];
 
    }
 
}
 
 
 |