I have a standard API set up in which clients are verified by hashing the request body with the private key attributed to the provided public key, and comparing it to the value of hash in the query string.
E.g., request body is "THIS IS REQUEST BODY", they would set hash as sha256('THIS IS REQUEST BODY'.PRIVATE_KEY), and then the server would do the same thing to validate it.
How can I secure this authentication process if the request body is empty? The hash would be the hashed value of the private key, and could then be re-used for similar "empty" requests by anyone listening to the traffic.
I'm assuming that the only answer will be "require content in the request body", but maybe I'm missing something obvious.
First off, don't do HASH(DATA + KEY). There are known vulnerabilities with it. This is precisely what HMAC is designed for. So your hash would be:
hash = HMAC(sha256, data, privateKey)
Now, the typical way of handling your question (how to prevent replay attacks) is by adding a randomizing factor to each request. There are a few ways to do this, but one that works well is a nonce based approach. So:
nonce = random(16)
now = time()
data = api_data + '|' + nonce + '|' + now
hash = HMAC(sha256, data, privateKey)
apiCall = data '&nonce=' + nonce + '&time=' + now + '&sig=' + hash
Then, on the receiving side, you keep track of the list of nonce's seen in the past 30 seconds. If you get one that you've seen, then reject the api call (as that would be a replay attack). If you get one that's more than 30 seconds old, reject the api call.
if (now < time() - 30) {
return false;
} else if (nonceExists(nonce)) {
return false;
}
addNonce(nonce);
data = api_data + '|' + nonce + '|' + now
myhash = HMAC(sha256, data, privateKey)
if (myhash == hash) {
return api_data;
}
return false;
And then you can purge the databases on nonce's every 30 seconds (on a cron job) or every week, doesn't really matter. Depends on how active your API is.
The key here though is that you want to keep track of the nonce's while they are valid, otherwise you'd be vulnerable to replay attacks...
Why not use timestamp with the request. That will make the messages unique. Either you have time synchronization or you could send the timestamp in in plain text with the request to verify at the server.
Related
Refresh token returned from Cognito is not a JWT token , hence cannot be decoded. Is there a way to get the refresh token expiry or it needs to be maintained at application level.
There is no way to decode a refresh token. If you know the expiration time set in cognito for refresh tokens you can store the time it was generated and calculate based on that.
just to elaborate on the accepted answer, as I had the same question.
jwt.org cannot decode the refresh token from aws, as it is encrypted
My way around it, is as follows:
the id token carries "auth_time" (see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-id-token.html)
on login (you could technically do it on refresh as well), I look at that value and add expiration duration to that for a rough estimate
how to get the expiration duration programmatically? There are probably easier ways to do it, but the sdk-v3 command that worked for me was the 'DescribeUserPoolClientCommand' (https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-cognito-identity-provider/classes/describeuserpoolclientcommand.html)
pseudo code in typescript (used in nodejs backend code) looks something like this:
import { CognitoIdentityProviderClient, DescribeUserPoolClientCommand, DescribeUserPoolClientCommandInput} from "#aws-sdk/client-cognito-identity-provider"
import get from 'lodash/get'
const client = new CognitoIdentityProviderClient({ region: [yourRegion] })
const input = {
UserPoolId: [yourUserPoolId],
ClientId: [yourWebClientId],
} as DescribeUserPoolClientCommandInput
const command = new DescribeUserPoolClientCommand(input)
const response = await client.send(command)
const refreshTokenValidityUnits = get(
response,
"UserPoolClient.TokenValidityUnits.RefreshToken"
)
const refreshTokenValidity = get(
response,
"UserPoolClient.RefreshTokenValidity"
)
// result: "days" and "30" for example
This is obviously not complete enough to get the exact values, but enough to get anyone started who, like me, might not be as familiar with the aws-sdk yet.
I am facing an issue with implementing the API to authorize the user and make a session and launch a course.
All the full steps like making a token and passing it with REST API has been done and in the response I am receiving the success in the response token.
Now the issue is that when I am trying opening a course link, it redirects me to the login page despite landing on course. Can you please help set up a session and let me know which API is to be used to make a session so that it doesn't redirects me to the login page.
For those still looking for an answer, I'll show you how to generate a temporary link that will authorize a user and direct them to the desired location in Docebo.
Things you need:
The username.
The SSO secret for the token hash.
-In Docebo: Click APPS and Features on left-hand side. Click Third party integrations. Activate API and SSO, if not already activated. After API and SSO is active, click on its gear icon. Click the check box that starts with "Enable SSO with...". Enter a SSO secret in the box below the checkbox. Save.
Now, for the implementation. I myself used C# for this but hopefully it will be easily translatable to your language of choice (or lack of choice).
The basic idea is this:
1) Create an MD5 hash of three values:
(NOTE: Include commas between the values when generating the hash. Example further below...)
username(lowercase!!!)
time = Seconds since the Unix Epoch in utc.
SSO secret (the one you typed yourself).
2) Get the hex value of the hash.
3) Combine the destination url with the username, time, and hex. Like so:
http[s]://[yourdomain]/lms/index.php?r=site/sso&login_user=[username]&time=[utc
time]&token=[token]{&id_course=[id_course]}{&destination=[destination]}
For my example, I didn't specify a course or a destination.
Here is the above gibberish, in C#:
public string GenerateDoceboSSOLink()
{
string userName = "johnsmith"; //Note the lowercase!!
string domain = "http://my-test.docebosaas.com";
string ssoSecret = "MySSOSecret";
//Getting the seconds since the Unix Epoch
TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
int time = (int)t.TotalSeconds;
//Creating the hash...
MD5 md5 = System.Security.Cryptography.MD5.Create();
//Note the inclusion of the commas!
string input = userName + "," + time + "," + ssoSecret;
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] hash = md5.ComputeHash(inputBytes);
//Getting the hex value of the hash.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
string token = sb.ToString(); //the hex value, which we will call token
//The sso link.
string link = String.Format("{0}/lms/index.php?r=site/sso&login_user={1}&time={2}&token={3}", domain, userName, time, token);
return link;
}
So, I followed this
impossible-to-find documentation that led me to what you see above (I couldn't find the url so I just shared it).
I have been looking at techniques to secure an API for use in an android/iphone app or website application.
I have found one technique which I like but am unsure about if it is a good or what is wrong with it (aside from being a pritty long process).
Processing (users side initially):
First a salt is created by hashing the users password.
Then a signature is created by hashing the requested url (with username appended on the end via a query string) and the salt.
Lastly a token is created by hashing the username and the signature.
The token is passed inside a header to the server (everytime).
First Request:
The first request must be for the validate endpoint and include the device_id as a query string.
The same processing (as above) is done on the server and if the token matches that sent from the user than the device_id is stored in the database and is assigned to that username for future reference (the device id is found in the requested url) and is used to verify the username/device thereafter.
All subsequent requests:
The processing must take place on the users end and servers end for every request now with the token being different everytime (as the requested url changes).
No code is included as it is not written yet.
Your authentication model is a shared secret authentication. In your case your user's password serves as the shared secret. You need to ensure you have a secure way for getting the password to the user and server ahead of time. In order to sign the request you create a message with all your request headers and data. Then hash that request. Then that hash (token) will be passed with the request. The server will perform the same signing and hashing process on the server and ensure the tokens match.
In your example your sound like you want to create the token with this pseudo code:
Token = hmac-sha1( Hash(Pasword + Salt) + RequestUrl + UserName )
Your way is not bad but I would compare your method to Amazon's REST Auth model and implement a closer version of what they have detailed. http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html
Their implementation:
"Authorization: AWS " + AWSAccessKeyId + ":" + base64(hmac-sha1(VERB + "\n"
+ CONTENT-MD5 + "\n"
+ CONTENT-TYPE + "\n"
+ DATE + "\n"
+ CanonicalizedAmzHeaders + "\n"
+ CanonicalizedResource))
They have good reasons for including some fields that you have left out, including but not limited to:
The timestamp is to prevent replay attacks.
The content-MD5 is to prevent prevents people tampering with the request data (relevant to
POST and PUTS)
I have finished my app and then tried it on 3 FB accounts and it was ok,
but the 4th have a permanent error (it cannot get an access token):
com.restfb.exception.FacebookOAuthException: Received Facebook error response of type OAuthException: Expected 1 '.' In the input between the postcard and the payload.
I tried to remove the app and install it again on this account a few times and nothing changed.
I use Java and restFB client.
This is the code where i get the access token:
if (request.getParameter("code") != null) {
String code = request.getParameter("code");
String url = "https://graph.facebook.com/oauth/access_token?"
+ "client_id=" + clientId + "&" + "client_secret="
+ clientSecret + "&" + "code=" + code + "&" + "redirect_uri="
+ redirectURL +"&type=web_server";
String accessToken=readUrl(url).split("&")[0].replaceFirst("access_token=", "");
//....
}
I saw here someone with the same error, he said that the solution was:
replacing "|" with "%257C" which made my access token invalid"
I couldn't really understand what he means.
Embarrassing as it is -- I'll be honest in case it helps someone else:
When I got this error message, I had accidentally copy/pasted a Google access_token (e.g. ya29.A0A...) into a Facebook graph API route. :)
It's probably worth logging the response to the /oauth/access_token request and the value you extract for use as the access token.
For the account that doesn't work, check whether the /oauth/access_token response includes other parameters before access_token. IIRC I've seen responses like
expiry=86400&access_token=AAAxxxx
Check to ensure you are verifying the "code" parameter returned by Facebook before signing the request, not the "access token". That was the mistake I made.
I experience the same issue, and after debugging my only conclusion was that when this message is thrown it might just be the token is expired or invalid. Checking with a freshly generated token should not throw this error.
I'm looking for a simple way to generate passwords that will only work once for a limited amount of time, e.g. 1 day, 1 week, 1 month. This has to be implemented in an application that has no connectivity so a server isn't possible. The use case is something like:
1. Generate password for a specific date and length of time.
2. Send to user (email, phone, etc).
3. User enters in application.
4. Application is enabled for a specific time.
5. Password cannot be reused, even on another PC.
I'm assuming the only way to do this is to generate passwords that only work between a specific set of dates. Can anyone recommend an algorithm that can do this? It doesn't have to be incredibly secure, and I know you can crack this by resetting the time on the PC!
Thanks.
I know I'm late but I'll provide my advice anyway in case someone else who needs it found their way here.
To prevent it being used on another PC, you could probably use the MAC address or hardware address. However, this is subject to the network hardware being still available when checking the password. Please make sure you use the hardware address of the machine where the password will be checked.
private string GetBase64Mac()
{
System.Net.NetworkInformation.NetworkInterface[] interfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
if (interfaces.Length == 0)
{
System.Net.NetworkInformation.PhysicalAddress add = interfaces[0].GetPhysicalAddress();
if (add != null)
return System.Convert.ToBase64String(add.GetAddressBytes());
}
return "";
}
To limit it by some expiry date simply use the text string of the expiry date.
private string GetExpiryDate(DateTime expiryDate)
{
return expiryDate.ToString("yyyyMMdd");
}
Simply use a hash function to hash the combine expiry date, hardware address and a secret key. Prefix or suffix the hash output with the expiry date.
private void GeneratePassword(string prefix)
{
string secretKey = "MySecretKey";
System.Security.Cryptography.SHA1 sha = System.Security.Cryptography.SHA1.Create();
byte[] preHash = System.Text.Encoding.UTF32.GetBytes(prefix + secretKey + GetBase64Mac());
byte[] hash = sha.ComputeHash(preHash);
string password = prefix + System.Convert.ToBase64String(hash);
return password;
}
In the case above, i prefix the hash with the expiry date. So, when we check the password, we simply extract the expiry date from the password, use the same function to generate the same password. If the generated password match the provided password, then you have green light.
private void TestPassword()
{
int duration = 15; // in days
string prefix = GetExpiryDate(DateTime.Today.AddDays(duration));
string generated = GeneratePassword(prefix);
// Positive test
string testPrefix = generated.Substring(0, 8);
string testPassword = GeneratePassword(testPrefix);
if (generated != TestPassword)
return false;
// Negative test
generated[2] = '2';
generated[12] = 'b';
testPrefix = generated.Substring(0, 8);
testPassword = GeneratePassword(testPrefix);
if (generated != TestPassword)
return true;
return false;
}
Sample output password:
20110318k3X3GEDvP0LkBN6zCrkijIE+sNc=
If you can't get the hardware address, then simply use the customer's name. It won't prevent the password from being used in multiple machines, but it will ensure that the same person is using it.
Your application should have a attribute like validity for the password something like this
username password_hash validity_from Validity_end
xyz a73839$56 11-Nov-2010 12-Nov-2010
and then in your application you can validate that your password has expired or not
Generate passwords by any method you'd like (a word list, random letters, etc). Put them into some data structure, like an associative array, where you can associate a date with each password. Then you consult this data structure in the program that hands out passwords to give one out with the proper expiration date. The client program has the same list of passwords and dates, so when it gets a password, it just looks up the associated expiration date there.