public function write($ary, $now = 0) { // for this sample, we'll strip out meta data unset($ary[SecureString1::PREFIX_VERSION]); unset($ary[SecureString1::PREFIX_CREATED]); unset($ary[SecureString1::PREFIX_KEYID]); unset($ary[SecureString1::PREFIX_SALT]); unset($ary[SecureString1::PREFIX_MAC]); $payload = http_build_query($ary); // this is a "ok" 24-bit salt represent as 6 characters // (the substr is to remove padding chars which aren't useful here) // // A BETTER solution is use the DevUrandom // $salt = substr(SecureString1::b64_urlencode(pack("L", mt_rand())), 0, 6); $str = SecureString1::create($payload, $salt, $this->keys, $this->kid, $now); // grab cookie or URL from environment // since this is sample we'll just pull from global env $GLOBALS['userdata'] = $str; }
public function _write($now) { $this->dirty = FALSE; $ary = $this->payload; // we'll strip out meta data unset($ary[SecureString1::PREFIX_VERSION]); unset($ary[SecureString1::PREFIX_CREATED]); unset($ary[SecureString1::PREFIX_KEYID]); unset($ary[SecureString1::PREFIX_SALT]); unset($ary[SecureString1::PREFIX_MAC]); $payload = http_build_query($ary); $salt = SecureString1::b64_urlencode($this->random->bytes(6)); $str = SecureString1::create($payload, $salt, $this->keys, $this->kid, $now); switch ($this->encoding) { case self::ENCODE_B64: $str = SecureString1::b64_urlencode($str); break; case self::ENCODE_URL: $str = urlencode($str); break; } return $str; }
// ok here's some keys..... ideally from a config file // notice how the plain-text secret is md5'd to scramble the bits $keys = array(1 => hash('md5', "this is my secret key 1", true)); $mac = SecureString1::create($qs, $keys, 1); print $mac . "\n"; // if we do it again, the MAC is different (due to the salting) $mac = SecureString1::create($qs, $keys, 1); print $mac . "\n"; // the SecureString perserves query string formating // so you can break it apart just like this: parse_str($mac, $parts); // and now you have all the meta data in addition to your // data. print_r($parts); // Nice thing about PHP is that the associative array // keeps order. So we can reassemble the parts as is // and it still validates // print SecureString1::validate(http_build_query($parts), $keys) ? "same\n" : "different\n"; // like wise we can tamper with one element // and it won't re-validate $parts['cow'] = 'juice'; print_r($parts); print SecureString1::validate(http_build_query($parts), $keys) ? "same\n" : "different\n"; // and of course we can just directly tamper with it $bad = $mac; $bad[20] = '*'; // or anything, or add data or truncate print SecureString1::validate($bad, $keys) ? "same\n" : "different\n"; print_r(substr(SecureString1::b64_urlencode(pack("L", mt_rand())), 0, 6)); print "\n";
public function testBase64() { $this->assertEquals("MA..", SecureString1::b64_urlencode("0")); $this->assertEquals("0", SecureString1::b64_urldecode("MA..")); $this->assertTrue(SecureString1::b64_chars("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.")); $this->assertFalse(SecureString1::b64_chars("~!@#\$%^&*")); // go figure, bad input for base64_input doesn't pop an exception $this->assertEquals('', SecureString1::b64_urldecode("~!@#\$%^&*")); }
public function testSmoke() { $keys = array(1 => md5('whatever!')); $kid = 1; $random = new NotRandom(); $cname = 'foo'; $domain = 'client9.com'; $path = '/'; $secure = true; $httponly = true; $expiration = 86400; $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); // new cookie is not dirty $this->assertFalse($sc->dirty); // test non-exsistant property $this->assertEquals($sc->get('foo', 1), 1); $this->assertFalse($sc->dirty); // set and get property $sc->set('foo', 2); $this->assertEquals($sc->get('foo'), 2); $this->assertTrue($sc->dirty); // set and then unset $sc->set('ding', 'bat'); $this->assertEquals($sc->get('ding'), 'bat'); $sc->set('ding', null); $this->assertTrue(is_null($sc->get('ding'))); // doing a read should wipe everything out $sc->read(); $this->assertEquals($sc->get('foo', 1), 1); $sc->set('lead', 'gold'); // fake time in seconds $now = 123456789; $str = $sc->_write($now); // since keys, salt, and time are all fixed this should // always be the same $expected = "lead=gold&_now=123456789&_slt=MTIzNDU2&_kid=1&_ver=1&_mac=fExl2Kj9ASCkVGvA7EfoOkc10RE5ehIwAXdzNJR6Jxc."; $this->assertEquals($str, $expected); // // TEST URL ENCODING // // write $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_URL); $sc->set('lead', 'gold'); // new cookie is not dirty $this->assertTrue($sc->dirty); $now = 123456789; $str = $sc->_write($now); $this->assertEquals($str, urlencode($expected)); // written out cookie is not dirty $this->assertFalse($sc->dirty); // read 1x $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); $_COOKIE[$cname] = urlencode($expected); $ok = $sc->read($now + 1); $this->assertEquals(SecureCookie::REASON_OK, $ok); // read 2x $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); $_COOKIE[$cname] = urlencode(urlencode($expected)); $ok = $sc->read($now + 1); $this->assertEquals(SecureCookie::REASON_OK, $ok); // BASE 64 // write 64 $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_B64); $sc->set('lead', 'gold'); $now = 123456789; $str = $sc->_write($now); $this->assertEquals($str, SecureString1::b64_urlencode($expected)); // read base 64 $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); $_COOKIE[$cname] = SecureString1::b64_urlencode($expected); $ok = $sc->read($now + 1); $this->assertEquals(SecureCookie::REASON_OK, $ok); // TAINT $str = SecureString1::b64_urlencode($expected); $str[10] = '*'; // invalid char $_COOKIE[$cname] = $str; $ok = $sc->read($now + 1); $this->assertEquals(SecureCookie::REASON_INVALID, $ok); // // KEY PROBLEMS // // $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); $_COOKIE[$cname] = $expected; $ok = $sc->read($now + 1); $this->assertEquals(SecureCookie::REASON_OK, $ok); // test if the key is different $keys2 = array(1 => md5('different key')); $sc = new SecureCookie($keys2, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); $_COOKIE[$cname] = $expected; $ok = $sc->read($now + 1); $this->assertEquals(SecureCookie::REASON_INVALID, $ok); // let's try again but this time with a bad key id $keys2 = array(2 => md5('foo')); $sc = new SecureCookie($keys2, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); $_COOKIE[$cname] = $expected; $ok = $sc->read($now + 1); $this->assertEquals(SecureCookie::REASON_INVALID, $ok); // // EXPIRATION AND CLOCK SKEW // // $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); $_COOKIE[$cname] = $expected; $ok = $sc->read($now + $expiration + 1); $this->assertEquals(SecureCookie::REASON_EXPIRED, $ok); // clock skew $sc = new SecureCookie($keys, $kid, $random, $cname, $domain, $path, $secure, $httponly, $expiration, $expiration, SecureCookie::ENCODE_NONE); $_COOKIE[$cname] = $expected; $ok = $sc->read($now - $expiration); $this->assertEquals(SecureCookie::REASON_CLOCK_SKEW, $ok); }