688c0f8cffdc31b5fb71b4f2612d6659bfb39e5d
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
20 * The length of the randomly-generated, per-session signing key.
22 * Expressed as number of bytes. (Ex: 128 bits = 16 bytes)
26 const PRIVATE_KEY_LENGTH
= 16;
30 * @see hash_hmac_algos()
32 const HASH_ALGO
= 'sha256';
35 * The length of a generated signature/digest (expressed in hex digits).
38 const HASH_LENGTH
= 64;
40 public static $_key = NULL;
42 public static $_sessionID = NULL;
45 * Generate a private key per session and store in session.
48 * private key for this session
50 public static function privateKey() {
52 $session = CRM_Core_Session
::singleton();
53 self
::$_key = $session->get('qfPrivateKey');
55 self
::$_key = base64_encode(random_bytes(self
::PRIVATE_KEY_LENGTH
));
56 $session->set('qfPrivateKey', self
::$_key);
63 * @return mixed|null|string
65 public static function sessionID() {
66 if (!self
::$_sessionID) {
67 $session = CRM_Core_Session
::singleton();
68 self
::$_sessionID = $session->get('qfSessionID');
69 if (!self
::$_sessionID) {
70 self
::$_sessionID = session_id();
71 $session->set('qfSessionID', self
::$_sessionID);
74 return self
::$_sessionID;
78 * Generate a form key based on form name, the current user session
79 * and a private key. Modelled after drupal's form API
82 * @param bool $addSequence
83 * Should we add a unique sequence number to the end of the key.
88 public static function get($name, $addSequence = FALSE) {
89 $key = self
::sign($name);
92 // now generate a random number between 1 and 100K and add it to the key
93 // so that we can have forms in mutiple tabs etc
94 $key = $key . '_' . mt_rand(1, 10000);
100 * Validate a form key based on the form name.
103 * @param string $name
104 * @param bool $addSequence
107 * if valid, else null
109 public static function validate($key, $name, $addSequence = FALSE) {
110 if (!is_string($key)) {
115 list($k, $t) = explode('_', $key);
116 if ($t < 1 ||
$t > 10000) {
124 if (!hash_equals($k, self
::sign($name))) {
131 * The original version of this function, added circa 2010 and untouched
132 * since then, seemed intended to check for a 32-digit hex string followed
133 * optionally by an underscore and 4-digit number. But it had a bug where
134 * the optional part was never checked ever. So have decided to remove that
135 * second check to keep it simple since it seems like pseudo-security.
140 * TRUE if the signature ($key) is well-formed.
142 public static function valid($key) {
143 // ensure that hash is a hex number (of expected length)
144 return preg_match('#[0-9a-f]{' . self
::HASH_LENGTH
. '}#i', $key) ?
TRUE : FALSE;
148 * @param string $name
149 * The name of the form
151 * A signed digest of $name, computed with the per-session private key
153 private static function sign($name) {
154 $privateKey = self
::privateKey();
155 $sessionID = self
::sessionID();
157 if (strpos($sessionID, $delim) !== FALSE ||
strpos($name, $delim) !== FALSE) {
158 throw new \
RuntimeException("Failed to generate signature. Malformed session-id or form-name.");
160 // Note: Unsure why $sessionID is included, but it's always been there, and it doesn't seem harmful.
161 return hash_hmac(self
::HASH_ALGO
, $sessionID . $delim . $name, $privateKey);