Overview

Namespaces

  • BSN
    • CryptUser

Classes

  • CryptJSONSource
  • CryptMySQLSource
  • CryptUser
  • SSLKey

Interfaces

  • CryptDataSource
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: /*
  3: * Copyright (C) 2013 Bryan Nielsen - All Rights Reserved
  4: *
  5: * Author: Bryan Nielsen (bnielsen1965@gmail.com)
  6: *
  7: *
  8: * This file is part of cryptUser.
  9: * cryptUser is free software: you can redistribute it and/or modify
 10: * it under the terms of the GNU General Public License as published by
 11: * the Free Software Foundation, either version 3 of the License, or
 12: * (at your option) any later version.
 13: * 
 14: * cryptUser is distributed in the hope that it will be useful,
 15: * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17: * GNU General Public License for more details.
 18: * 
 19: * You should have received a copy of the GNU General Public License
 20: * along with cryptUser.  If not, see <http://www.gnu.org/licenses/>.
 21: */
 22: 
 23: namespace BSN\CryptUser;
 24: use Exception;
 25: 
 26: require_once 'SSLKey.php';
 27: 
 28: /**
 29:  * CryptUser object.
 30:  *
 31:  * @author Bryan Nielsen, bnielsen1965@gmail.com
 32:  * @copyright Copyright 2013, Bryan Nielsen
 33:  */
 34: class CryptUser {
 35:     /**
 36:      * Class constants
 37:      */
 38:     const ACL_ADMIN_FLAG = 1;
 39:     const ACL_ACTIVE_FLAG = 2;
 40:     const ACL_ALL_FLAGS = 3; // OR all flags together
 41:     
 42:     
 43:     /**
 44:      * Class properties
 45:      */
 46:     private $username;
 47:     private $password;
 48:     private $passwordHash; // hashed password
 49:     private $authenticated; // user authenticated flag
 50:     private $primaryKey; // SSLKey instance
 51:     private $sslKey; // PEM formatted private key and certificate
 52:     private $flags; // user flag settings
 53:     private $dataSource;
 54:     
 55:     
 56:     /**
 57:      * Object constructor
 58:      * @param string username The username for this user.
 59:      * @param string $password The password for this user.
 60:      * @param string $dataSource An object to provide the user data.
 61:      */
 62:     function __construct($username, $password, $dataSource = NULL) {
 63:         $this->username = $username;
 64:         $this->password = $password;
 65:         $this->dataSource = $dataSource;
 66:         $this->clearAllACLFlags();
 67:         $this->sslKey = '';
 68: 
 69:         if (!empty($this->dataSource)) $this->loadUser();
 70:     }
 71:     
 72:     
 73:     /**
 74:      * Load this user defined by this object from the data source
 75:      * @return boolean TRUE on success, FALSE on failure
 76:      */
 77:     private function loadUser() {
 78:         // retrieve user's data from the data source
 79:         $userData = $this->dataSource->getUserByName($this->username);
 80:         
 81:         // if user data returned then process
 82:         if ($userData) {
 83:             // use data values for this user
 84:             $this->username = $userData['username'];
 85:             $this->passwordHash = $userData['passwordHash'];
 86:             $this->flags = $userData['flags'];
 87:             $this->sslKey = $userData['sslKey'];
 88: 
 89:             // authenticate user by checking data source password hash with a hash of the provided password
 90:             if ($this->passwordHash == $this->hashPassword($this->password, $this->passwordHash)) {
 91:                 // authentication passed
 92:                 $this->authenticated = TRUE;
 93:                 
 94:                 // set the user's primary SSL key
 95:                 $this->setPrimaryKey(SSLKey::parsePrivateKey($this->sslKey), SSLKey::parseCertificate($this->sslKey));
 96:             }
 97:             else {
 98:                 // authentication failed
 99:                 $this->authenticated = FALSE;
100:             }
101:             
102:             // load successful
103:             return TRUE;
104:         }
105:         
106:         // failed to load the user
107:         return FALSE;
108:     }
109:     
110:     
111:     /**
112:      * Save this user to the data source
113:      * @return boolean TRUE on success, FALSE on failure.
114:      */
115:     public function saveUser() {
116:         return $this->dataSource->saveUser(array(
117:             'username' => $this->username,
118:             'passwordHash' => $this->passwordHash,
119:             'sslKey' => $this->sslKey,
120:             'flags' => $this->flags
121:         ));
122:     }
123:     
124:     
125:     /**
126:      * Complete the steps to establish this as a new user in the data source.
127:      * @return boolean TRUE on success, FALSE on failure
128:      */
129:     public function newUser() {
130:         // if user does not exist then create
131:         if ($this->dataSource->getUserByName($this->username) === FALSE) {
132:             // use the change password function to set the password hash and create a primary key
133:             $this->changePassword($this->password);
134: 
135:             // save the new user
136:             $this->saveUser();
137: 
138:             return TRUE;
139:         }
140:         else {
141:             // user already exists
142:             return FALSE;
143:         }
144:     }
145:     
146:     
147:     /**
148:      * Get the username string for this user.
149:      * @return string The username value.
150:      */
151:     public function getUsername() {
152:         return $this->username;
153:     }
154:     
155:     
156:     /**
157:      * Set the primary SSLKey to use with this user.
158:      * @param string $key Optional PEM encoded private key to use in generating the SSLKey.
159:      * @param string $certificate Optional PEM encoded certificate to use in generating the SSLKey.
160:      * @return boolean TRUE on success, FALSE on failure.
161:      */
162:     public function setPrimaryKey($key = NULL, $certificate = NULL) {
163:         // create primary key
164:         $this->primaryKey = new SSLKey($this->password, $key, $certificate);
165:         
166:         // if the provided key or certificate were empty then use the new values from the new primary key
167:         if (empty($key) || empty($certificate)) $this->sslKey = $this->primaryKey->getPrivateKey() . $this->primaryKey->getCertificate();
168:         
169:         return TRUE;
170:     }
171:     
172:     
173:     /**
174:      * Sets the Access Control Level flags specified in the flagMask variable.
175:      * @param integer $flagMask Integer containing the binary flags to turn on.
176:      * @return boolean TRUE on success, FALSE on failure.
177:      */
178:     public function setACLFlags($flagMask) {
179:         $this->flags = $this->flags | $flagMask;
180:         return TRUE;
181:     }
182: 
183:     
184:     /**
185:      * Clears the Access Control Level flags specified in flagMask.
186:      * @param integer $flagMask Integer containing the binary flags to turn off.
187:      * @return boolean TRUE on success, FALSE on failure.
188:      */
189:     public function clearACLFlags($flagMask) {
190:         $this->flags = $this->flags & ~$flagMask;
191:         
192:         return TRUE;
193:     }
194:     
195:     
196:     /**
197:      * Set all possible ACL flags on this user.
198:      */
199:     public function setAllACLFlags() {
200:         $this->flags = CryptUser::ACL_ALL_FLAGS;
201:     }
202:     
203:     
204:     /**
205:      * Clear all possible ACL flags on this user.
206:      */
207:     public function clearAllACLFlags() {
208:         $this->flags = 0;
209:     }
210: 
211:     
212:     /**
213:      * Test if the specified flags are set as specified in the flagMask argument.
214:      * @param integer $flagmask Integer containing the binary flags to test.
215:      * @return boolean TRUE if the specified flags are set, FALSE if any of the flags are not set.
216:      */
217:     public function isACLFlagSet($flagMask) {
218:         return ($flagMask & $this->flags ? TRUE : FALSE);
219:     }
220:     
221:     
222:     /**
223:      * Determine if this user is an admin
224:      * @return boolean TRUE if admin, FALSE if not.
225:      */
226:     public function isAdmin() {
227:         // check flags against admin mask
228:         return $this->isACLFlagSet(CryptUser::ACL_ADMIN_FLAG);
229:     }
230:     
231:     
232:     /**
233:      * Determine if this user account is active.
234:      * @return boolean TRUE if active, FALSE if not.
235:      */
236:     public function isActive() {
237:         // check flags against active mask
238:         return $this->isACLFlagSet(CryptUser::ACL_ACTIVE_FLAG);
239:     }
240:     
241:     
242:     /**
243:      * Check if this user is authenticated.
244:      * @return boolean TRUE if authenticated, FALSE if not.
245:      */
246:     public function isAuthenticated() {
247:         return ($this->authenticated ? TRUE : FALSE);
248:     }
249:     
250:     
251:     /**
252:      * Change the user's password and the SSL Key elements that rely on the password.
253:      * @param string $password The new password to use.
254:      * @param string $callback Optional callback function used by the application for
255:      * post password change maintenance like re-encryption of data. The callback must
256:      * accept two CryptUser objects, the first object is the CryptUser with the new
257:      * password and encryption key, the second object is the Cryptuser with the old
258:      * password and encryption key.
259:      * @return boolean TRUE on success, FALSE on failure.
260:      */
261:     public function changePassword($password, $callback = NULL) {
262:         // if a callback is provided then prepare to use callback
263:         if (!empty($callback)) {
264:             // if this user is not authenticated then decryption will not be possible so we fail
265:             if (!$this->isAuthenticated()) return FALSE;
266:             
267:             // create a clone of this user to be sent to callback
268:             $oldCryptUser = clone $this;
269:         }
270:         
271:         $this->password = $password;
272:         $this->passwordHash = $this->hashPassword($password);
273:         
274:         // set a new primary key
275:         $this->setPrimaryKey();
276:         
277:         // set the sslKey value using the new primary key
278:         $this->sslKey = $this->primaryKey->getPrivateKey() . $this->primaryKey->getCertificate();
279:         
280:         // if a callback is provided then call now with the old and this new user
281:         if (!empty($callback)) call_user_func($callback, $oldCryptUser, $this);
282:         
283:         // password change complete
284:         return TRUE;
285:     }
286:     
287:     
288:     
289:     
290:     
291:     /**
292:      * Encrypt a package using the primary SSL key pair.
293:      * @param mixed $package The package to encrypt.
294:      * @return array An array containing the envelope and encrypted package or NULL if an error occurs.
295:      */
296:     public function encryptPackage($package) {
297:         if ($this->primaryKey) {
298:             // encrypt the package
299:             $encryptedPackage = $this->primaryKey->encryptPackage($package);
300:             
301:             if ($encryptedPackage) {
302:                 // use base 64 encoding to make strings safe for various storage mechanisms
303:                 $encryptedPackage['envelope'] = base64_encode($encryptedPackage['envelope']);
304:                 $encryptedPackage['package'] = base64_encode($encryptedPackage['package']);
305:                 
306:                 return $encryptedPackage;
307:             }
308:         }
309:         
310:         return FALSE;
311:     }
312:     
313:     
314:     /**
315:      * Decrypt the sealed package using the primary SSL key pair.
316:      * @param mixed $package An associative array containing 'package' and 'envelope'.
317:      * @return mixed The decrypted package or FALSE if there was an error.
318:      */
319:     public function decryptPackage($package) {
320:         if ($this->primaryKey) {
321:             return $this->primaryKey->decryptPackage(base64_decode($package['package']), base64_decode($package['envelope']));
322:         }
323:         
324:         return FALSE;
325:     }
326: 
327: 
328:     
329:     /**
330:      * Get the data source used for this user.
331:      * @return object The data source object for this user.
332:      */
333:     public function getDatasource() {
334:         return $this->dataSource;
335:     }
336:     
337:     
338:     /**
339:      * Hash the provided password string.
340:      * @param string $password The plain text user password.
341:      * @param string $salt Optional salt to use with crypt. The salt must be provided
342:      * when performing a password hash check to make sure the hash values come out the
343:      * same. If no salt is provided then the best salt for this server will be generated.
344:      * @return string The hashed password.
345:      */
346:     public static function hashPassword($password, $salt = NULL) {
347:         // if password empty then return empty
348:         if( empty($password) ) return '';
349: 
350:         if( is_null($salt) ) $salt = cryptUser::bestSalt();
351: 
352:         return crypt($password, $salt);
353:     }
354:     
355:     
356:     /**
357:      * Determine the best salt to use for the crypt function on this server.
358:      * @return string The salt to be used with crypt.
359:      */
360:     public static function bestSalt() {
361:         if( defined('CRYPT_SHA512') && CRYPT_SHA512 == 1 ) return '$6$' . SSLKey::makePhrase(16) . '$';
362:         if( defined('CRYPT_SHA256') && CRYPT_SHA256 == 1 ) return '$5$' . SSLKey::makePhrase(16) . '$';
363:         if( defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH == 1 ) return '$2a$07$' . base64_encode(SSLKey::makePhrase(22)) . '$';
364:         if( defined('CRYPT_MD5') && CRYPT_MD5 == 1 ) return '$1$' . SSLKey::makePhrase(12) . '$';
365:         if( defined('CRYPT_EXT_DES') && CRYPT_EXT_DES == 1 ) return '_' . SSLKey::makePhrase(8);
366:         if( defined('CRYPT_STD_DES') && CRYPT_STD_DES == 1 ) return SSLKey::makePhrase(2);
367:         return '';
368:     }
369: }
370: 
371: 
cryptUser API documentation generated by ApiGen 2.8.0