Skip to content

Commit

Permalink
Sub-packages for algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
Spomky committed May 29, 2018
0 parents commit ec635dc
Show file tree
Hide file tree
Showing 12 changed files with 941 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Contributing

This repository is a sub repository of [the JWT Framework](https://github.com/web-token/jwt-framework) project and is READ ONLY.
Please do not submit any Pull Requests here. It will be automatically closed.
3 changes: 3 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Please do not submit any Pull Requests here. It will be automatically closed.

You should submit it here: https://github.com/web-token/jwt-framework/pulls
390 changes: 390 additions & 0 deletions ECDHES.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@
<?php

declare(strict_types=1);

/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2018 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/

namespace Jose\Component\Encryption\Algorithm\KeyEncryption;

use Base64Url\Base64Url;
use Jose\Component\Core\JWK;
use Jose\Component\Core\Util\Ecc\Curve;
use Jose\Component\Core\Util\Ecc\NistCurve;
use Jose\Component\Core\Util\Ecc\PrivateKey;
use Jose\Component\Encryption\Util\ConcatKDF;
use Jose\Component\Encryption\Util\Ecc\EcDH;

final class ECDHES implements KeyAgreement
{
/**
* {@inheritdoc}
*/
public function allowedKeyTypes(): array
{
return ['EC', 'OKP'];
}

/**
* {@inheritdoc}
*/
public function getAgreementKey(int $encryption_key_length, string $algorithm, JWK $recipient_key, array $complete_header = [], array &$additional_header_values = []): string
{
if ($recipient_key->has('d')) {
list($public_key, $private_key) = $this->getKeysFromPrivateKeyAndHeader($recipient_key, $complete_header);
} else {
list($public_key, $private_key) = $this->getKeysFromPublicKey($recipient_key, $additional_header_values);
}

$agreed_key = $this->calculateAgreementKey($private_key, $public_key);

$apu = array_key_exists('apu', $complete_header) ? $complete_header['apu'] : '';
$apv = array_key_exists('apv', $complete_header) ? $complete_header['apv'] : '';

return ConcatKDF::generate($agreed_key, $algorithm, $encryption_key_length, $apu, $apv);
}

/**
* @param JWK $recipient_key
* @param array $additional_header_values
*
* @return JWK[]
*/
private function getKeysFromPublicKey(JWK $recipient_key, array &$additional_header_values): array
{
$this->checkKey($recipient_key, false);
$public_key = $recipient_key;
switch ($public_key->get('crv')) {
case 'P-256':
case 'P-384':
case 'P-521':
$private_key = $this->createECKey($public_key->get('crv'));

break;
case 'X25519':
$private_key = $this->createOKPKey('X25519');

break;
default:
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $public_key->get('crv')));
}
$epk = $private_key->toPublic()->all();
$additional_header_values['epk'] = $epk;

return [$public_key, $private_key];
}

/**
* @param JWK $recipient_key
* @param array $complete_header
*
* @return JWK[]
*/
private function getKeysFromPrivateKeyAndHeader(JWK $recipient_key, array $complete_header): array
{
$this->checkKey($recipient_key, true);
$private_key = $recipient_key;
$public_key = $this->getPublicKey($complete_header);
if ($private_key->get('crv') !== $public_key->get('crv')) {
throw new \InvalidArgumentException('Curves are different');
}

return [$public_key, $private_key];
}

/**
* @param JWK $private_key
* @param JWK $public_key
*
* @throws \InvalidArgumentException
*
* @return string
*/
public function calculateAgreementKey(JWK $private_key, JWK $public_key): string
{
switch ($public_key->get('crv')) {
case 'P-256':
case 'P-384':
case 'P-521':
$curve = $this->getCurve($public_key->get('crv'));

$rec_x = $this->convertBase64ToGmp($public_key->get('x'));
$rec_y = $this->convertBase64ToGmp($public_key->get('y'));
$sen_d = $this->convertBase64ToGmp($private_key->get('d'));

$priv_key = PrivateKey::create($sen_d);
$pub_key = $curve->getPublicKeyFrom($rec_x, $rec_y);

return $this->convertDecToBin(EcDH::computeSharedKey($curve, $pub_key, $priv_key));
case 'X25519':
$sKey = Base64Url::decode($private_key->get('d'));
$recipientPublickey = Base64Url::decode($public_key->get('x'));

return sodium_crypto_scalarmult($sKey, $recipientPublickey);
default:
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $public_key->get('crv')));
}
}

/**
* {@inheritdoc}
*/
public function name(): string
{
return 'ECDH-ES';
}

/**
* {@inheritdoc}
*/
public function getKeyManagementMode(): string
{
return self::MODE_AGREEMENT;
}

/**
* @param array $complete_header
*
* @return JWK
*/
private function getPublicKey(array $complete_header)
{
if (!array_key_exists('epk', $complete_header)) {
throw new \InvalidArgumentException('The header parameter "epk" is missing');
}
if (!is_array($complete_header['epk'])) {
throw new \InvalidArgumentException('The header parameter "epk" is not an array of parameter');
}

$public_key = JWK::create($complete_header['epk']);
$this->checkKey($public_key, false);

return $public_key;
}

/**
* @param JWK $key
* @param bool $is_private
*/
private function checkKey(JWK $key, $is_private)
{
if (!in_array($key->get('kty'), $this->allowedKeyTypes())) {
throw new \InvalidArgumentException('Wrong key type.');
}
foreach (['x', 'crv'] as $k) {
if (!$key->has($k)) {
throw new \InvalidArgumentException(sprintf('The key parameter "%s" is missing.', $k));
}
}

switch ($key->get('crv')) {
case 'P-256':
case 'P-384':
case 'P-521':
if (!$key->has('y')) {
throw new \InvalidArgumentException('The key parameter "y" is missing.');
}

break;
case 'X25519':
break;
default:
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $key->get('crv')));
}
if (true === $is_private) {
if (!$key->has('d')) {
throw new \InvalidArgumentException('The key parameter "d" is missing.');
}
}
}

/**
* @param string $crv
*
* @throws \InvalidArgumentException
*
* @return Curve
*/
private function getCurve(string $crv): Curve
{
switch ($crv) {
case 'P-256':
return NistCurve::curve256();
case 'P-384':
return NistCurve::curve384();
case 'P-521':
return NistCurve::curve521();
default:
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported', $crv));
}
}

/**
* @param string $value
*
* @return \GMP
*/
private function convertBase64ToGmp(string $value): \GMP
{
$value = unpack('H*', Base64Url::decode($value));

return gmp_init($value[1], 16);
}

/**
* @param \GMP $dec
*
* @return string
*/
private function convertDecToBin(\GMP $dec): string
{
if (gmp_cmp($dec, 0) < 0) {
throw new \InvalidArgumentException('Unable to convert negative integer to string');
}

$hex = gmp_strval($dec, 16);

if (0 !== mb_strlen($hex, '8bit') % 2) {
$hex = '0'.$hex;
}

return hex2bin($hex);
}

/**
* @param string $crv The curve
*
* @return JWK
*/
public function createECKey(string $crv): JWK
{
try {
$jwk = self::createECKeyUsingOpenSSL($crv);
} catch (\Exception $e) {
$jwk = self::createECKeyUsingPurePhp($crv);
}

return JWK::create($jwk);
}

/**
* @param string $curve The curve
*
* @return JWK
*/
public static function createOKPKey(string $curve): JWK
{
switch ($curve) {
case 'X25519':
$keyPair = sodium_crypto_box_keypair();
$d = sodium_crypto_box_secretkey($keyPair);
$x = sodium_crypto_box_publickey($keyPair);

break;
case 'Ed25519':
$keyPair = sodium_crypto_sign_keypair();
$d = sodium_crypto_sign_secretkey($keyPair);
$x = sodium_crypto_sign_publickey($keyPair);

break;
default:
throw new \InvalidArgumentException(sprintf('Unsupported "%s" curve', $curve));
}

return JWK::create([
'kty' => 'OKP',
'crv' => $curve,
'x' => Base64Url::encode($x),
'd' => Base64Url::encode($d),
]);
}

/**
* @param string $curve
*
* @return array
*/
private static function createECKeyUsingPurePhp(string $curve): array
{
switch ($curve) {
case 'P-256':
$nistCurve = NistCurve::curve256();

break;
case 'P-384':
$nistCurve = NistCurve::curve384();

break;
case 'P-521':
$nistCurve = NistCurve::curve521();

break;
default:
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
}

$privateKey = $nistCurve->createPrivateKey();
$publicKey = $nistCurve->createPublicKey($privateKey);

return [
'kty' => 'EC',
'crv' => $curve,
'd' => Base64Url::encode(gmp_export($privateKey->getSecret())),
'x' => Base64Url::encode(gmp_export($publicKey->getPoint()->getX())),
'y' => Base64Url::encode(gmp_export($publicKey->getPoint()->getY())),
];
}

/**
* @param string $curve
*
* @return array
*/
private static function createECKeyUsingOpenSSL(string $curve): array
{
$key = openssl_pkey_new([
'curve_name' => self::getOpensslCurveName($curve),
'private_key_type' => OPENSSL_KEYTYPE_EC,
]);
$res = openssl_pkey_export($key, $out);
if (false === $res) {
throw new \RuntimeException('Unable to create the key');
}
$res = openssl_pkey_get_private($out);

$details = openssl_pkey_get_details($res);

return [
'kty' => 'EC',
'crv' => $curve,
'd' => Base64Url::encode($details['ec']['d']),
'x' => Base64Url::encode($details['ec']['x']),
'y' => Base64Url::encode($details['ec']['y']),
];
}

/**
* @param string $curve
*
* @return string
*/
private static function getOpensslCurveName(string $curve): string
{
switch ($curve) {
case 'P-256':
return 'prime256v1';
case 'P-384':
return 'secp384r1';
case 'P-521':
return 'secp521r1';
default:
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
}
}
}
Loading

0 comments on commit ec635dc

Please sign in to comment.