. * * @package StatusNet * @author James Walker * @copyright 2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ require_once 'Crypt/RSA.php'; class Magicsig extends Memcached_DataObject { const PUBLICKEYREL = 'magic-public-key'; public $__table = 'magicsig'; public $user_id; public $keypair; public $alg; private $_rsa; public function __construct($alg = 'RSA-SHA256') { $this->alg = $alg; } public /*static*/ function staticGet($k, $v=null) { $obj = parent::staticGet(__CLASS__, $k, $v); if (!empty($obj)) { return Magicsig::fromString($obj->keypair); } return $obj; } function table() { return array( 'user_id' => DB_DATAOBJECT_INT, 'keypair' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, 'alg' => DB_DATAOBJECT_STR ); } static function schemaDef() { return array(new ColumnDef('user_id', 'integer', null, true, 'PRI'), new ColumnDef('keypair', 'varchar', 255, false), new ColumnDef('alg', 'varchar', 64, false)); } function keys() { return array_keys($this->keyTypes()); } function keyTypes() { return array('user_id' => 'K'); } function sequenceKey() { return array(false, false, false); } function insert() { $this->keypair = $this->toString(); return parent::insert(); } public function generate($user_id, $key_length = 512) { PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $keypair = new Crypt_RSA_KeyPair($key_length); $params['public_key'] = $keypair->getPublicKey(); $params['private_key'] = $keypair->getPrivateKey(); $this->_rsa = new Crypt_RSA($params); PEAR::popErrorHandling(); $this->user_id = $user_id; $this->insert(); } 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()); $private_exp = ''; if ($full_pair && $private_key->getExponent()) { $private_exp = '.' . base64_url_encode($private_key->getExponent()); } return 'RSA.' . $mod . '.' . $exp . $private_exp; } public static function fromString($text) { PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $magic_sig = new Magicsig(); // remove whitespace $text = preg_replace('/\s+/', '', $text); // parse components if (!preg_match('/RSA\.([^\.]+)\.([^\.]+)(.([^\.]+))?/', $text, $matches)) { return false; } $mod = base64_url_decode($matches[1]); $exp = base64_url_decode($matches[2]); if (!empty($matches[4])) { $private_exp = base64_url_decode($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; } 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->_rsa = new Crypt_RSA($params); PEAR::popErrorHandling(); return $magic_sig; } public function getName() { return $this->alg; } public function getHash() { switch ($this->alg) { case 'RSA-SHA256': return 'magicsig_sha256'; } } 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; } 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; } } // Define a sha256 function for hashing // (Crypt_RSA should really be updated to use hash() ) function magicsig_sha256($bytes) { return hash('sha256', $bytes); } function base64_url_encode($input) { return strtr(base64_encode($input), '+/', '-_'); } function base64_url_decode($input) { return base64_decode(strtr($input, '-_', '+/')); }