Closed Fraktl closed 10 years ago
@escapestudios I think I have found the solution.
For reference for other people that might find this topic. The solution is this:
security.yml
security:
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
firewalls:
wsse_secured:
pattern: ^/api/.*
wsse:
lifetime: 300 #lifetime of nonce
realm: "Secured API" #identifies the set of resources to which the authentication information will apply (WWW-Authenticate)
profile: "UsernameToken" #WSSE profile (WWW-Authenticate)
encoder: #digest algorithm
algorithm: sha512
encodeHashAsBase64: true
iterations: 1
anonymous: true
provider: fos_userbundle
access_control:
- { path: ^/api.*, role: ROLE_USER }
- { path: ^/security.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
Then overload the validateDigest from the Escapist provider to your own provider (and set this class as the provider in config.yml
Replace // validate secret with
....
//validate secret
$expected = $this->getEncoder()->encodePassword(
sprintf(
'%s%s%s',
base64_decode($nonce),
$created,
$secret
),
$user->getSalt()
);
....
Especially the $user->getSalt() is important.
Then in your SecurityController change the passwordDigest generation to a SHA512 hash:
...
$created = date('c');
$nonce = substr(md5(uniqid('nonce_', true)), 0, 16);
$nonceHigh = base64_encode($nonce);
$container = $this->get('service_container');
$iterations = $container->getParameter('wsse_iterations');
$salted = $nonce . $created . $user->getPassword() . "{" . $user->getSalt() . "}";
$passwordDigest = hash('sha512', $salted, true);
for ($i = 1; $i < $iterations; $i++) {
$passwordDigest = hash('sha512', $passwordDigest . $salted, true);
}
$passwordDigest = base64_encode($passwordDigest);
$header = "UsernameToken Username=\"{$username}\", PasswordDigest=\"{$passwordDigest}\", Nonce=\"{$nonceHigh}\", Created=\"{$created}\"";
$view->setHeader("Authorization", 'WSSE profile="UsernameToken"');
$view->setHeader(
"X-WSSE",
"UsernameToken Username=\"{$username}\", PasswordDigest=\"{$passwordDigest}\", Nonce=\"{$nonceHigh}\", Created=\"{$created}\""
);
$data = array('WSSE' => $header);
...
I hope this helps for anyone looking to integrate FOSUserbundle with salt + SHA512 passwords and WSSE to secure API's
After some more testing I found a bug in the above code. Since I'm never testing the password that I'm using in the passwordDigest a user can login with an incorrect password ($user->getPassword()) is used. Right now I'm stuck at this point but help is more than welcome.
Hi @Fraktl,
unfortunately we don't use the FOSUserBundle, but the "encoder of choice" changes made to the WSSEAuthenticationBundle a while ago would mean that you should be able to use the bundle with FOSUserBundle as well - there shouldn't be any need to overload any methods of this bundle to make it work...
I'll copy in @peschee who initially opened a related issue (#20) - he might be able to help you out some more on the short term. In the longer run adding more documentation, specifically about the use of this bundle with the FOSUserBundle is definitely on my list of things to do...
Hope this helps!
Kind regards, David
@djoos
The problem is that you need the salt, the encoder type of choice isn't enough. Adding this to my SecurityController.php will generate a SHA512 hashed password with the same original salt as stored in the database.
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$enc_password = $encoder->encodePassword($password, $user->getSalt());
This works but only if I add this to your validateDigest method
$expected = $this->getEncoder()->encodePassword(
sprintf(
'%s%s%s',
base64_decode($nonce),
$created,
$secret
),
$user->getSalt()
);
So far so great, now looking into https://github.com/FlyersWeb/angular-symfony to make it work in the frontend too.
Hi @Fraktl,
ok, got it!
Please check out 0454e35 - these minor changes will allow for a smoother integration... Thanks in advance for your feedback!
Kind regards, David
Continuing the conversation here on the FOSUser + WSSE Auth implementation issues also mentioned in #32. It would be great if we could get this solved together and documented...
Thanks in advance for your help guys!
Kind regards, David
Mea culpa, I got carried away with @Fraktl's suggestion...
@Fraktl: wouldn't it be better to ther $user->getPassword() (=plain text pwd, encoded + salted) and send this over in the digest which is a then sha1 / sha512 / ... without a salt? If you insist of salting the digest, I've kept the protected getSalt() method in the Provider so that you could still override it and do something with that if really needed. Hope this helps...
Have a great weekend!
Kind regards, David
I use https://github.com/davedevelopment/guzzle-wsse-auth-plugin to interact with my secured API based on symfony2 fw. Did i understand it right that after this commit we need to provide Salt to http client for properly generate digest? Isn't a security breach?
Hi @wiistriker,
there's no need to provide a salt to the client - only if you want to... What is your particular use-case?
Thanks in advance for your feedback!
Kind regards, David
@djoos I don't want to pass salt to client, i think it's security breach. But it seems salt now required after this merge to generate right digest on client side. I just notice that library https://github.com/davedevelopment/guzzle-wsse-auth-plugin stop working after this merge and after some digging i found that now in validateDigest function salt is required. So salt is required to generate digest on client now, isnt it?
p.s. now i redefine method getSalt() inside WSSE Provider and return "" (empty string) for user salts, so now it works as expected. i still not sure that this commit doint it right
Hi,
in your case as you don't want the user's getSalt() to be used for WSSE Auth: you can specify a custom authentication class for the provider-class that overrides the getSecret() method of the provider returning "".
RE: security breach The only way to create a digest for a user for WSSE Auth is to either use the password as stored in the db (salted) in the client -or- use the plain text and salt it upon digest creation. The bundle is able to cater for both of these setups, it just depends on the developer's implementation. The best way forward is to have a separate key and secret for the user for API-purposes and then override getSecret() and getSalt() in the provider to call on the relevant user methods (eg. $user->getAPIKey() and $user->getAPISecret()). Let me know what you think!
Hope this helps! David
Hi,
I have completed programming an API and I'm looking into securing it using WSSE. I'm having some trouble getting authenticated to the secured parts of the api.
My configuration is as follows:
security.yml
config.yml
SecurityController.php
The tokens are getting generated by doing POST /security/create_token.json?username=myusername&password="plaintextpassword" over SSL But when I use that token with a X-WSSE header to go to GET /api/me I keep getting a 401 Not Authorized.
Any ideas? Did I misinterpreted the documentation?