]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OStatus/classes/Magicsig.php
Updating all Memcached_DataObject extended classes to Managed_DataObject
[quix0rs-gnu-social.git] / plugins / OStatus / classes / Magicsig.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2010, StatusNet, Inc.
5  *
6  * A sample module to show best practices for StatusNet plugins
7  *
8  * PHP version 5
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU Affero General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Affero General Public License for more details.
19  *
20  * You should have received a copy of the GNU Affero General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  * @package   StatusNet
24  * @author    James Walker <james@status.net>
25  * @copyright 2010 StatusNet, Inc.
26  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
27  * @link      http://status.net/
28  */
29
30 if (!defined('STATUSNET')) {
31     exit(1);
32 }
33
34 require_once 'Crypt/RSA.php';
35
36 class Magicsig extends Managed_DataObject
37 {
38     const PUBLICKEYREL = 'magic-public-key';
39
40     public $__table = 'magicsig';
41
42     /**
43      * Key to user.id/profile.id for the local user whose key we're storing.
44      *
45      * @var int
46      */
47     public $user_id;
48
49     /**
50      * Flattened string representation of the key pair; callers should
51      * usually use $this->publicKey and $this->privateKey directly,
52      * which hold live Crypt_RSA key objects.
53      *
54      * @var string
55      */
56     public $keypair;
57
58     /**
59      * Crypto algorithm used for this key; currently only RSA-SHA256 is supported.
60      *
61      * @var string
62      */
63     public $alg;
64
65     /**
66      * Public RSA key; gets serialized in/out via $this->keypair string.
67      *
68      * @var Crypt_RSA
69      */
70     public $publicKey;
71
72     /**
73      * PrivateRSA key; gets serialized in/out via $this->keypair string.
74      *
75      * @var Crypt_RSA
76      */
77     public $privateKey;
78
79     public function __construct($alg = 'RSA-SHA256')
80     {
81         $this->alg = $alg;
82     }
83
84     function table()
85     {
86         return array(
87             'user_id' => DB_DATAOBJECT_INT,
88             'keypair' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
89             'alg'     => DB_DATAOBJECT_STR
90         );
91     }
92
93     static function schemaDef()
94     {
95         return array(new ColumnDef('user_id', 'integer',
96                                    null, false, 'PRI'),
97                      new ColumnDef('keypair', 'text',
98                                    false, false),
99                      new ColumnDef('alg', 'varchar',
100                                    64, false));
101     }
102
103     function keys()
104     {
105         return array_keys($this->keyTypes());
106     }
107
108     function keyTypes()
109     {
110         return array('user_id' => 'K');
111     }
112
113     function sequenceKey() {
114         return array(false, false, false);
115     }
116
117     /**
118      * Save this keypair into the database.
119      *
120      * Overloads default insert behavior to encode the live key objects
121      * as a flat string for storage.
122      *
123      * @return mixed
124      */
125     function insert()
126     {
127         $this->keypair = $this->toString();
128
129         return parent::insert();
130     }
131
132     /**
133      * Generate a new keypair for a local user and store in the database.
134      *
135      * Warning: this can be very slow on systems without the GMP module.
136      * Runtimes of 20-30 seconds are not unheard-of.
137      *
138      * @param int $user_id id of local user we're creating a key for
139      */
140     public function generate($user_id)
141     {
142         $rsa = new Crypt_RSA();
143
144         $keypair = $rsa->createKey();
145
146         $rsa->loadKey($keypair['privatekey']);
147
148         $this->privateKey = new Crypt_RSA();
149         $this->privateKey->loadKey($keypair['privatekey']);
150
151         $this->publicKey = new Crypt_RSA();
152         $this->publicKey->loadKey($keypair['publickey']);
153
154         $this->user_id = $user_id;
155         $this->insert();
156     }
157
158     /**
159      * Encode the keypair or public key as a string.
160      *
161      * @param boolean $full_pair set to false to leave out the private key.
162      * @return string
163      */
164     public function toString($full_pair = true)
165     {
166         $mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes());
167         $exp = Magicsig::base64_url_encode($this->publicKey->exponent->toBytes());
168         $private_exp = '';
169         if ($full_pair && $this->privateKey->exponent->toBytes()) {
170             $private_exp = '.' . Magicsig::base64_url_encode($this->privateKey->exponent->toBytes());
171         }
172
173         return 'RSA.' . $mod . '.' . $exp . $private_exp;
174     }
175
176     /**
177      * Decode a string representation of an RSA public key or keypair
178      * as a Magicsig object which can be used to sign or verify.
179      *
180      * @param string $text
181      * @return Magicsig
182      */
183     public static function fromString($text)
184     {
185         $magic_sig = new Magicsig();
186
187         // remove whitespace
188         $text = preg_replace('/\s+/', '', $text);
189
190         // parse components
191         if (!preg_match('/RSA\.([^\.]+)\.([^\.]+)(.([^\.]+))?/', $text, $matches)) {
192             return false;
193         }
194
195         $mod = $matches[1];
196         $exp = $matches[2];
197         if (!empty($matches[4])) {
198             $private_exp = $matches[4];
199         } else {
200             $private_exp = false;
201         }
202
203         $magic_sig->loadKey($mod, $exp, 'public');
204         if ($private_exp) {
205             $magic_sig->loadKey($mod, $private_exp, 'private');
206         }
207
208         return $magic_sig;
209     }
210
211     /**
212      * Fill out $this->privateKey or $this->publicKey with a Crypt_RSA object
213      * representing the give key (as mod/exponent pair).
214      *
215      * @param string $mod base64-encoded
216      * @param string $exp base64-encoded exponent
217      * @param string $type one of 'public' or 'private'
218      */
219     public function loadKey($mod, $exp, $type = 'public')
220     {
221         common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")");
222
223         $rsa = new Crypt_RSA();
224         $rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1;
225         $rsa->setHash('sha256');
226         $rsa->modulus = new Math_BigInteger(Magicsig::base64_url_decode($mod), 256);
227         $rsa->k = strlen($rsa->modulus->toBytes());
228         $rsa->exponent = new Math_BigInteger(Magicsig::base64_url_decode($exp), 256);
229
230         if ($type == 'private') {
231             $this->privateKey = $rsa;
232         } else {
233             $this->publicKey = $rsa;
234         }
235     }
236
237     /**
238      * Returns the name of the crypto algorithm used for this key.
239      *
240      * @return string
241      */
242     public function getName()
243     {
244         return $this->alg;
245     }
246
247     /**
248      * Returns the name of a hash function to use for signing with this key.
249      *
250      * @return string
251      * @fixme is this used? doesn't seem to be called by name.
252      */
253     public function getHash()
254     {
255         switch ($this->alg) {
256
257         case 'RSA-SHA256':
258             return 'sha256';
259         }
260     }
261
262     /**
263      * Generate base64-encoded signature for the given byte string
264      * using our private key.
265      *
266      * @param string $bytes as raw byte string
267      * @return string base64-encoded signature
268      */
269     public function sign($bytes)
270     {
271         $sig = $this->privateKey->sign($bytes);
272         return Magicsig::base64_url_encode($sig);
273     }
274
275     /**
276      *
277      * @param string $signed_bytes as raw byte string
278      * @param string $signature as base64
279      * @return boolean
280      */
281     public function verify($signed_bytes, $signature)
282     {
283         $signature = Magicsig::base64_url_decode($signature);
284         return $this->publicKey->verify($signed_bytes, $signature);
285     }
286
287     /**
288      * URL-encoding-friendly base64 variant encoding.
289      *
290      * @param string $input
291      * @return string
292      */
293     public static function base64_url_encode($input)
294     {
295         return strtr(base64_encode($input), '+/', '-_');
296     }
297
298     /**
299      * URL-encoding-friendly base64 variant decoding.
300      *
301      * @param string $input
302      * @return string
303      */
304     public static function base64_url_decode($input)
305     {
306         return base64_decode(strtr($input, '-_', '+/'));
307     }
308 }