Example #1
0
 /**
  * Creates a symmetric key.
  *
  * The supported formats are:
  *
  * - `php` - JSON web key formatted as a PHP associative array
  * - `json` - JSON web key
  * - `jwe` - Encrypted JSON web key
  * - `base64url` - the symmetric key encoded in Base64url format
  * - `base64` - the symmetric key encoded in Base64 format
  * - `bin` - the symmetric key encoded in binary 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 'base64url':
             $jwk = array('kty' => self::KTY, 'k' => $data);
             parent::__construct($jwk);
             break;
         case 'base64':
             $jwk = array('kty' => self::KTY, 'k' => Util::base64url_encode(base64_decode($data)));
             parent::__construct($jwk);
             break;
         case 'bin':
             $jwk = array('kty' => self::KTY, 'k' => Util::base64url_encode($data));
             parent::__construct($jwk);
             break;
         default:
             throw new KeyException('Incorrect format');
     }
     if (!isset($this->data['kty'])) {
         $this->data['kty'] = self::KTY;
     }
 }
Example #2
0
 public function sign($data, $keys, $kid = null)
 {
     $key = $this->getSigningKey($keys, $kid);
     if ($key == null || !is_a($key, 'SimpleJWT\\Keys\\SymmetricKey')) {
         throw new KeyException('Key not found or is invalid');
     }
     return Util::base64url_encode(hash_hmac('sha' . $this->size, $data, $key->toBinary(), true));
 }
Example #3
0
 function testPBES2Decrypt()
 {
     $encrypted_key = 'TrqXOwuNUfDV9VPTNbyGvEJ9JMjefAVn-TR1uIxR9p6hsRQh9Tk7BA';
     $password = '******';
     $keys = $this->getKeySet($password);
     $headers = array('alg' => 'PBES2-HS256+A128KW', 'p2c' => 4096, 'p2s' => '2WCTcJZ1Rvd_CJuJripQ1w');
     $alg = new PBES2('PBES2-HS256+A128KW');
     $cek = $alg->decryptKey($encrypted_key, $keys, $headers);
     $this->assertEquals('bxsZNEIdFE5csDjwQdBScKGDJDfK7LmsgReZwsMw_bY', Util::base64url_encode($cek));
 }
Example #4
0
 /**
  * Signs and serialises the JWT.
  *
  * @param SimpleJWT\Keys\KeySet $keys the key set containing the key to sign the
  * JWT
  * @param string $kid the ID of the key to use to sign. If null, this
  * is automatically retrieved
  * @param bool|array $auto_complete an array of headers or claims that
  * should be automatically completed, or false if no auto-completion is
  * to be performed
  * @param string $alg if not null, override the `alg` header
  * @param string $format the JWT serialisation format
  * @return string the signed and serialised JWT
  * @throws SimpleJWT\Keys\KeyException if there is an error obtaining the key
  * to sign the JWT
  * @throws SimpleJWT\Crypt\CryptException if there is a cryptographic error
  */
 public function encode($keys, $kid = null, $auto_complete = array('iat', 'kid'), $alg = null, $format = self::COMPACT_FORMAT)
 {
     if ($auto_complete === false) {
         $auto_complete = array();
     }
     if ($alg != null) {
         $this->headers['alg'] = $alg;
     }
     if (in_array('iat', $auto_complete) && !isset($this->claims['iat'])) {
         $this->claims['iat'] = time();
     }
     try {
         $signer = AlgorithmFactory::create($this->headers['alg']);
     } catch (\UnexpectedValueException $e) {
         throw new CryptException($e->getMessage(), 0, $e);
     }
     $key = $signer->getSigningKey($keys, $kid);
     if ($key != null && in_array('kid', $auto_complete)) {
         $this->headers['kid'] = $key->getKeyId();
     }
     $protected = Util::base64url_encode(json_encode($this->headers));
     $payload = Util::base64url_encode(json_encode($this->claims));
     $signing_input = $protected . '.' . $payload;
     $signature = $signer->sign($signing_input, $keys, $kid);
     switch ($format) {
         case self::COMPACT_FORMAT:
             return $signing_input . '.' . $signature;
         case self::JSON_FORMAT:
             return json_encode(array('protected' => $protected, 'payload' => $payload, 'signature' => $signature));
         default:
             throw new \InvalidArgumentException('Incorrect format');
     }
 }
Example #5
0
 public function encryptAndSign($plaintext, $cek, $additional, $iv = null)
 {
     $params = self::$alg_params[$this->getAlg()];
     if (strlen($cek) != $this->getCEKSize() / 8) {
         throw new CryptException('Incorrect key length');
     }
     if ($iv == null) {
         $iv = openssl_random_pseudo_bytes($this->getIVSize() / 8);
     } else {
         $iv = Util::base64url_decode($iv);
         if (strlen($iv) != $this->getIVSize() / 8) {
             throw new CryptException('Incorrect IV length');
         }
     }
     list($mac_key, $enc_key) = str_split($cek, (int) (strlen($cek) / 2));
     $al = Util::packInt64(strlen($additional) * 8);
     $e = openssl_encrypt($plaintext, $params['cipher'], $enc_key, OPENSSL_RAW_DATA, $iv);
     $m = hash_hmac($params['hash'], $additional . $iv . $e . $al, $mac_key, true);
     $t = substr($m, 0, $params['tag']);
     return array('ciphertext' => Util::base64url_encode($e), 'tag' => Util::base64url_encode($t), 'iv' => Util::base64url_encode($iv));
 }
 protected function base64url($base64)
 {
     return Util::base64url_encode(base64_decode($base64));
 }
Example #7
0
 /**
  * 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;
     }
 }
Example #8
0
 /**
  * Encrypts the JWE.
  *
  * @param SimpleJWT\Keys\KeySet $keys the key set containing the key to encrypt the
  * content encryption key
  * @param string $kid the ID of the key to use to encrypt. If null, this
  * is automatically retrieved
  * @param string $format the JWE serialisation format
  * @return string the encrypted JWE
  * @throws SimpleJWT\Keys\KeyException if there is an error obtaining the key
  * to sign the JWT
  * @throws SimpleJWT\Crypt\CryptException if there is a cryptographic error
  */
 public function encrypt($keys, $kid = null, $format = self::COMPACT_FORMAT)
 {
     if (!isset($this->headers['alg'])) {
         throw new \InvalidArgumentException('alg parameter missing');
     }
     if (!isset($this->headers['enc'])) {
         throw new \InvalidArgumentException('enc parameter missing');
     }
     $key_enc = AlgorithmFactory::create($this->headers['alg']);
     $content_enc = AlgorithmFactory::create($this->headers['enc']);
     if ($kid != null) {
         $this->headers['kid'] = $kid;
     }
     if ($key_enc instanceof KeyDerivationAlgorithm) {
         $agreed_key = $key_enc->deriveKey($keys, $this->headers, $kid);
         if ($key_enc instanceof KeyEncryptionAlgorithm) {
             // Key agreement with wrapping
             $keys->add(new SymmetricKey(array('kty' => SymmetricKey::KTY, 'alg' => $this->headers['alg'], 'k' => Util::base64url_encode($agreed_key)), 'php'));
         } else {
             // Direct key agreement or direct encryption
             $cek = $agreed_key;
         }
     }
     if (!isset($cek)) {
         $cek = Util::random_bytes($content_enc->getCEKSize() / 8);
     }
     if ($key_enc instanceof KeyEncryptionAlgorithm) {
         $encrypted_key = $key_enc->encryptKey($cek, $keys, $this->headers, $kid);
     } else {
         $encrypted_key = '';
     }
     if (isset($this->headers['zip'])) {
         switch ($this->headers['zip']) {
             case 'DEF':
                 $plaintext = gzdeflate($this->plaintext);
                 break;
             default:
                 throw new \InvalidArgumentException('Unsupported zip header:' . $this->headers['zip']);
         }
     } else {
         $plaintext = $this->plaintext;
     }
     $protected = Util::base64url_encode(json_encode($this->headers));
     $results = $content_enc->encryptAndSign($plaintext, $cek, $protected);
     $ciphertext = $results['ciphertext'];
     $iv = isset($results['iv']) ? $results['iv'] : '';
     $tag = $results['tag'];
     switch ($format) {
         case self::COMPACT_FORMAT:
             return $protected . '.' . $encrypted_key . '.' . $iv . '.' . $ciphertext . '.' . $tag;
         case self::JSON_FORMAT:
             $obj = array('protected' => $protected, 'ciphertext' => $ciphertext, 'tag' => $tag, 'encrypted_key' => $encrypted_key);
             if ($iv) {
                 $obj['iv'] = $iv;
             }
             return json_encode($obj);
         default:
             throw new \InvalidArgumentException('Incorrect format');
     }
 }
Example #9
0
 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);
 }
Example #10
0
 public function encryptKey($cek, $keys, &$headers, $kid = null)
 {
     $salt_input = $this->generateSaltInput();
     $headers['p2s'] = Util::base64url_encode($salt_input);
     $headers['p2c'] = $this->iterations;
     $key = $this->selectKey($keys, $kid);
     if ($key == null) {
         throw new CryptException('Key not found or is invalid');
     }
     $derived_keyset = $this->getKeySetFromPassword($key->toBinary(), $headers);
     return $this->aeskw->encryptKey($cek, $derived_keyset, $headers);
 }
Example #11
0
 /**
  * Obtains a signature for the key.  The signature is derived from the
  * keys to the JSON web key object as returned by the {@link getSignatureKeys()}
  * function.
  *
  * For asymmetric keys, the public and private keys should have the same
  * signature.
  *
  * @return string the signature
  */
 public function getSignature()
 {
     $keys = $this->getSignatureKeys();
     $signing = array();
     foreach ($keys as $key) {
         $signing[$key] = $this->data[$key];
     }
     ksort($signing);
     return Util::base64url_encode(hash('sha256', json_encode($signing), true));
 }
Example #12
0
 /**
  * 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;
     }
 }
Example #13
0
 public function encryptKey($cek, $keys, &$headers, $kid = null)
 {
     $key = $this->selectKey($keys, $kid, array(Key::PUBLIC_PROPERTY => true, '~use' => 'enc'));
     if ($key == null || !$key->isPublic()) {
         throw new CryptException('Key not found or is invalid');
     }
     $headers['kid'] = $key->getKeyId();
     $params = self::$alg_params[$this->getAlg()];
     if (isset($params['oaep'])) {
         // $key->getSize() ignores the first octet when calculating the key size,
         // therefore we need to add it back in
         $cek = $this->oaep_encode($cek, 1 + $key->getSize() / 8, $params['oaep']);
     }
     $ciphertext = '';
     if (!openssl_public_encrypt($cek, $ciphertext, $key->toPEM(), $params['padding'])) {
         $messages = array();
         while ($message = openssl_error_string()) {
             $messages[] = $message;
         }
         throw new CryptException('Cannot encrypt key: ' . implode("\n", $messages));
     }
     return Util::base64url_encode($ciphertext);
 }
Example #14
0
 public function shortHash($data)
 {
     $hash = hash('sha' . $this->size, $data, true);
     $short = substr($hash, 0, $this->size / 16);
     return Util::base64url_encode($short);
 }
Example #15
0
 protected function hex2base64url($hex)
 {
     return Util::base64url_encode(pack('H*', $hex));
 }