Example #1
0
 /**
  * Break a public or private key down into its constituent components
  *
  * @access public
  * @param string $key
  * @param string $password optional
  * @return array
  */
 static function load($key, $password = '')
 {
     if (!is_string($key)) {
         return false;
     }
     $parts = explode(' ', $key, 3);
     $key = isset($parts[1]) ? Base64::decode($parts[1]) : Base64::decode($parts[0]);
     if ($key === false) {
         return false;
     }
     $comment = isset($parts[2]) ? $parts[2] : false;
     if (substr($key, 0, 11) != "ssh-rsa") {
         return false;
     }
     Strings::shift($key, 11);
     if (strlen($key) <= 4) {
         return false;
     }
     extract(unpack('Nlength', Strings::shift($key, 4)));
     if (strlen($key) <= $length) {
         return false;
     }
     $publicExponent = new BigInteger(Strings::shift($key, $length), -256);
     if (strlen($key) <= 4) {
         return false;
     }
     extract(unpack('Nlength', Strings::shift($key, 4)));
     if (strlen($key) != $length) {
         return false;
     }
     $modulus = new BigInteger(Strings::shift($key, $length), -256);
     return array('isPublicKey' => true, 'modulus' => $modulus, 'publicExponent' => $publicExponent, 'comment' => $comment);
 }
Example #2
0
 /**
  * Break a public or private key down into its constituent components
  *
  * @access public
  * @param string $key
  * @param string $password optional
  * @return array
  */
 static function load($key, $password = '')
 {
     if (!is_string($key)) {
         return false;
     }
     $components = array('isPublicKey' => false, 'primes' => array(), 'exponents' => array(), 'coefficients' => array());
     $use_errors = libxml_use_internal_errors(true);
     $dom = new \DOMDocument();
     if (!$dom->loadXML('<xml>' . $key . '</xml>')) {
         return false;
     }
     $xpath = new \DOMXPath($dom);
     $keys = array('modulus', 'exponent', 'p', 'q', 'dp', 'dq', 'inverseq', 'd');
     foreach ($keys as $key) {
         // $dom->getElementsByTagName($key) is case-sensitive
         $temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='{$key}']");
         if (!$temp->length) {
             continue;
         }
         $value = new BigInteger(Base64::decode($temp->item(0)->nodeValue), 256);
         switch ($key) {
             case 'modulus':
                 $components['modulus'] = $value;
                 break;
             case 'exponent':
                 $components['publicExponent'] = $value;
                 break;
             case 'p':
                 $components['primes'][1] = $value;
                 break;
             case 'q':
                 $components['primes'][2] = $value;
                 break;
             case 'dp':
                 $components['exponents'][1] = $value;
                 break;
             case 'dq':
                 $components['exponents'][2] = $value;
                 break;
             case 'inverseq':
                 $components['coefficients'][2] = $value;
                 break;
             case 'd':
                 $components['privateExponent'] = $value;
         }
     }
     libxml_use_internal_errors($use_errors);
     return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false;
 }
Example #3
0
 /**
  * 
  * Encrypt a message with defuse/php-encryption, using an ephemeral key, 
  * then encrypt the key with RSA.
  * 
  * @param string $ciphertext
  * @param PrivateKey $rsaPrivateKey
  * 
  * @return string
  * @throws InvalidCiphertextException
  * @throws InvalidChecksumException
  */
 public static function decrypt($ciphertext, PrivateKey $rsaPrivateKey)
 {
     $split = explode(self::SEPARATOR, $ciphertext);
     if (\count($split) !== 4) {
         throw new InvalidCiphertextException('Invalid ciphertext message');
     }
     if (!\hash_equals($split[0], self::VERSION_TAG)) {
         throw new InvalidCiphertextException('Invalid version tag');
     }
     $checksum = \substr(\hash('sha256', implode('$', array_slice($split, 0, 3))), 0, 16);
     if (!\hash_equals($split[3], $checksum)) {
         throw new InvalidChecksumException('Invalid checksum');
     }
     $key = Key::loadFromAsciiSafeString(self::rsaDecrypt(Base64::decode($split[1]), $rsaPrivateKey));
     return Crypto::Decrypt(Base64::decode($split[2]), $key, true);
 }
 /**
  * @covers Base64::encode()
  * @covers Base64::decode()
  */
 public function testRandom()
 {
     for ($i = 1; $i < 32; ++$i) {
         for ($j = 0; $j < 50; ++$j) {
             $random = \random_bytes($i);
             $enc = Base64::encode($random);
             $this->assertSame($random, Base64::decode($enc));
             $this->assertSame(\base64_encode($random), $enc);
             $unpadded = \rtrim($enc, '=');
             $this->assertSame($random, Base64::decode($unpadded));
         }
     }
     $str = 'MIIFzzCCBLegAwIBAgIDAfdlMA0GCSqGSIb3DQEBBQUAMHMxCzAJBgNVBAYTAlBM' . 'MSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMSQwIgYDVQQ' . 'DDBtDT1BFIFNaQUZJUiAtIEt3YWxpZmlrb3dhbnkxFDASBgNVBAUTC05yIHdwaXN1Oi' . 'A2MB4XDTExMTEwOTA2MDAwMFoXDTEzMTEwOTA2MDAwMFowgdkxCzAJBgNVBAYTAlBMM' . 'RwwGgYDVQQKDBNVcnrEhWQgTWlhc3RhIEdkeW5pMRswGQYDVQQFExJQRVNFTDogNjEw' . 'NjA2MDMxMTgxGTAXBgNVBAMMEEplcnp5IFByemV3b3Jza2kxTzBNBgNVBBAwRgwiQWw' . 'uIE1hcnN6YcWCa2EgUGnFgnN1ZHNraWVnbyA1Mi81NAwNODEtMzgyIEdkeW5pYQwGUG' . '9sc2thDAlwb21vcnNraWUxDjAMBgNVBCoMBUplcnp5MRMwEQYDVQQEDApQcnpld29yc' . '2tpMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMm5vjGqHPthJCMqKpqssSISRo' . 's0PYDTcEQzyyurfX67EJWKtZj6HNwuDMEGJ02iBNZfjUl7r8dIi28bSKhNlsfycXZKY' . 'RcIjp0+r5RqtR2auo9GQ6veKb61DEAGIqaR+uLLcJVTHCu0w9oXLGbRlGth5eNoj03C' . 'xXVAH2IfhbNwIDAQABo4IChzCCAoMwDAYDVR0TAQH/BAIwADCCAUgGA1UdIAEB/wSCA' . 'TwwggE4MIIBNAYJKoRoAYb3IwEBMIIBJTCB3QYIKwYBBQUHAgIwgdAMgc1EZWtsYXJh' . 'Y2phIHRhIGplc3Qgb8Wbd2lhZGN6ZW5pZW0gd3lkYXdjeSwgxbxlIHRlbiBjZXJ0eWZ' . 'pa2F0IHpvc3RhxYIgd3lkYW55IGpha28gY2VydHlmaWthdCBrd2FsaWZpa293YW55IH' . 'pnb2RuaWUgeiB3eW1hZ2FuaWFtaSB1c3Rhd3kgbyBwb2RwaXNpZSBlbGVrdHJvbmlje' . 'm55bSBvcmF6IHRvd2FyenlzesSFY3ltaSBqZWogcm96cG9yesSFZHplbmlhbWkuMEMG' . 'CCsGAQUFBwIBFjdodHRwOi8vd3d3Lmtpci5jb20ucGwvY2VydHlmaWthY2phX2tsdWN' . '6eS9wb2xpdHlrYS5odG1sMAkGA1UdCQQCMAAwIQYDVR0RBBowGIEWai5wcnpld29yc2' . 'tpQGdkeW5pYS5wbDAOBgNVHQ8BAf8EBAMCBkAwgZ4GA1UdIwSBljCBk4AU3TGldJXip' . 'N4oGS3ZYmnBDMFs8gKhd6R1MHMxCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dh' . 'IEl6YmEgUm96bGljemVuaW93YSBTLkEuMSQwIgYDVQQDDBtDT1BFIFNaQUZJUiAtIEt' . '3YWxpZmlrb3dhbnkxFDASBgNVBAUTC05yIHdwaXN1OiA2ggJb9jBIBgNVHR8EQTA/MD' . '2gO6A5hjdodHRwOi8vd3d3Lmtpci5jb20ucGwvY2VydHlmaWthY2phX2tsdWN6eS9DU' . 'kxfT1pLMzIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBYPIqnAreyeql7/opJjcar/qWZ' . 'y9ruhB2q0lZFsJOhwgMnbQXzp/4vv93YJqcHGAXdHP6EO8FQX47mjo2ZKQmi+cIHJHL' . 'ONdX/3Im+M17V0iNAh7Z1lOSfTRT+iiwe/F8phcEaD5q2RmvYusR7zXZq/cLL0If0hX' . 'oPZ/EHQxjN8pxzxiUx6bJAgturnIMEfRNesxwghdr1dkUjOhGLf3kHVzgM6j3VAM7oF' . 'mMUb5y5s96Bzl10DodWitjOEH0vvnIcsppSxH1C1dCAi0o9f/1y2XuLNhBNHMAyTqpY' . 'PX8Yvav1c+Z50OMaSXHAnTa20zv8UtiHbaAhwlifCelUMj93S';
     try {
         Base64::decode($str, true);
         $this->fail('Strict padding not enforced');
     } catch (\Exception $ex) {
         $this->assertSame(Base64::decode($str), \base64_decode($str));
     }
 }
Example #5
0
 public function testEncrypt()
 {
     $keyPair = KeyPair::generateKeyPair(2048);
     $secretKey = $keyPair->getPrivateKey();
     $publicKey = $keyPair->getPublicKey();
     $plain = str_repeat('This is a relatively long plaintext message, far longer than RSA could safely encrypt directly.' . "\n", mt_rand(128, 512));
     $encrypt = EasyRSA::encrypt($plain, $publicKey);
     $decrypt = EasyRSA::decrypt($encrypt, $secretKey);
     $dissect = explode('$', $encrypt);
     $this->assertEquals(EasyRSA::VERSION_TAG, $dissect[0]);
     $this->assertEquals($decrypt, $plain);
     $size = strlen($plain);
     $size += 4;
     // Header
     $size += 16;
     // IV
     $size += 32;
     // HHKF Salt
     $size += 32;
     // HMAC
     $this->assertEquals(strlen(Base64::decode($dissect[2])), $size);
 }
Example #6
0
 /**
  * Automatic script execution
  *
  * @param array $autoRun
  * @return mixed
  */
 protected function autoRunScript(array $autoRun)
 {
     $ret = null;
     // Get a unique temporary file
     do {
         $script = \tempnam(ROOT . DIRECTORY_SEPARATOR . 'tmp', 'update-script-');
     } while (\file_exists($script));
     // What kind of autoRun script is it?
     switch ($autoRun['type']) {
         case 'php':
             \file_put_contents($script . '.php', Base64::decode($autoRun['data']));
             $ret = Sandbox::safeRequire($script . '.php');
             \unlink($script . '.php');
             break;
         case 'sh':
             \file_put_contents($script . '.sh', Base64::decode($autoRun['data']));
             $ret = \shell_exec($script . '.sh');
             \unlink($script . '.sh');
             break;
         case 'pgsql':
         case 'mysql':
             \file_put_contents($script . '.' . $autoRun['type'], Base64::decode($autoRun['data']));
             $ret = Sandbox::runSQLFile($script . '.' . $autoRun['type'], $autoRun['type']);
             \unlink($script . '.' . $autoRun['type']);
             break;
     }
     return $ret;
 }
Example #7
0
 /**
  * Break a public or private key down into its constituent components
  *
  * @access public
  * @param string $key
  * @param string $password optional
  * @return array
  */
 static function load($key, $password = '')
 {
     if (!is_string($key)) {
         return false;
     }
     static $one;
     if (!isset($one)) {
         $one = new BigInteger(1);
     }
     if (strpos($key, 'BEGIN SSH2 PUBLIC KEY')) {
         $data = preg_split('#[\\r\\n]+#', $key);
         $data = array_splice($data, 2, -1);
         $data = implode('', $data);
         $components = OpenSSH::load($data);
         if ($components === false) {
             return false;
         }
         if (!preg_match('#Comment: "(.+)"#', $key, $matches)) {
             return false;
         }
         $components['comment'] = str_replace(array('\\\\', '\\"'), array('\\', '"'), $matches[1]);
         return $components;
     }
     $components = array('isPublicKey' => false);
     $key = preg_split('#\\r\\n|\\r|\\n#', $key);
     $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0]));
     if ($type != 'ssh-rsa') {
         return false;
     }
     $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
     $components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
     $publicLength = trim(preg_replace('#Public-Lines: (\\d+)#', '$1', $key[3]));
     $public = Base64::decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
     $public = substr($public, 11);
     extract(unpack('Nlength', Strings::shift($public, 4)));
     $components['publicExponent'] = new BigInteger(Strings::shift($public, $length), -256);
     extract(unpack('Nlength', Strings::shift($public, 4)));
     $components['modulus'] = new BigInteger(Strings::shift($public, $length), -256);
     $privateLength = trim(preg_replace('#Private-Lines: (\\d+)#', '$1', $key[$publicLength + 4]));
     $private = Base64::decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength))));
     switch ($encryption) {
         case 'aes256-cbc':
             $symkey = static::generateSymmetricKey($password, 32);
             $crypto = new AES(AES::MODE_CBC);
     }
     if ($encryption != 'none') {
         $crypto->setKey($symkey);
         $crypto->setIV(str_repeat("", $crypto->getBlockLength() >> 3));
         $crypto->disablePadding();
         $private = $crypto->decrypt($private);
     }
     extract(unpack('Nlength', Strings::shift($private, 4)));
     if (strlen($private) < $length) {
         return false;
     }
     $components['privateExponent'] = new BigInteger(Strings::shift($private, $length), -256);
     extract(unpack('Nlength', Strings::shift($private, 4)));
     if (strlen($private) < $length) {
         return false;
     }
     $components['primes'] = array(1 => new BigInteger(Strings::shift($private, $length), -256));
     extract(unpack('Nlength', Strings::shift($private, 4)));
     if (strlen($private) < $length) {
         return false;
     }
     $components['primes'][] = new BigInteger(Strings::shift($private, $length), -256);
     $temp = $components['primes'][1]->subtract($one);
     $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp));
     $temp = $components['primes'][2]->subtract($one);
     $components['exponents'][] = $components['publicExponent']->modInverse($temp);
     extract(unpack('Nlength', Strings::shift($private, 4)));
     if (strlen($private) < $length) {
         return false;
     }
     $components['coefficients'] = array(2 => new BigInteger(Strings::shift($private, $length), -256));
     return $components;
 }
Example #8
0
 /**
  * Extract raw BER from Base64 encoding
  *
  * @access private
  * @param string $str
  * @return string
  */
 static function _extractBER($str)
 {
     /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
      * above and beyond the ceritificate.
      * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
      *
      * Bag Attributes
      *     localKeyID: 01 00 00 00
      * subject=/O=organization/OU=org unit/CN=common name
      * issuer=/O=organization/CN=common name
      */
     $temp = preg_replace('#.*?^-+[^-]+-+[\\r\\n ]*$#ms', '', $str, 1);
     // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
     $temp = preg_replace('#-+[^-]+-+#', '', $temp);
     // remove new lines
     $temp = str_replace(array("\r", "\n", ' '), '', $temp);
     $temp = preg_match('#^[a-zA-Z\\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false;
     return $temp != false ? $temp : $str;
 }
Example #9
0
 /**
  * Break a public or private key down into its constituent components
  *
  * @access public
  * @param string $key
  * @param string $password optional
  * @return array
  */
 static function load($key, $password = '')
 {
     if (!is_string($key)) {
         return false;
     }
     if (self::$format != self::MODE_DER) {
         $decoded = ASN1::extractBER($key);
         if ($decoded !== false) {
             $key = $decoded;
         } elseif (self::$format == self::MODE_PEM) {
             return false;
         }
     }
     $asn1 = new ASN1();
     $decoded = $asn1->decodeBER($key);
     if (empty($decoded)) {
         return false;
     }
     $meta = [];
     $asn1->loadOIDs(oids);
     $decrypted = $asn1->asn1map($decoded[0], EncryptedPrivateKeyInfo);
     if (strlen($password) && is_array($decrypted)) {
         $algorithm = $decrypted['encryptionAlgorithm']['algorithm'];
         switch ($algorithm) {
             // PBES1
             case 'pbeWithMD2AndDES-CBC':
             case 'pbeWithMD2AndRC2-CBC':
             case 'pbeWithMD5AndDES-CBC':
             case 'pbeWithMD5AndRC2-CBC':
             case 'pbeWithSHA1AndDES-CBC':
             case 'pbeWithSHA1AndRC2-CBC':
             case 'pbeWithSHAAnd3-KeyTripleDES-CBC':
             case 'pbeWithSHAAnd2-KeyTripleDES-CBC':
             case 'pbeWithSHAAnd128BitRC2-CBC':
             case 'pbeWithSHAAnd40BitRC2-CBC':
             case 'pbeWithSHAAnd128BitRC4':
             case 'pbeWithSHAAnd40BitRC4':
                 $cipher = self::getPBES1EncryptionObject($algorithm);
                 $hash = self::getPBES1Hash($algorithm);
                 $kdf = self::getPBES1KDF($algorithm);
                 $meta['meta']['algorithm'] = $algorithm;
                 $temp = $asn1->decodeBER($decrypted['encryptionAlgorithm']['parameters']);
                 extract($asn1->asn1map($temp[0], PBEParameter));
                 $iterationCount = (int) $iterationCount->toString();
                 $cipher->setPassword($password, $kdf, $hash, Base64::decode($salt), $iterationCount);
                 $key = $cipher->decrypt(Base64::decode($decrypted['encryptedData']));
                 $decoded = $asn1->decodeBER($key);
                 if (empty($decoded)) {
                     return false;
                 }
                 break;
             case 'id-PBES2':
                 $meta['meta']['algorithm'] = $algorithm;
                 $temp = $asn1->decodeBER($decrypted['encryptionAlgorithm']['parameters']);
                 $temp = $asn1->asn1map($temp[0], PBES2params);
                 extract($temp);
                 $cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']);
                 $meta['meta']['cipher'] = $encryptionScheme['algorithm'];
                 $temp = $asn1->decodeBER($decrypted['encryptionAlgorithm']['parameters']);
                 $temp = $asn1->asn1map($temp[0], PBES2params);
                 extract($temp);
                 if (!$cipher instanceof RC2) {
                     $cipher->setIV(Base64::decode($encryptionScheme['parameters']['octetString']));
                 } else {
                     $temp = $asn1->decodeBER($encryptionScheme['parameters']);
                     extract($asn1->asn1map($temp[0], RC2CBCParameter));
                     $effectiveKeyLength = (int) $rc2ParametersVersion->toString();
                     switch ($effectiveKeyLength) {
                         case 160:
                             $effectiveKeyLength = 40;
                             break;
                         case 120:
                             $effectiveKeyLength = 64;
                             break;
                         case 58:
                             $effectiveKeyLength = 128;
                             break;
                             //default: // should be >= 256
                     }
                     $cipher->setIV(Base64::decode($iv));
                     $cipher->setKeyLength($effectiveKeyLength);
                 }
                 $meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm'];
                 switch ($keyDerivationFunc['algorithm']) {
                     case 'id-PBKDF2':
                         $temp = $asn1->decodeBER($keyDerivationFunc['parameters']);
                         $prf = ['algorithm' => 'id-hmacWithSHA1'];
                         $params = $asn1->asn1map($temp[0], PBKDF2params);
                         extract($params);
                         $meta['meta']['prf'] = $prf['algorithm'];
                         $hash = str_replace('-', '/', substr($prf['algorithm'], 11));
                         $params = [$password, 'pbkdf2', $hash, Base64::decode($salt), (int) $iterationCount->toString()];
                         if (isset($keyLength)) {
                             $params[] = (int) $keyLength->toString();
                         }
                         call_user_func_array([$cipher, 'setPassword'], $params);
                         $key = $cipher->decrypt(Base64::decode($decrypted['encryptedData']));
                         $decoded = $asn1->decodeBER($key);
                         if (empty($decoded)) {
                             return false;
                         }
                         break;
                     default:
                         throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys');
                 }
                 break;
             case 'id-PBMAC1':
                 //$temp = $asn1->decodeBER($decrypted['encryptionAlgorithm']['parameters']);
                 //$value = $asn1->asn1map($temp[0], PBMAC1params);
                 // since i can't find any implementation that does PBMAC1 it is unsupported
                 throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.');
                 // at this point we'll assume that the key conforms to PublicKeyInfo
         }
     }
     $private = $asn1->asn1map($decoded[0], PrivateKeyInfo);
     if (is_array($private)) {
         return $private + $meta;
     }
     // EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference
     // is that the former has an octet string and the later has a bit string. the first byte of a bit
     // string represents the number of bits in the last byte that are to be ignored but, currently,
     // bit strings wanting a non-zero amount of bits trimmed are not supported
     $public = $asn1->asn1map($decoded[0], PublicKeyInfo);
     if (is_array($public)) {
         $public['publicKey'] = base64_decode($public['publicKey']);
         if ($public['publicKey'][0] != "") {
             return false;
         }
         $public['publicKey'] = base64_encode(substr($public['publicKey'], 1));
         return $public;
     }
     return false;
 }
Example #10
0
 /**
  * ASN.1 Encode (Helper function)
  *
  * @param string $source
  * @param string $mapping
  * @param int $idx
  * @return string
  * @throws \RuntimeException if the input has an error in it
  * @access private
  */
 function _encode_der($source, $mapping, $idx = null, $special = array())
 {
     if ($source instanceof Element) {
         return $source->element;
     }
     // do not encode (implicitly optional) fields with value set to default
     if (isset($mapping['default']) && $source === $mapping['default']) {
         return '';
     }
     if (isset($idx)) {
         if (isset($special[$idx])) {
             $source = call_user_func($special[$idx], $source);
         }
         $this->location[] = $idx;
     }
     $tag = $mapping['type'];
     switch ($tag) {
         case self::TYPE_SET:
             // Children order is not important, thus process in sequence.
         // Children order is not important, thus process in sequence.
         case self::TYPE_SEQUENCE:
             $tag |= 0x20;
             // set the constructed bit
             // ignore the min and max
             if (isset($mapping['min']) && isset($mapping['max'])) {
                 $value = array();
                 $child = $mapping['children'];
                 foreach ($source as $content) {
                     $temp = $this->_encode_der($content, $child, null, $special);
                     if ($temp === false) {
                         return false;
                     }
                     $value[] = $temp;
                 }
                 /* "The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared
                                         as octet strings with the shorter components being padded at their trailing end with 0-octets.
                                         NOTE - The padding octets are for comparison purposes only and do not appear in the encodings."
                 
                                        -- sec 11.6 of http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf  */
                 if ($mapping['type'] == self::TYPE_SET) {
                     sort($value);
                 }
                 $value = implode($value, '');
                 break;
             }
             $value = '';
             foreach ($mapping['children'] as $key => $child) {
                 if (!array_key_exists($key, $source)) {
                     if (!isset($child['optional'])) {
                         return false;
                     }
                     continue;
                 }
                 $temp = $this->_encode_der($source[$key], $child, $key, $special);
                 if ($temp === false) {
                     return false;
                 }
                 // An empty child encoding means it has been optimized out.
                 // Else we should have at least one tag byte.
                 if ($temp === '') {
                     continue;
                 }
                 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
                 if (isset($child['constant'])) {
                     /*
                       From X.680-0207.pdf#page=58 (30.6):
                     
                       "The tagging construction specifies explicit tagging if any of the following holds:
                        ...
                        c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
                        AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
                        an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
                     */
                     if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
                         $subtag = chr(self::CLASS_CONTEXT_SPECIFIC << 6 | 0x20 | $child['constant']);
                         $temp = $subtag . Functions::encodeLength(strlen($temp)) . $temp;
                     } else {
                         $subtag = chr(self::CLASS_CONTEXT_SPECIFIC << 6 | ord($temp[0]) & 0x20 | $child['constant']);
                         $temp = $subtag . substr($temp, 1);
                     }
                 }
                 $value .= $temp;
             }
             break;
         case self::TYPE_CHOICE:
             $temp = false;
             foreach ($mapping['children'] as $key => $child) {
                 if (!isset($source[$key])) {
                     continue;
                 }
                 $temp = $this->_encode_der($source[$key], $child, $key, $special);
                 if ($temp === false) {
                     return false;
                 }
                 // An empty child encoding means it has been optimized out.
                 // Else we should have at least one tag byte.
                 if ($temp === '') {
                     continue;
                 }
                 $tag = ord($temp[0]);
                 // if isset($child['constant']) is true then isset($child['optional']) should be true as well
                 if (isset($child['constant'])) {
                     if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
                         $subtag = chr(self::CLASS_CONTEXT_SPECIFIC << 6 | 0x20 | $child['constant']);
                         $temp = $subtag . Functions::encodeLength(strlen($temp)) . $temp;
                     } else {
                         $subtag = chr(self::CLASS_CONTEXT_SPECIFIC << 6 | ord($temp[0]) & 0x20 | $child['constant']);
                         $temp = $subtag . substr($temp, 1);
                     }
                 }
             }
             if (isset($idx)) {
                 array_pop($this->location);
             }
             if ($temp && isset($mapping['cast'])) {
                 $temp[0] = chr($mapping['class'] << 6 | $tag & 0x20 | $mapping['cast']);
             }
             return $temp;
         case self::TYPE_INTEGER:
         case self::TYPE_ENUMERATED:
             if (!isset($mapping['mapping'])) {
                 if (is_numeric($source)) {
                     $source = new BigInteger($source);
                 }
                 $value = $source->toBytes(true);
             } else {
                 $value = array_search($source, $mapping['mapping']);
                 if ($value === false) {
                     return false;
                 }
                 $value = new BigInteger($value);
                 $value = $value->toBytes(true);
             }
             if (!strlen($value)) {
                 $value = chr(0);
             }
             break;
         case self::TYPE_UTC_TIME:
         case self::TYPE_GENERALIZED_TIME:
             $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
             $format .= 'mdHis';
             $value = @gmdate($format, strtotime($source)) . 'Z';
             break;
         case self::TYPE_BIT_STRING:
             if (isset($mapping['mapping'])) {
                 $bits = array_fill(0, count($mapping['mapping']), 0);
                 $size = 0;
                 for ($i = 0; $i < count($mapping['mapping']); $i++) {
                     if (in_array($mapping['mapping'][$i], $source)) {
                         $bits[$i] = 1;
                         $size = $i;
                     }
                 }
                 if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
                     $size = $mapping['min'] - 1;
                 }
                 $offset = 8 - ($size + 1 & 7);
                 $offset = $offset !== 8 ? $offset : 0;
                 $value = chr($offset);
                 for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
                     unset($bits[$i]);
                 }
                 $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
                 $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
                 foreach ($bytes as $byte) {
                     $value .= chr(bindec($byte));
                 }
                 break;
             }
         case self::TYPE_OCTET_STRING:
             /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
                                the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
             
                                -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
             $value = Base64::decode($source);
             break;
         case self::TYPE_OBJECT_IDENTIFIER:
             $oid = preg_match('#(?:\\d+\\.)+#', $source) ? $source : array_search($source, $this->oids);
             if ($oid === false) {
                 throw new \RuntimeException('Invalid OID');
                 return false;
             }
             $value = '';
             $parts = explode('.', $oid);
             $value = chr(40 * $parts[0] + $parts[1]);
             for ($i = 2; $i < count($parts); $i++) {
                 $temp = '';
                 if (!$parts[$i]) {
                     $temp = "";
                 } else {
                     while ($parts[$i]) {
                         $temp = chr(0x80 | $parts[$i] & 0x7f) . $temp;
                         $parts[$i] >>= 7;
                     }
                     $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7f);
                 }
                 $value .= $temp;
             }
             break;
         case self::TYPE_ANY:
             $loc = $this->location;
             if (isset($idx)) {
                 array_pop($this->location);
             }
             switch (true) {
                 case !isset($source):
                     return $this->_encode_der(null, array('type' => self::TYPE_NULL) + $mapping, null, $special);
                 case is_int($source):
                 case $source instanceof BigInteger:
                     return $this->_encode_der($source, array('type' => self::TYPE_INTEGER) + $mapping, null, $special);
                 case is_float($source):
                     return $this->_encode_der($source, array('type' => self::TYPE_REAL) + $mapping, null, $special);
                 case is_bool($source):
                     return $this->_encode_der($source, array('type' => self::TYPE_BOOLEAN) + $mapping, null, $special);
                 case is_array($source) && count($source) == 1:
                     $typename = implode('', array_keys($source));
                     $outtype = array_search($typename, $this->ANYmap, true);
                     if ($outtype !== false) {
                         return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special);
                     }
             }
             $filters = $this->filters;
             foreach ($loc as $part) {
                 if (!isset($filters[$part])) {
                     $filters = false;
                     break;
                 }
                 $filters = $filters[$part];
             }
             if ($filters === false) {
                 throw new \RuntimeException('No filters defined for ' . implode('/', $loc));
                 return false;
             }
             return $this->_encode_der($source, $filters + $mapping, null, $special);
         case self::TYPE_NULL:
             $value = '';
             break;
         case self::TYPE_NUMERIC_STRING:
         case self::TYPE_TELETEX_STRING:
         case self::TYPE_PRINTABLE_STRING:
         case self::TYPE_UNIVERSAL_STRING:
         case self::TYPE_UTF8_STRING:
         case self::TYPE_BMP_STRING:
         case self::TYPE_IA5_STRING:
         case self::TYPE_VISIBLE_STRING:
         case self::TYPE_VIDEOTEX_STRING:
         case self::TYPE_GRAPHIC_STRING:
         case self::TYPE_GENERAL_STRING:
             $value = $source;
             break;
         case self::TYPE_BOOLEAN:
             $value = $source ? "ÿ" : "";
             break;
         default:
             throw new \RuntimeException('Mapping provides no type definition for ' . implode('/', $this->location));
             return false;
     }
     if (isset($idx)) {
         array_pop($this->location);
     }
     if (isset($mapping['cast'])) {
         if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) {
             $value = chr($tag) . Functions::encodeLength(strlen($value)) . $value;
             $tag = $mapping['class'] << 6 | 0x20 | $mapping['cast'];
         } else {
             $tag = $mapping['class'] << 6 | ord($temp[0]) & 0x20 | $mapping['cast'];
         }
     }
     return chr($tag) . Functions::encodeLength(strlen($value)) . $value;
 }
Example #11
0
 /**
  * Break a public or private key down into its constituent components
  *
  * @access public
  * @param string $key
  * @param string $password optional
  * @return array
  */
 static function load($key, $password = '')
 {
     if (!is_string($key)) {
         return false;
     }
     $key = Base64::decode($key);
     if (!is_string($key) || strlen($key) < 20) {
         return false;
     }
     // PUBLICKEYSTRUC  publickeystruc
     // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx
     extract(unpack('atype/aversion/vreserved/Valgo', self::_string_shift($key, 8)));
     switch (ord($type)) {
         case self::PUBLICKEYBLOB:
         case self::PUBLICKEYBLOBEX:
             $publickey = true;
             break;
         case self::PRIVATEKEYBLOB:
             $publickey = false;
             break;
         default:
             return false;
     }
     $components = array('isPublicKey' => $publickey);
     // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx
     switch ($algo) {
         case self::CALG_RSA_KEYX:
         case self::CALG_RSA_SIGN:
             break;
         default:
             return false;
     }
     // RSAPUBKEY rsapubkey
     // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx
     // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit
     extract(unpack('Vmagic/Vbitlen/a4pubexp', self::_string_shift($key, 12)));
     switch ($magic) {
         case self::RSA2:
             $components['isPublicKey'] = false;
         case self::RSA1:
             break;
         default:
             return false;
     }
     $baseLength = $bitlen / 16;
     if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) {
         return false;
     }
     $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256);
     // BYTE modulus[rsapubkey.bitlen/8]
     $components['modulus'] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 8)), 256);
     if ($publickey) {
         return $components;
     }
     $components['isPublicKey'] = false;
     // BYTE prime1[rsapubkey.bitlen/16]
     $components['primes'] = array(1 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256));
     // BYTE prime2[rsapubkey.bitlen/16]
     $components['primes'][] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256);
     // BYTE exponent1[rsapubkey.bitlen/16]
     $components['exponents'] = array(1 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256));
     // BYTE exponent2[rsapubkey.bitlen/16]
     $components['exponents'][] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256);
     // BYTE coefficient[rsapubkey.bitlen/16]
     $components['coefficients'] = array(2 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256));
     if (isset($components['privateExponent'])) {
         $components['publicExponent'] = $components['privateExponent'];
     }
     // BYTE privateExponent[rsapubkey.bitlen/8]
     $components['privateExponent'] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 8)), 256);
     return $components;
 }
Example #12
0
 /**
  * Coerce a string into base64 format.
  *
  * @param string $hash
  * @param string $algo
  * @return string
  * @throws \Exception
  */
 protected function coerceBase64(string $hash, string $algo = 'sha256') : string
 {
     switch ($algo) {
         case 'sha256':
             $limits = ['raw' => 32, 'hex' => 64, 'pad_min' => 40, 'pad_max' => 44];
             break;
         default:
             throw new \Exception('Browsers currently only support sha256 public key pins.');
     }
     $len = Binary::safeStrlen($hash);
     if ($len === $limits['hex']) {
         $hash = Base64::encode(Hex::decode($hash));
     } elseif ($len === $limits['raw']) {
         $hash = Base64::encode($hash);
     } elseif ($len > $limits['pad_min'] && $len < $limits['pad_max']) {
         // Padding was stripped!
         $hash .= \str_repeat('=', $len % 4);
         // Base64UrlSsafe encoded.
         if (\strpos($hash, '_') !== false || \strpos($hash, '-') !== false) {
             $hash = Base64UrlSafe::decode($hash);
         } else {
             $hash = Base64::decode($hash);
         }
         $hash = Base64::encode($hash);
     }
     return $hash;
 }
Example #13
0
 /**
  * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching)
  *
  * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5
  * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified.
  * This means that under rare conditions you can have a perfectly valid v1.5 signature
  * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends
  * that if you're going to validate these types of signatures you "should indicate
  * whether the underlying BER encoding is a DER encoding and hence whether the signature
  * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do
  * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of
  * RSA::PADDING_PKCS1... that means BER encoding was used.
  *
  * @access private
  * @param string $m
  * @param string $s
  * @return bool
  */
 function _rsassa_pkcs1_v1_5_relaxed_verify($m, $s)
 {
     // Length checking
     if (strlen($s) != $this->k) {
         return false;
     }
     // RSA verification
     $s = $this->_os2ip($s);
     $m2 = $this->_rsavp1($s);
     if ($m2 === false) {
         return false;
     }
     $em = $this->_i2osp($m2, $this->k);
     if ($em === false) {
         return false;
     }
     if ($this->_string_shift($em, 2) != "") {
         return false;
     }
     $em = ltrim($em, "ÿ");
     if ($this->_string_shift($em) != "") {
         return false;
     }
     $asn1 = new ASN1();
     $decoded = $asn1->decodeBER($em);
     if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) {
         return false;
     }
     $AlgorithmIdentifier = array('type' => ASN1::TYPE_SEQUENCE, 'children' => array('algorithm' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 'parameters' => array('type' => ASN1::TYPE_ANY, 'optional' => true)));
     $DigestInfo = array('type' => ASN1::TYPE_SEQUENCE, 'children' => array('digestAlgorithm' => $AlgorithmIdentifier, 'digest' => array('type' => ASN1::TYPE_OCTET_STRING)));
     $oids = array('1.2.840.113549.2.2' => 'md2', '1.2.840.113549.2.4' => 'md4', '1.2.840.113549.2.5' => 'md5', '1.3.14.3.2.26' => 'sha1', '2.16.840.1.101.3.4.2.1' => 'sha256', '2.16.840.1.101.3.4.2.2' => 'sha384', '2.16.840.1.101.3.4.2.3' => 'sha512');
     $asn1->loadOIDs($oids);
     $decoded = $asn1->asn1map($decoded[0], $DigestInfo);
     if (!isset($decoded) || $decoded === false) {
         return false;
     }
     if (!in_array($decoded['digestAlgorithm']['algorithm'], $oids)) {
         return false;
     }
     $hash = new Hash($decoded['digestAlgorithm']['algorithm']);
     $em = $hash->hash($m);
     $em2 = Base64::decode($decoded['digest']);
     return $this->_equals($em, $em2);
 }