X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=plugins%2FOStatus%2Fclasses%2FMagicsig.php;h=31d061e6a0148ab90522db25ce3c5f7857440e73;hb=c0bb1a57984266024e8e5a968c0f3a3b54befff6;hp=5a46aeeb6e5f79b8b6ce59fab283685b51521d42;hpb=45e8819c1b9cc618e9b2d6678b0ff14c653a09d3;p=quix0rs-gnu-social.git diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 5a46aeeb6e..31d061e6a0 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -27,31 +27,80 @@ * @link http://status.net/ */ +if (!defined('STATUSNET')) { + exit(1); +} + require_once 'Crypt/RSA.php'; class Magicsig extends Memcached_DataObject { - const PUBLICKEYREL = 'magic-public-key'; - + public $__table = 'magicsig'; + /** + * Key to user.id/profile.id for the local user whose key we're storing. + * + * @var int + */ public $user_id; + + /** + * Flattened string representation of the key pair; callers should + * usually use $this->publicKey and $this->privateKey directly, + * which hold live Crypt_RSA key objects. + * + * @var string + */ public $keypair; + + /** + * Crypto algorithm used for this key; currently only RSA-SHA256 is supported. + * + * @var string + */ public $alg; - - private $_rsa; + + /** + * Public RSA key; gets serialized in/out via $this->keypair string. + * + * @var Crypt_RSA + */ + public $publicKey; + + /** + * PrivateRSA key; gets serialized in/out via $this->keypair string. + * + * @var Crypt_RSA + */ + public $privateKey; public function __construct($alg = 'RSA-SHA256') { $this->alg = $alg; } - + + /** + * Fetch a Magicsig object from the cache or database on a field match. + * + * @param string $k + * @param mixed $v + * @return Magicsig + */ public /*static*/ function staticGet($k, $v=null) { $obj = parent::staticGet(__CLASS__, $k, $v); if (!empty($obj)) { - return Magicsig::fromString($obj->keypair); + $obj = Magicsig::fromString($obj->keypair); + + // Double check keys: Crypt_RSA did not + // consistently generate good keypairs. + // We've also moved to 1024 bit keys. + if (strlen($obj->publicKey->modulus->toBits()) != 1024) { + $obj->delete(); + return false; + } } return $obj; @@ -70,14 +119,13 @@ class Magicsig extends Memcached_DataObject static function schemaDef() { return array(new ColumnDef('user_id', 'integer', - null, true, 'PRI'), - new ColumnDef('keypair', 'varchar', - 255, false), + null, false, 'PRI'), + new ColumnDef('keypair', 'text', + false, false), new ColumnDef('alg', 'varchar', 64, false)); } - function keys() { return array_keys($this->keyTypes()); @@ -92,6 +140,14 @@ class Magicsig extends Memcached_DataObject return array(false, false, false); } + /** + * Save this keypair into the database. + * + * Overloads default insert behavior to encode the live key objects + * as a flat string for storage. + * + * @return mixed + */ function insert() { $this->keypair = $this->toString(); @@ -99,43 +155,61 @@ class Magicsig extends Memcached_DataObject return parent::insert(); } - public function generate($user_id, $key_length = 512) + /** + * Generate a new keypair for a local user and store in the database. + * + * Warning: this can be very slow on systems without the GMP module. + * Runtimes of 20-30 seconds are not unheard-of. + * + * @param int $user_id id of local user we're creating a key for + */ + public function generate($user_id) { - PEAR::pushErrorHandling(PEAR_ERROR_RETURN); + $rsa = new Crypt_RSA(); - $keypair = new Crypt_RSA_KeyPair($key_length); - $params['public_key'] = $keypair->getPublicKey(); - $params['private_key'] = $keypair->getPrivateKey(); + $keypair = $rsa->createKey(); - $this->_rsa = new Crypt_RSA($params); - PEAR::popErrorHandling(); + $rsa->loadKey($keypair['privatekey']); + + $this->privateKey = new Crypt_RSA(); + $this->privateKey->loadKey($keypair['privatekey']); + + $this->publicKey = new Crypt_RSA(); + $this->publicKey->loadKey($keypair['publickey']); $this->user_id = $user_id; $this->insert(); } - + /** + * Encode the keypair or public key as a string. + * + * @param boolean $full_pair set to false to leave out the private key. + * @return string + */ public function toString($full_pair = true) { - $public_key = $this->_rsa->_public_key; - $private_key = $this->_rsa->_private_key; - - $mod = base64_url_encode($public_key->getModulus()); - $exp = base64_url_encode($public_key->getExponent()); + $mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes()); + $exp = Magicsig::base64_url_encode($this->publicKey->exponent->toBytes()); $private_exp = ''; - if ($full_pair && $private_key->getExponent()) { - $private_exp = '.' . base64_url_encode($private_key->getExponent()); + if ($full_pair && $this->privateKey->exponent->toBytes()) { + $private_exp = '.' . Magicsig::base64_url_encode($this->privateKey->exponent->toBytes()); } - return 'RSA.' . $mod . '.' . $exp . $private_exp; + return 'RSA.' . $mod . '.' . $exp . $private_exp; } - + + /** + * Decode a string representation of an RSA public key or keypair + * as a Magicsig object which can be used to sign or verify. + * + * @param string $text + * @return Magicsig + */ public static function fromString($text) { - PEAR::pushErrorHandling(PEAR_ERROR_RETURN); - $magic_sig = new Magicsig(); - + // remove whitespace $text = preg_replace('/\s+/', '', $text); @@ -143,91 +217,118 @@ class Magicsig extends Memcached_DataObject if (!preg_match('/RSA\.([^\.]+)\.([^\.]+)(.([^\.]+))?/', $text, $matches)) { return false; } - - $mod = base64_url_decode($matches[1]); - $exp = base64_url_decode($matches[2]); + + $mod = $matches[1]; + $exp = $matches[2]; if (!empty($matches[4])) { - $private_exp = base64_url_decode($matches[4]); + $private_exp = $matches[4]; } else { $private_exp = false; } - $params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public'); - if ($params['public_key']->isError()) { - $error = $params['public_key']->getLastError(); - common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage()); - return false; - } + $magic_sig->loadKey($mod, $exp, 'public'); if ($private_exp) { - $params['private_key'] = new Crypt_RSA_KEY($mod, $private_exp, 'private'); - if ($params['private_key']->isError()) { - $error = $params['private_key']->getLastError(); - common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage()); - return false; - } + $magic_sig->loadKey($mod, $private_exp, 'private'); } - $magic_sig->_rsa = new Crypt_RSA($params); - PEAR::popErrorHandling(); - return $magic_sig; } + /** + * Fill out $this->privateKey or $this->publicKey with a Crypt_RSA object + * representing the give key (as mod/exponent pair). + * + * @param string $mod base64-encoded + * @param string $exp base64-encoded exponent + * @param string $type one of 'public' or 'private' + */ + public function loadKey($mod, $exp, $type = 'public') + { + common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")"); + + $rsa = new Crypt_RSA(); + $rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1; + $rsa->setHash('sha256'); + $rsa->modulus = new Math_BigInteger(Magicsig::base64_url_decode($mod), 256); + $rsa->k = strlen($rsa->modulus->toBytes()); + $rsa->exponent = new Math_BigInteger(Magicsig::base64_url_decode($exp), 256); + + if ($type == 'private') { + $this->privateKey = $rsa; + } else { + $this->publicKey = $rsa; + } + } + + /** + * Returns the name of the crypto algorithm used for this key. + * + * @return string + */ public function getName() { return $this->alg; } + /** + * Returns the name of a hash function to use for signing with this key. + * + * @return string + * @fixme is this used? doesn't seem to be called by name. + */ public function getHash() { switch ($this->alg) { case 'RSA-SHA256': - return 'magicsig_sha256'; + return 'sha256'; } - } - + + /** + * Generate base64-encoded signature for the given byte string + * using our private key. + * + * @param string $bytes as raw byte string + * @return string base64-encoded signature + */ public function sign($bytes) { - $hash = $this->getHash(); - $sig = $this->_rsa->createSign($bytes, null, $hash); - if ($this->_rsa->isError()) { - $error = $this->_rsa->getLastError(); - common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage()); - return false; - } - - return $sig; + $sig = $this->privateKey->sign($bytes); + return Magicsig::base64_url_encode($sig); } + /** + * + * @param string $signed_bytes as raw byte string + * @param string $signature as base64 + * @return boolean + */ public function verify($signed_bytes, $signature) { - $hash = $this->getHash(); - $result = $this->_rsa->validateSign($signed_bytes, $signature, null, $hash); - if ($this->_rsa->isError()) { - $error = $this->keypair->getLastError(); - common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage()); - return false; - } - return $result; + $signature = Magicsig::base64_url_decode($signature); + return $this->publicKey->verify($signed_bytes, $signature); } - -} -// Define a sha256 function for hashing -// (Crypt_RSA should really be updated to use hash() ) -function magicsig_sha256($bytes) -{ - return hash('sha256', $bytes); -} + /** + * URL-encoding-friendly base64 variant encoding. + * + * @param string $input + * @return string + */ + public static function base64_url_encode($input) + { + return strtr(base64_encode($input), '+/', '-_'); + } -function base64_url_encode($input) -{ - return strtr(base64_encode($input), '+/', '-_'); + /** + * URL-encoding-friendly base64 variant decoding. + * + * @param string $input + * @return string + */ + public static function base64_url_decode($input) + { + return base64_decode(strtr($input, '-_', '+/')); + } } - -function base64_url_decode($input) -{ - return base64_decode(strtr($input, '-_', '+/')); -} \ No newline at end of file