Closed Lausebengel closed 10 years ago
Hi @oliver13,
if I'm not mistaking @timtailor got a working solution:
"I basically used it out of the box."
...
"My android app retrieves the FOS user's salt from a public url, encodes his plain text password with his salt (sha512 and 5 iterations), then passes the WSSE Header (like http://www.teria.com/~koseki/tools/wssegen/) to the API url which is EscapeWSSE protected."
(mentioned in issue #32)
Please do let me know how it goes, it would be great to get some documentation together so we can include this in the bundle!
Thanks in advance for your feedback!
Kind regards, David
Hi @oliver13,
in my project it worked just by adding WSSE as a firewall and having the FOSUserBundle as a provider. The important logic happens mainly on the client side (in my case an android app).
The client must create a WSSE header with
To generate the hashed password, you need to replicate the FOSUserBundle's hashing mechanism, which uses SHA512, Base64 over x iterations (please google for the details there, because i don't have them at hand right now).
Input for that algorithm is
The plain password is available via user input (naturally), and to get the user's salt i provided a public API call (without firewall protection).
Before you implement it, you can use the form at http://www.teria.com/~koseki/tools/wssegen/ to test the WSSE integration (input the hashed user password as in the fos_user table, and then copy the output into a REST testing tool as provided by many IDEs).
@djoos: I also recommend to put this into the documentation, as many developers might want to use WSSE for their Symfony / FOSUserBundle projects.
Hi @timtailor,
thanks for your reply!
I definitely think having a write up on how to use WSSE+FOSUser in the documentation would be great. Would any of you be willing to help out and send me a PR?
Thanks in advance!
Kind regards, David
P.S. Rather than using a public/unprotected API to get a user's salt: how about using a "system API"-call (as in eg. "android" user with a plaintext pwd)? In that way it is not completely open and once a user's salt has been retrieved, actual "user API"-calls can be made.
Hello @djoos and @timtailor,
thanks for your support ! I think I get it step by step. Until now I have installed the bundles and it seams that they work.
But if I request the API with a REST client in Firefox I get "Status Code: 401 Unauthorized" ... So it is still something wrong.
Here is what I have as configuration, etc.:
REST stuff in config.yml
fos_rest:
format_listener:
rules:
- { priorities: ['json', 'xml'], fallback_format: json, prefer_extension: false }
view:
view_response_listener: true
nelmio_api_doc: ~
fos_user:
db_driver: orm
firewall_name: wsse_secured
user_class: [...]\UserBundle\Entity\User
escape_wsse_authentication:
authentication_provider_class: Escape\WSSEAuthenticationBundle\Security\Core\Authentication\Provider\Provider
authentication_listener_class: Escape\WSSEAuthenticationBundle\Security\Http\Firewall\Listener
authentication_entry_point_class: Escape\WSSEAuthenticationBundle\Security\Http\EntryPoint\EntryPoint
authentication_encoder_class: Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder
The security.yml:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
wsse_secured:
pattern: ^/
wsse:
lifetime: 300 #lifetime of nonce
realm: "Secured API"
profile: "UsernameToken"
encoder: #digest algorithm
algorithm: sha512
encodeHashAsBase64: true
iterations: 1
anonymous: true
provider: fos_userbundle
stateless: true
access_control:
- { path: ^/, role: ROLE_USER }
That is what I send via the REST Client:
X-WSSE: UsernameToken Username="test123", PasswordDigest="74k64qaoSUYaDO+9TjI/vq2Dwz6GxseD7AAC3OpfjrqwpsrcjOO8Pn7DwKZK5LfxHSS+oPdSPY5FP03LFMWxew==", Nonce="NmY5YTQ5NjJiMjBhMGM0Yw==", Created="2014-03-18T13:57:13Z"
Thank you in advance for further help !
Ok, the request of my REST client seams to be faulty. The listener class can't find the string "X-WSSE" in my request header (Line 42):
if(!$request->headers->has('X-WSSE'))
I use restclient.net in firefox. What do I have to enter here as header ?
Thanks in advance
Update: Using another rest client solves this : http://code.google.com/p/rest-client/ . But still getting "Status Code: 401 Unauthorized" ...
Hi @oliver13,
could you have a look at the validateDigest-method in Security/Core/Authentication/Provider/Provider.php?
Note to myself: adding a verbose option for debugging purposes to the bundle wouldn't actually be a bad thing, would it? :-)
Let me know how it goes!
Kind regards, David
Hi David, thanks for your reply. I have looked into Provider class. It seams that there is a problem with the lifetime check. I the logfile I found : app.DEBUG: Token has expired....
Update: Can this be caused by the missing salt in Line 73 of Provider ?
"Can this be caused by the missing salt in Line 73 of Provider ?" No, it's just a matter of generating a new header and consuming it within the 300 (seconds), which is the default lifetime for the nonce...
Hope this helps!
Now it seams to work ... uff ... :-)
I had to change the following in the Provider class:
if(
$user &&
$this->validateDigest(
$token->getAttribute('digest'),
$token->getAttribute('nonce'),
$token->getAttribute('created'),
$this->getSecret($user),
$this->getSalt($user)
)
)
{
$authenticatedToken = new Token($user->getRoles());
$authenticatedToken->setUser($user);
$authenticatedToken->setAuthenticated(true);
return $authenticatedToken;
}
Here I have to change this : $this->getSalt($user)
protected function getSalt(UserInterface $user)
{
return $user->getSalt();
}
Here I change the parameters and return value.
Thanks for the help !
Hi @oliver13,
it sounds like the getSalt could do with the UserInterface $user
parameter by default (letting it return "" by default) and then your can simply extend the provider class by a custom one (see: "Specify custom authentication class(es)" in the README.md) and that would sort it out, right?
Let me know/send me a PR if you want to and then I'll try to get this merged in ASAP!
Kind regards, David
P.S. In that way getSecret() can be overwritten as well if you'd like to make use of an API key for a user rather than the user's password, as well as having a separate salt via getSalt() for the API key than the password. #flexibility
I actually think this might be a useful threat (even though quite lengthy, I admit) for issue #27... Let me know what you think @Fraktl!
@Oliver13, could you give the latest version a go? This should work out of the box for you...
Thanks in advance for your feedback! David
Hi @djoos, yes I get it to work with a custom provider class.Thanks a lot !
This is definitely not the way to do that.
Wsse header should be generated by concating base64_decode(nonce), timestamp and secret.
Here the secret is hashed password as generated by FOSUserBundle OR lets say by security component.
Here getSecret should instead return the hashed password and the expected digest vs sent digest comparison logic should be as it was before.
oliver13's client should use the hashed password (generated by the same algorithm as symfony security component) in place of secret and thats is. We dont need any changes in this bundle and validateDigest to address that!
How we actually use this bundle for securing API call is:
we have a different field called token which is regenerated in each login from mobile app and is passed along to the client. The client uses this token to sign all the request. We just overload the getSecret method which returns the token instead of the actual password.
Likewise, in @oliver13 's case the getSecret method should return the hashed password.
This is not something that needs to be changed in the core. There is support for this already
Hi @broncha,
thanks for getting in touch! I'm not 100% sure I got your point though...
The recent change to the getSalt-method in the provider makes it possible to bring a custom salt into play as well when validating the digest - which it actually already did (even though being a fixed salt of "", an empty string).
It should not introduce any BC issues, but allow for more flexible use: how would the validate digest be able to check a user's (eg. FOSUser) digest when encodePassword doesn't have the ability to get the user's salt to encode the password? Allowing this flexibility now allows for the bundle to support salted passwords (as opposed to non-salted, like eg. the token you mentioned) as well.
Thanks in advance for your feedback!
Kind regards, David
Hi,
I got it working by doing these steps:
protected function getSalt(UserInterface $user)
{
return null;
}
No need to get the salt, because it was already used in FOS User saved password
sha512 algorithm with 5000 iterations. Here we need to get The Salt to generate the password. So we need to expose User Salt over the API to be used by the client. Here is the function to generate the hashed password in client side:
// Here we suppose that you have already the User SALT. for example exposed by a public rest service
function hashPassword($password, $salt)
{
$salted = $password . '{' . $salt . '}';
$digest = hash('sha512', $salted, true);
for ($i = 1; $i < 5000; $i++) {
$digest = hash('sha512', $digest . $salted, true);
}
return base64_encode($digest);
}
And this is the wsse_header generator:
function wsse_header($username, $hashedPassword)
{
$nonce = hash_hmac('sha512', uniqid(null, true), uniqid(), true);
$created = new DateTime('now', new DateTimezone('UTC'));
$created = $created->format(DateTime::ISO8601);
$digest = hash('sha512', $nonce . $created . $hashedPassword, true);
return sprintf(
'X-WSSE: UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"',
$username, base64_encode($digest), base64_encode($nonce), $created
);
}
Finally, to use it just call:
wsse_header('someLogin',
hashPassword('somePassword', 'someSalt'));
Note: This was a PHP client, for Android, you can take a look here: Android Client Tutorial
encoder: #digest algorithm
algorithm: sha512
encodeHashAsBase64: true
iterations: 1 #Here one iteration, because in wsse_header we make only 1 iteration
I hope this help you ! regards Skander
gooooooooooood @skonsoft
Hello,
I'm trying to setup EscapeWSSEAuthenticationBundle and FOSUserBundle to secure my api. Does someone know a good tutorial about this ? I tried it with this info : https://github.com/escapestudios/EscapeWSSEAuthenticationBundle/issues/31 but it is to much fragmented. I didn't get it to work ...
Thanks in advance.