/** * @group github602 */ public function testEmptyContextTag() { $asn1 = new ASN1(); $decoded = $asn1->decodeBER("�"); $this->assertInternalType('array', $decoded); $this->assertCount(0, $decoded[0]['content']); }
/** * 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 = ['isPublicKey' => strpos($key, 'PUBLIC') !== false]; $key = parent::load($key, $password); if ($key === false) { return false; } $asn1 = new ASN1(); $decoded = $asn1->decodeBER($key); if (empty($decoded)) { return false; } $key = $asn1->asn1map($decoded[0], RSAPrivateKey); if (is_array($key)) { $components += ['modulus' => $key['modulus'], 'publicExponent' => $key['publicExponent'], 'privateExponent' => $key['privateExponent'], 'primes' => [1 => $key['prime1'], $key['prime2']], 'exponents' => [1 => $key['exponent1'], $key['exponent2']], 'coefficients' => [2 => $key['coefficient']]]; if ($key['version'] == 'multi') { foreach ($key['otherPrimeInfos'] as $primeInfo) { $components['primes'][] = $primeInfo['prime']; $components['exponents'][] = $primeInfo['exponent']; $components['coefficients'][] = $primeInfo['coefficient']; } } return $components; } $key = $asn1->asn1map($decoded[0], RSAPublicKey); return is_array($key) ? $components + $key : false; }
/** * {@inheritdoc} */ protected function supportsKey($key) { if (false === parent::supportsKey($key)) { return false; } // openssl_sign with EC keys was introduced in this PHP release $minVersions = array('5.4' => '5.4.26', '5.5' => '5.5.10', '5.6' => '5.6.0'); if (isset($minVersions[PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION]) && version_compare(PHP_VERSION, $minVersions[PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION], '<')) { return false; } $keyDetails = openssl_pkey_get_details($key); if (0 === preg_match('/-----BEGIN PUBLIC KEY-----([^-]+)-----END PUBLIC KEY-----/', $keyDetails['key'], $matches)) { return false; } $publicKey = trim($matches[1]); $asn1 = new ASN1(); /* * http://tools.ietf.org/html/rfc3279#section-2.2.3 * AlgorithmIdentifier ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, * parameters ANY DEFINED BY algorithm OPTIONAL * } * For ECDSA Signature Algorithm: * algorithm: ansi-X9-62 => 1.2.840.10045.2.1 * parameters: id-ecSigType => 1.2.840.10045.x.y.z * */ $asnAlgorithmIdentifier = array('type' => ASN1::TYPE_SEQUENCE, 'children' => array('ansi-X9-62' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 'id-ecSigType' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER))); /* * http://tools.ietf.org/html/rfc5280#section-4.1 * SubjectPublicKeyInfo ::= SEQUENCE { * algorithm AlgorithmIdentifier, * subjectPublicKey BIT STRING * } */ $asnSubjectPublicKeyInfo = array('type' => ASN1::TYPE_SEQUENCE, 'children' => array('algorithm' => $asnAlgorithmIdentifier, 'subjectPublicKey' => array('type' => ASN1::TYPE_BIT_STRING))); $decoded = $asn1->decodeBER(base64_decode($publicKey)); $mappedDetails = $asn1->asn1map($decoded[0], $asnSubjectPublicKeyInfo); return isset($mappedDetails['algorithm']['id-ecSigType']) ? $this->getSupportedECDSACurve() === $mappedDetails['algorithm']['id-ecSigType'] : false; }
/** * 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; }
/** * Compute a public key identifier. * * Although key identifiers may be set to any unique value, this function * computes key identifiers from public key according to the two * recommended methods (4.2.1.2 RFC 3280). * Highly polymorphic: try to accept all possible forms of key: * - Key object * - \phpseclib\File\X509 object with public or private key defined * - Certificate or CSR array * - \phpseclib\File\ASN1\Element object * - PEM or DER string * * @param mixed $key optional * @param int $method optional * @access public * @return string binary key identifier */ function computeKeyIdentifier($key = null, $method = 1) { if (is_null($key)) { $key = $this; } switch (true) { case is_string($key): break; case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method); case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method); case !is_object($key): return false; case $key instanceof Element: // Assume the element is a bitstring-packed key. $asn1 = new ASN1(); $decoded = $asn1->decodeBER($key->element); if (empty($decoded)) { return false; } $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING)); if (empty($raw)) { return false; } $raw = base64_decode($raw); // If the key is private, compute identifier from its corresponding public key. $key = new RSA(); if (!$key->loadKey($raw)) { return false; // Not an unencrypted RSA key. } if ($key->getPrivateKey() !== false) { // If private. return $this->computeKeyIdentifier($key, $method); } $key = $raw; // Is a public key. break; case $key instanceof X509: if (isset($key->publicKey)) { return $this->computeKeyIdentifier($key->publicKey, $method); } if (isset($key->privateKey)) { return $this->computeKeyIdentifier($key->privateKey, $method); } if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) { return $this->computeKeyIdentifier($key->currentCert, $method); } return false; default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA). $key = $key->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1); break; } // If in PEM format, convert to binary. $key = $this->_extractBER($key); // Now we have the key string: compute its sha-1 sum. $hash = new Hash('sha1'); $hash = $hash->hash($key); if ($method == 2) { $hash = substr($hash, -8); $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40); } return $hash; }
/** * Decodes and maps the BasicOCSPResponse part of the response. * * @return array */ private function getResponseMapped() { $parser = new Asn1Parser(); $responseBasicDecoded = $parser->decodeBER(base64_decode($this->response)); return $parser->asn1map($responseBasicDecoded[0], $this->asn1->BasicOCSPResponse); }
/** * 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); }
/** * @param $publicKey * * @return array|bool */ protected static function loadPublicKey($publicKey) { $asn1 = new ASN1(); $asnAlgorithmIdentifier = ['type' => ASN1::TYPE_SEQUENCE, 'children' => ['ansi-X9-62' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER], 'id-ecSigType' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER]]]; $asnSubjectPublicKeyInfo = ['type' => ASN1::TYPE_SEQUENCE, 'children' => ['algorithm' => $asnAlgorithmIdentifier, 'subjectPublicKey' => ['type' => ASN1::TYPE_BIT_STRING]]]; $decoded = $asn1->decodeBER(base64_decode($publicKey)); $mappedDetails = $asn1->asn1map($decoded[0], $asnSubjectPublicKeyInfo); if (null === $mappedDetails) { return false; } $details = Base64Url::decode($mappedDetails['subjectPublicKey']); if (!self::isAlgorithmSupported($mappedDetails['algorithm']['id-ecSigType'])) { return false; } if (substr($details, 0, 1) !== "") { return false; } if (substr($details, 1, 1) !== "") { return false; } $X = substr($details, 2, (strlen($details) - 2) / 2); $Y = substr($details, (strlen($details) - 2) / 2 + 2, (strlen($details) - 2) / 2); return ['x' => Base64Url::encode($X), 'y' => Base64Url::encode($Y)]; }
/** * Load a Certificate Revocation List * * @param String $crl * * @access public * @return Mixed */ function loadCRL($crl) { if (is_array($crl) && isset($crl['tbsCertList'])) { $this->currentCert = $crl; unset($this->signatureSubject); return $crl; } $asn1 = new ASN1(); $crl = $this->_extractBER($crl); $orig = $crl; if ($crl === false) { $this->currentCert = false; return false; } $asn1->loadOIDs($this->oids); $decoded = $asn1->decodeBER($crl); if (empty($decoded)) { $this->currentCert = false; return false; } $crl = $asn1->asn1map($decoded[0], $this->CertificateList); if (!isset($crl) || $crl === false) { $this->currentCert = false; return false; } $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1); $rclist =& $this->_subArray($crl, 'tbsCertList/revokedCertificates'); if (is_array($rclist)) { foreach ($rclist as $i => $extension) { $this->_mapInExtensions($rclist, "{$i}/crlEntryExtensions", $asn1); } } $this->currentKeyIdentifier = null; $this->currentCert = $crl; return $crl; }