4 # FILE: SPT--SPTUser.php 6 # Part of the Collection Workflow Integration System (CWIS) 7 # Copyright 2004-2013 Edward Almasy and Internet Scout Research Group 8 # http://scout.wisc.edu/cwis/ 14 # ---- PUBLIC INTERFACE -------------------------------------------------- 29 # regenerate keys every day (24 * 60 * 60 = 86,400 seconds) 30 $KeyRegenInterval = 86400;
32 # oldest key we want to keep 33 $MaxKeyAge = 3 * $KeyRegenInterval;
35 # if the page cache was enabled, be sure to keep keys that were 36 # current (i.e. not expired) as of the oldest cache entry 37 if ($GLOBALS[
"AF"]->PageCacheEnabled())
39 $CacheInfo = $GLOBALS[
"AF"]->GetPageCacheInfo();
40 if ($CacheInfo[
"NumberOfEntries"]>0)
42 $MaxKeyAge += (time()-$CacheInfo[
"OldestTimestamp"]);
46 # NOTE: One can not simply subtract two TIMESTAMPs and expect 47 # a sane result from mysql. Using a TIMESTAMP in numeric 48 # context converts it to an int, but in YYYYMMDDHHMMSS format 49 # rather than as a UNIX time. Hence the use of 50 # TIMESTAMPDIFF() below. 52 # clear expired keys and replay protection tokens 53 $DB->Query(
"DELETE FROM UsedLoginTokens WHERE " 54 .
"TIMESTAMPDIFF(SECOND, KeyCTime, NOW()) > ".$MaxKeyAge);
56 $DB->Query(
"LOCK TABLES LoginKeys WRITE");
57 $DB->Query(
"DELETE FROM LoginKeys WHERE " 58 .
"TIMESTAMPDIFF(SECOND, CreationTime, NOW()) > ".$MaxKeyAge);
60 # get the most recently generated key 61 $DB->Query(
"SELECT TIMESTAMPDIFF(SECOND, CreationTime, NOW()) as Age," 62 .
"KeyPair FROM LoginKeys " 63 .
"ORDER BY Age ASC LIMIT 1");
64 $Row =
$DB->FetchRow();
66 # if there is no key in the database, or the key is too old 67 if ( ($Row===FALSE) || ($Row[
"Age"]>= $KeyRegenInterval) )
69 # generate a new OpenSSL format keypair 70 $KeyPair = openssl_pkey_new(
72 'private_key_bits' => 512, # make
this a Sysadmin pref later?
73 'private_key_type' => OPENSSL_KEYTYPE_RSA ));
75 # serialize it for storage 76 openssl_pkey_export($KeyPair, $KeyPairDBFormat);
78 # stick it into the database 79 $DB->Query(
"INSERT INTO LoginKeys " 80 .
"(KeyPair, CreationTime) VALUES (" 81 .
"\"".addslashes($KeyPairDBFormat).
"\"," 86 # if we do have a current key in the database, 87 # convert it to openssl format for usage 88 $KeyPair = openssl_pkey_get_private( $Row[
"KeyPair"] );
90 $DB->Query(
"UNLOCK TABLES");
104 # Export the keypair as an ASCII signing request (which contains the data we want) 105 openssl_csr_export(openssl_csr_new(array(), $KeyPair), $Export, FALSE);
112 '/Modulus \([0-9]+ bit\):(.*)Exponent: [0-9]+ \(0x([0-9a-f]+)\)/ms',
113 '/Public-Key: \([0-9]+ bit\).*Modulus:(.*)Exponent: [0-9]+ \(0x([0-9a-f]+)\)/ms',
117 foreach ($Patterns as $Pattern)
119 if (preg_match($Pattern, $Export, $Matches))
121 $Modulus = $Matches[1];
122 $Exponent = $Matches[2];
127 # Clean newlines and whitespace out of the modulus 128 $Modulus = preg_replace(
"/[^0-9a-f]/",
"", $Modulus);
130 # Return key material 131 return array(
"Modulus" => $Modulus,
"Exponent" => $Exponent );
144 $DB->Query(
"SELECT CreationTime, KeyPair FROM LoginKeys");
146 # start off assuming that nothing will decrypt properly 149 # remove the base64 encoding sent on the cyphertext 150 $Cyphertext = base64_decode($EncryptedPassword);
152 # extract recently used login keys from the database 153 $Keys =
$DB->FetchRows();
155 # iterate through them on the cyphertext, trying to decrypt it with 156 # each key. by default, 48 hr worth of keys are kept, and fresh keys 157 # are generated every 24 hr. 158 foreach ($Keys as $Key)
160 $KeyCTime = $Key[
"CreationTime"];
161 $DBFormatKeyPair = $Key[
"KeyPair"];
162 # extract the OpenSSL format private key from the database key format 163 $Keypair = openssl_pkey_get_private($DBFormatKeyPair);
165 # attempt to decrypt the cyphertext 166 if (openssl_private_decrypt($Cyphertext, $Cleartext, $Keypair))
168 # on success, extract the password portion and the random bytes 169 $Data = explode(
"\t", $Cleartext);
171 if (count($Data) == 2)
173 list($Password, $Token) = $Data;
175 # check to see if we've seen these particular bytes 176 # before for this user and this key 177 $DB->Query(
"SELECT * FROM UsedLoginTokens WHERE" 178 .
" Token=\"".addslashes($Token).
"\" AND" 179 .
" KeyCTime=\"".$KeyCTime.
"\" AND" 180 .
" UserName=\"".$UserName.
"\"");
182 if (
$DB->NumRowsSelected() > 0)
184 # if so, this is a replay attack; fail the login 189 # otherwise, record these bytes as seen, and attempt to login 190 # with the plaintext password 191 $DB->Query(
"INSERT INTO UsedLoginTokens" 192 .
" (Token, KeyCTime, UserName) VALUES (" 193 .
"\"".addslashes($Token).
"\"," 194 .
"\"".addslashes($KeyCTime).
"\"," 195 .
"\"".addslashes($UserName).
"\")");
198 # no need to try other keys when we've found one that
SQL database abstraction object with smart query caching.
static ExtractPubKeyParameters($KeyPair)
Extract the modulus and exponent of the public key from an OpenSSL format keypair to send in login fo...
static DecryptPassword($UserName, $EncryptedPassword)
Decrypt an encrypted password.
static GetCryptKey()
Get/generate a cryptographic keypair for user login.
CWIS-specific user class.