/** * Creates an RSA key. * * The supported formats are: * * - `php` - JSON web key formatted as a PHP associative array * - `json` - JSON web key * - `jwe` - Encrypted JSON web key * - `pem` - the public or private key encoded in PEM (base64 encoded DER) format * * @param string|array $data the key data * @param string $format the format * @param string $password the password, if the key is password protected * @param string $alg the algorithm, if the key is password protected */ public function __construct($data, $format, $password = null, $alg = 'PBES2-HS256+A128KW') { switch ($format) { case 'php': case 'json': case 'jwe': parent::__construct($data, $format, $password, $alg); break; case 'pem': $offset = 0; $jwk = array(); if (preg_match(Key::PEM_PUBLIC, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $algorithm); // OBJECT IDENTIFIER - AlgorithmIdentifier $algorithm = ASN1::decodeOID($algorithm); if ($algorithm != self::OID) { throw new KeyException('Not RSA key'); } $offset += ASN1::readDER($der, $offset, $value); // NULL - parameters $offset += ASN1::readDER($der, $offset, $value, true); // BIT STRING $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $n); // INTEGER [n] $offset += ASN1::readDER($der, $offset, $e); // INTEGER [e] $jwk['kty'] = self::KTY; $jwk['n'] = Util::base64url_encode($n); $jwk['e'] = Util::base64url_encode($e); } elseif (preg_match(self::PEM_PRIVATE, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset += ASN1::readDER($der, $offset, $data); // SEQUENCE $offset += ASN1::readDER($der, $offset, $version); // INTEGER if (ord($version) != 0) { throw new KeyException('Unsupported RSA private key version'); } $offset += ASN1::readDER($der, $offset, $n); // INTEGER [n] $offset += ASN1::readDER($der, $offset, $e); // INTEGER [e] $offset += ASN1::readDER($der, $offset, $d); // INTEGER [d] $offset += ASN1::readDER($der, $offset, $p); // INTEGER [p] $offset += ASN1::readDER($der, $offset, $q); // INTEGER [q] $offset += ASN1::readDER($der, $offset, $dp); // INTEGER [dp] $offset += ASN1::readDER($der, $offset, $dq); // INTEGER [dq] $offset += ASN1::readDER($der, $offset, $qi); // INTEGER [qi] if (strlen($der) > $offset) { ASN1::readDER($der, $offset, $oth); } // INTEGER [other] $jwk['kty'] = self::KTY; $jwk['n'] = Util::base64url_encode($n); $jwk['e'] = Util::base64url_encode($e); $jwk['d'] = Util::base64url_encode($d); $jwk['p'] = Util::base64url_encode($p); $jwk['q'] = Util::base64url_encode($q); $jwk['dp'] = Util::base64url_encode($dp); $jwk['dq'] = Util::base64url_encode($dq); $jwk['qi'] = Util::base64url_encode($qi); } parent::__construct($jwk); break; default: throw new KeyException('Incorrect format'); } if (!isset($this->data['kty'])) { $this->data['kty'] = self::KTY; } }
public function sign($data, $keys, $kid = null) { $key = $this->getSigningKey($keys, $kid); if ($key == null) { throw new KeyException('Key not found or is invalid'); } $binary = ''; if (!openssl_sign($data, $binary, $key->toPEM(), 'SHA' . $this->size)) { $messages = array(); while ($message = openssl_error_string()) { $messages[] = $message; } throw new CryptException('Cannot calculate signature: ' . implode("\n", $messages)); } if ($key->getKeyType() == \SimpleJWT\Keys\ECKey::KTY) { // OpenSSL returns ECDSA signatures as an ASN.1 DER SEQUENCE $offset = 0; $offset += ASN1::readDER($binary, $offset, $value); // SEQUENCE $offset += ASN1::readDER($binary, $offset, $r); // INTEGER $offset += ASN1::readDER($binary, $offset, $s); // INTEGER // DER integers are big-endian in two's complement form. We need to // convert these to unsigned integers. But the r and s values are always // positive, so it is just a matter of stripping out the leading null $r = ltrim($r, ""); $s = ltrim($s, ""); // Now pad out r and s so that they are $key->getSize() bits long $r = str_pad($r, $key->getSize() / 8, "", STR_PAD_LEFT); $s = str_pad($s, $key->getSize() / 8, "", STR_PAD_LEFT); $binary = $r . $s; } return Util::base64url_encode($binary); }
/** * Detects the format of key data and returns a key object. * * The supported formats are: * * - `php` - JSON web key formatted as a PHP associative array * - `json` - JSON web key * - `pem` - the public or private key encoded in PEM (base64 encoded DER) format * - `jwe` - Encrypted JSON web key * * @param string $data the key data * @param string $format the format * @param string $password the password, if the key is password protected * @param string $alg the algorithm, if the key is password protected * @return Key the key object * @throws KeyException if an error occurs in reading the data */ public static function create($data, $format = null, $password = null, $alg = 'PBES2-HS256+A128KW') { // 1. Detect format if ($format == null || $format == 'auto') { if (is_array($data)) { $format = 'php'; } elseif (json_decode($data, true) != null) { $format = 'json'; } elseif (substr_count($data, '.') == 5) { $format = 'jwe'; } elseif (preg_match('/-----([^-:]+)-----/', $data)) { $format = 'pem'; } } if ($format == null || $format == 'auto') { throw new KeyException('Cannot detect key format'); } // 2. Decode JSON if ($format == 'json') { $json = json_decode($data, true); if (isset($json['ciphertext'])) { $format = 'jwe'; } else { $data = $json; $format = 'php'; } } // 3. JWE if ($format == 'jwe') { if ($password == null) { throw new KeyException('No password for encrypted key'); } else { $keys = KeySet::createFromSecret($password, 'bin'); try { $jwe = JWE::decrypt($data, $keys, $alg, isset($data['ciphertext']) ? JWE::JSON_FORMAT : JWE::COMPACT_FORMAT); $data = json_decode($jwe->getPlaintext()); $format = 'php'; } catch (CryptException $e) { throw new KeyException('Cannot decrypt key', 0, $e); } } } // 4. PHP/JSON if ($format == 'php') { if ($data != null && isset($data['kty'])) { if (isset(self::$jwk_kty_map[$data['kty']])) { return new self::$jwk_kty_map[$data['kty']]($data, 'php'); } } } // 4. PEM if ($format == 'pem') { if (preg_match(Key::PEM_PUBLIC, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset = 0; $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $algorithm); // OBJECT IDENTIFIER - AlgorithmIdentifier $oid = ASN1::decodeOID($algorithm); if (isset(self::$oid_map[$oid])) { return new self::$oid_map[$oid]($data, 'pem'); } } else { foreach (self::$pem_map as $regex => $cls) { if (preg_match($regex, $data)) { return new $cls($data, 'pem'); } } } } // 5. Symmetric key if ($format == 'base64url' || $format == 'base64' || $format == 'bin') { return new SymmetricKey($data, $format); } return null; }
/** * Creates an EC key. * * The supported formats are: * * - `php` - JSON web key formatted as a PHP associative array * - `json` - JSON web key * - `jwe` - Encrypted JSON web key * - `pem` - the public or private key encoded in PEM (base64 encoded DER) format * * @param string|array $data the key data * @param string $format the format * @param string $password the password, if the key is password protected * @param string $alg the algorithm, if the key is password protected */ public function __construct($data, $format, $password = null, $alg = 'PBES2-HS256+A128KW') { switch ($format) { case 'php': case 'json': case 'jwe': parent::__construct($data, $format, $password, $alg); break; case 'pem': $offset = 0; $jwk = array(); if (preg_match(Key::PEM_PUBLIC, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $algorithm); // OBJECT IDENTIFIER - AlgorithmIdentifier $algorithm = ASN1::decodeOID($algorithm); if ($algorithm != self::EC_OID) { throw new KeyException('Not EC key'); } $offset += ASN1::readDER($der, $offset, $curve); // OBJECT IDENTIFIER - parameters $curve = ASN1::decodeOID($curve); if (!isset(self::$curves, $curve)) { throw new KeyException('Unrecognised EC parameter: ' . $curve); } $len = self::$curves[$curve]['len']; $offset += ASN1::readDER($der, $offset, $point); // BIT STRING - ECPoint if (strlen($point) != $len + 1) { throw new KeyException('Incorrect public key length: ' . strlen($point)); } if (ord($point[0]) != 0x4) { throw new KeyException('Invalid public key'); } // W $x = substr($point, 1, $len / 2); $y = substr($point, 1 + $len / 2); $jwk['kty'] = self::KTY; $jwk['crv'] = self::$curves[$curve]['crv']; $jwk['x'] = Util::base64url_encode($x); $jwk['y'] = Util::base64url_encode($y); } elseif (preg_match(self::PEM_PRIVATE, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset += ASN1::readDER($der, $offset, $data); // SEQUENCE $offset += ASN1::readDER($der, $offset, $version); // INTEGER if (ord($version) != 1) { throw new KeyException('Invalid private key version'); } $offset += ASN1::readDER($der, $offset, $d); // OCTET STRING [d] $offset += ASN1::readDER($der, $offset, $data); // SEQUENCE[0] $offset += ASN1::readDER($der, $offset, $curve); // OBJECT IDENTIFIER - parameters $curve = ASN1::decodeOID($curve); if (!isset(self::$curves, $curve)) { throw new KeyException('Unrecognised EC parameter: ' . $curve); } $len = self::$curves[$curve]['len']; $offset += ASN1::readDER($der, $offset, $data); // SEQUENCE[1] $offset += ASN1::readDER($der, $offset, $point); // BIT STRING - ECPoint if (strlen($point) != $len + 1) { throw new KeyException('Incorrect private key length: ' . strlen($point)); } if (ord($point[0]) != 0x4) { throw new KeyException('Invalid private key'); } // W $x = substr($point, 1, ($len - 1) / 2); $y = substr($point, 1 + ($len - 1) / 2); $jwk['kty'] = self::KTY; $jwk['crv'] = self::$curves[$curve]['crv']; $jwk['d'] = Util::base64url_encode($d); $jwk['x'] = Util::base64url_encode($x); $jwk['y'] = Util::base64url_encode($y); } parent::__construct($jwk); break; default: throw new KeyException('Incorrect format'); } if (!isset($this->data['kty'])) { $this->data['kty'] = self::KTY; } }