Skip to content

Commit

Permalink
Merge pull request facebookarchive#280 from SammyK/injectable-prsg
Browse files Browse the repository at this point in the history
[4.1] Added injectable CSPRSG
  • Loading branch information
gfosco committed Dec 13, 2014
2 parents 67ae92b + 9ae638c commit 564f527
Show file tree
Hide file tree
Showing 16 changed files with 846 additions and 92 deletions.
24 changes: 24 additions & 0 deletions docs/Facebook.fbmd
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ $fb = new Facebook\Facebook([
'http_client_handler' => 'guzzle',
'persistent_data_handler' => 'memory',
'url_detection_handler' => new MyUrlDetectionHandler(),
'pseudo_random_string_generator' => new MyPseudoRandomStringGenerator(),
]);
~~~~

Expand Down Expand Up @@ -117,6 +118,29 @@ $fb = new Facebook([
]);
~~~~

If any other value is provided an `InvalidArgumentException` will be thrown.

### `pseudo_random_string_generator`
Allows you to overwrite the default cryptographically secure pseudo-random string generator.

Generating random strings in PHP is easy but generating _cryptographically secure_ random strings is another matter. By default the SDK will attempt to detect a suitable to cryptographically secure random string generator for you. If a cryptographically secure method cannot be detected, a `Facebook\Exceptions\FacebookSDKException` will be thrown.

You can force a specific implementation of the CSPRSG's provided in the SDK by setting `pseudo_random_string_generator` to one of the following methods: `mcrypt`, `openssl` and `urandom`.

~~~~
$fb = new Facebook([
'pseudo_random_string_generator' => 'openssl',
]);
~~~~

You can write your own CSPRSG that implements the `[Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface](/docs/php/PseudoRandomStringGeneratorInterface)` and set the value of `pseudo_random_string_generator` to an instance of your custom generator.

~~~~
$fb = new Facebook([
'pseudo_random_string_generator' => new MyPseudoRandomStringGenerator(),
]);
~~~~

If any other value is provided an `InvalidArgumentException` will be thrown.
</card>

Expand Down
65 changes: 65 additions & 0 deletions docs/PseudoRandomStringGeneratorInterface.fbmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<card>
# The cryptographically secure pseudo-random string generator interface for the Facebook SDK for PHP

The cryptographically secure pseudo-random string generator interface allows you to overwrite the default CSPRSG logic by coding to the `Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface`.
</card>

<card>
## Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface {#overview}

By default the SDK will attempt to generate a cryptographically secure random string using a number of methods. If a cryptographically secure method is not detected, a `Facebook\Exceptions\FacebookSDKException` will be thrown.

If your hosting environment does not support any of the CSPRSG methods used by the SDK or if you have preferred CSPRSG, you can provide your own CSPRSG to the SDK using this interface.

> **Caution:** Although it is popular to use `rand()`, `mt_rand()` and `uniqid()` to generate random strings in PHP, these methods are not cryptographically secure. Since the pseudo-random string generator is used to validate against Cross-Site Request Forgery (CSRF) attacks, the random strings _must_ be cryptographically secure. Only overwrite this functionality if your custom pseudo-random string generator is a cryptographically strong one.

An example of implementing a custom CSPRSG:

~~~~
use Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface;

class MyCustomPseudoRandomStringGenerator implements PseudoRandomStringGeneratorInterface
{
/**
* @inheritdoc
*/
public function getPseudoRandomString($length)
{
$randomString = '';

// . . . Do CSPRSG logic here . . .

return $randomString;
}
}
~~~~

To enable your custom CSPRSG implementation in the SDK, you can set an instance of the generator to the `pseudo_random_string_generator` config of the `Facebook\Facebook` super service.

~~~~
$fb = new Facebook\Facebook([
// . . .
'pseudo_random_string_generator' => new MyCustomPseudoRandomStringGenerator(),
// . . .
]);
~~~~

Alternatively, if you're working with the `Facebook\Helpers\FacebookRedirectLoginHelper` directly, you can inject your custom generator via the constructor.

~~~~
use Facebook\Helpers\FacebookRedirectLoginHelper;

$myPseudoRandomStringGenerator = new MyCustomPseudoRandomStringGenerator();
$helper = new FacebookRedirectLoginHelper($fbApp, null, null, $myPseudoRandomStringGenerator);
~~~~
</card>

<card>
## Method Reference {#method-reference}

### getPseudoRandomString() {#get-pseudo-random-string}
~~~~
public string getPseudoRandomString(int $length)
~~~~
Returns a cryptographically secure pseudo-random string that is `$length` characters long.
</card>
6 changes: 5 additions & 1 deletion docs/sdk_reference.fbmd
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ FB(devsite:markdown-wiki:table {
<card>
# Interfaces {#interfaces}

Interfaces you can code to.
You can overwrite certain functionality of the SDK by coding to an interface.

FB(devsite:markdown-wiki:table {
columns: ['Interface name','Description',],
Expand All @@ -240,6 +240,10 @@ FB(devsite:markdown-wiki:table {
'`[Facebook\Url\UrlDetectionInterface](/docs/php/UrlDetectionInterface)`',
'Interface to code your own URL detection logic.',
],
[
'`[Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface](/docs/php/PseudoRandomStringGeneratorInterface)`',
'Interface to code your own cryptographically secure pseudo-random string generator.',
],
],
})
</card>
36 changes: 28 additions & 8 deletions src/Facebook/Facebook.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
use Facebook\GraphNodes\GraphList;
use Facebook\Url\UrlDetectionInterface;
use Facebook\Url\FacebookUrlDetectionHandler;
use Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface;
use Facebook\PseudoRandomString\McryptPseudoRandomStringGenerator;
use Facebook\PseudoRandomString\OpenSslPseudoRandomStringGenerator;
use Facebook\PseudoRandomString\UrandomPseudoRandomStringGenerator;
use Facebook\HttpClients\FacebookHttpClientInterface;
use Facebook\HttpClients\FacebookCurlHttpClient;
use Facebook\HttpClients\FacebookStreamHttpClient;
Expand Down Expand Up @@ -91,6 +95,12 @@ class Facebook
*/
protected $urlDetectionHandler;

/**
* @var PseudoRandomStringGeneratorInterface|null The cryptographically secure
* pseudo-random string generator.
*/
protected $pseudoRandomStringGenerator;

/**
* @var AccessToken|null The default access token to use with requests.
*/
Expand All @@ -111,13 +121,6 @@ class Facebook
*/
protected $lastResponse;

/**
* @TODO Add FacebookInputInterface
* @TODO Add FacebookRandomGeneratorInterface
* @TODO Add FacebookRequestInterface
* @TODO Add FacebookResponseInterface
*/

/**
* Instantiates a new Facebook super-class object.
*
Expand Down Expand Up @@ -179,6 +182,22 @@ public function __construct(array $config = [])
}
}

if (isset($config['pseudo_random_string_generator'])) {
if ($config['pseudo_random_string_generator'] instanceof PseudoRandomStringGeneratorInterface) {
$this->pseudoRandomStringGenerator = $config['pseudo_random_string_generator'];
} elseif ($config['pseudo_random_string_generator'] === 'mcrypt') {
$this->pseudoRandomStringGenerator = new McryptPseudoRandomStringGenerator();
} elseif ($config['pseudo_random_string_generator'] === 'openssl') {
$this->pseudoRandomStringGenerator = new OpenSslPseudoRandomStringGenerator();
} elseif ($config['pseudo_random_string_generator'] === 'urandom') {
$this->pseudoRandomStringGenerator = new UrandomPseudoRandomStringGenerator();
} else {
throw new \InvalidArgumentException(
'The pseudo_random_string_generator must be an instance of Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface'
);
}
}

if (isset($config['persistent_data_handler'])) {
if ( $config['persistent_data_handler'] instanceof PersistentDataInterface) {
$this->persistentDataHandler = $config['persistent_data_handler'];
Expand Down Expand Up @@ -302,7 +321,8 @@ public function getRedirectLoginHelper()
return new FacebookRedirectLoginHelper(
$this->app,
$this->persistentDataHandler,
$this->urlDetectionHandler
$this->urlDetectionHandler,
$this->pseudoRandomStringGenerator
);
}

Expand Down
122 changes: 59 additions & 63 deletions src/Facebook/Helpers/FacebookRedirectLoginHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
use Facebook\Url\FacebookUrlManipulator;
use Facebook\PersistentData\PersistentDataInterface;
use Facebook\PersistentData\FacebookSessionPersistentDataHandler;
use Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface;
use Facebook\PseudoRandomString\McryptPseudoRandomStringGenerator;
use Facebook\PseudoRandomString\OpenSslPseudoRandomStringGenerator;
use Facebook\PseudoRandomString\UrandomPseudoRandomStringGenerator;
use Facebook\Exceptions\FacebookSDKException;
use Facebook\FacebookClient;

Expand All @@ -43,6 +47,11 @@
class FacebookRedirectLoginHelper
{

/**
* @const int The length of CSRF string to validate the login link.
*/
const CSRF_LENGTH = 32;

/**
* @var FacebookApp The FacebookApp entity.
*/
Expand All @@ -58,20 +67,30 @@ class FacebookRedirectLoginHelper
*/
protected $persistentDataHandler;

/**
* @var PseudoRandomStringGeneratorInterface The cryptographically secure
* pseudo-random string generator.
*/
protected $pseudoRandomStringGenerator;

/**
* Constructs a RedirectLoginHelper for a given appId.
*
* @param FacebookApp $app The FacebookApp entity.
* @param PersistentDataInterface|null $persistentDataHandler The persistent data handler.
* @param UrlDetectionInterface|null $urlHandler The URL detection handler.
* @param PseudoRandomStringGeneratorInterface|null $prsg The cryptographically secure
* pseudo-random string generator.
*/
public function __construct(FacebookApp $app,
PersistentDataInterface $persistentDataHandler = null,
UrlDetectionInterface $urlHandler = null)
UrlDetectionInterface $urlHandler = null,
PseudoRandomStringGeneratorInterface $prsg = null)
{
$this->app = $app;
$this->persistentDataHandler = $persistentDataHandler ?: new FacebookSessionPersistentDataHandler();
$this->urlDetectionHandler = $urlHandler ?: new FacebookUrlDetectionHandler();
$this->pseudoRandomStringGenerator = $prsg ?: $this->detectPseudoRandomStringGenerator();
}

/**
Expand All @@ -94,6 +113,42 @@ public function getUrlDetectionHandler()
return $this->urlDetectionHandler;
}

/**
* Returns the cryptographically secure pseudo-random string generator.
*
* @return PseudoRandomStringGeneratorInterface
*/
public function getPseudoRandomStringGenerator()
{
return $this->pseudoRandomStringGenerator;
}

/**
* Detects which pseudo-random string generator to use.
*
* @return PseudoRandomStringGeneratorInterface
*
* @throws FacebookSDKException
*/
public function detectPseudoRandomStringGenerator()
{
// Since openssl_random_pseudo_bytes() can sometimes return non-cryptographically
// secure pseudo-random strings (in rare cases), we check for mcrypt_create_iv() first.
if(function_exists('mcrypt_create_iv')) {
return new McryptPseudoRandomStringGenerator();
}
if(function_exists('openssl_random_pseudo_bytes')) {
return new OpenSslPseudoRandomStringGenerator();
}
if( ! ini_get('open_basedir') && is_readable('/dev/urandom')) {
return new UrandomPseudoRandomStringGenerator();
}

throw new FacebookSDKException(
'Unable to detect a cryptographically secure pseudo-random string generator.'
);
}

/**
* Stores CSRF state and returns a URL to which the user should be sent to
* in order to continue the login process with Facebook. The
Expand All @@ -118,8 +173,10 @@ public function getLoginUrl($redirectUrl,
$separator = '&')
{
$version = $version ?: Facebook::DEFAULT_GRAPH_VERSION;
$state = $this->generateState();

$state = $this->pseudoRandomStringGenerator->getPseudoRandomString(static::CSRF_LENGTH);
$this->persistentDataHandler->set('state', $state);

$params = [
'client_id' => $this->app->getId(),
'redirect_uri' => $redirectUrl,
Expand Down Expand Up @@ -277,65 +334,4 @@ private function getInput($key)
return isset($_GET[$key]) ? $_GET[$key] : null;
}

/**
* Generate a state string for CSRF protection.
*
* @return string
*/
protected function generateState()
{
return $this->random(16);
}

/**
* Generate a cryptographically secure pseudrandom number.
*
* @param int $bytes Number of bytes to return.
*
* @return string
*
* @throws FacebookSDKException
*
* @TODO Add support for Windows platforms.
*/
private function random($bytes)
{
if (!is_numeric($bytes)) {
throw new FacebookSDKException(
"random() expects an integer"
);
}
if ($bytes < 1) {
throw new FacebookSDKException(
"random() expects an integer greater than zero"
);
}
$buf = '';
// http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
if (!ini_get('open_basedir')
&& is_readable('/dev/urandom')) {
$fp = fopen('/dev/urandom', 'rb');
if ($fp !== FALSE) {
$buf = fread($fp, $bytes);
fclose($fp);
if($buf !== FALSE) {
return bin2hex($buf);
}
}
}

if (function_exists('mcrypt_create_iv')) {
$buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
if ($buf !== FALSE) {
return bin2hex($buf);
}
}

while (strlen($buf) < $bytes) {
$buf .= md5(uniqid(mt_rand(), true), true);
// We are appending raw binary
}
return bin2hex(substr($buf, 0, $bytes));
}

}
Loading

0 comments on commit 564f527

Please sign in to comment.