I am creating a host/client style that uses WCF and its wsHttpBinding to communicate clients to a server running the host and I wanted to provide some sort of security or validation so I have this and I was wondering how good or secure it is.
Every method in the service library has a USERNAME and PASSWORD variable which have to both be populated with a value that gets hashed by the client using the SHA512 hash algorithm. So the username, password and any other parameters for the method are sent along to the server which will check the hashed username and password against a database of hashed usernames and passwords to see if a match is found. If it does match then it returns the data that the client requested but if it does not match then it returns an error or message and doesn't send back the data. A code snipped of a method with this 'security' is below:
// The USERNAME and PASSWORD parameter values have been hashed by the client before
[OperationContract]
string SayHello(string USERNAME, string PASSWORD, string name)
//-------------------------------------------------------------------
public string SayHello(string USERNAME, string PASSWORD, string name)
{
if (USERNAME == "username" & PASSWORD == "password")
{ return string.Format("Hello, {0}!", name); }
else { return "Invalid credentials, method aborted"; }
}
Is this a good security method for validating 'calls'? It is more on the side of checking that they have an account as opposed to being secure but I think it may be quite secure. What do you think, how secure or good is it but more importantly, how could it be broken by hackers or other methods?
This article should help you: How to: Authenticate with a User Name and Password.
WSHttpBinding userNameBinding = new WSHttpBinding();
userNameBinding.Security.Mode = SecurityMode.Message;
userNameBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
svcHost.AddServiceEndpoint(typeof(IService1), userNameBinding, "");
...
string username;
string password;
// Instantiate the proxy
Service1Client proxy = new Service1Client();
// Prompt the user for username & password
GetPassword(out username, out password);
// Set the user’s credentials on the proxy
proxy.ClientCredentials.UserName.UserName = username;
proxy.ClientCredentials.UserName.Password = password;
// Treat the test certificate as trusted
proxy.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
System.ServiceModel.Security.X509CertificateValidationMode.PeerOrChainTrust;
// Call the service operation using the proxy
Related
I'm writing a program (WinForms, C#) that runs on a Win 7 client machine. It obtains credentials from the user (user id, password, and subdomain name) and uses them to authenticate (via Active Directory) to other servers to which the program remotely connects. The other servers are on a domain different than the domain the Win 7 client machine is on.
Using the NetworkCredential, LdapDirectoryIdentifier, and LdapConnection classes, I can test the credentials with no more than the user id, password, and subdomain name (See answer for S.O. Why does Active Directory validate last password?).
For example, for the user account ssmith#xyz.gov, I need only provide ssmith (user id), the password for ssmith, and xyz (the subdomain). I don't need to provide the top-level domain name (gov in this case).
Now, I want to programmatically obtain the top-level domain name for this user account (gov in this case). I've examined the properties and methods of the NetworkCredential, LdapDirectoryIdentifier, and LdapConnection classes. I've looked over the other classes in the System.DirectoryServices.Protocols Namespace. I don't see a way to programmatically obtain the top-level domain name.
Given user id, password, and subdomain name, how can I obtain the top-level domain name for a user account?
Here is my code. Given a user account of ssmith#xyz.gov, My call looks like this (asterisks represent a SecureString password)
bool result = ValidateCredentials("ssmith","******", "xyz");
Here is my code for the method.
private const int ERROR_LOGON_FAILURE = 0x31;
private bool ValidateCredentials(string username, SecureString ssPassword, string domain)
{
//suports secure string
NetworkCredential credentials = new NetworkCredential(username, ssPassword, domain);
LdapDirectoryIdentifier id = new LdapDirectoryIdentifier(domain);
using (LdapConnection connection = new LdapConnection(id, credentials, AuthType.Kerberos))
{
connection.SessionOptions.Sealing = true;
connection.SessionOptions.Signing = true;
try
{
// The only way to test credentials on a LDAP connection seems to be to attempt a
// Bind operation, which will throw an exception if the credentials are bad
connection.Bind();
}
catch (LdapException lEx)
{
credentials = null;
id = null;
if (ERROR_LOGON_FAILURE == lEx.ErrorCode)
{
return false;
}
throw;
}
}
credentials = null;
id = null;
return true;
}
After a successful bind, then the full DNS name of the domain will be in the LdapConnection object:
var domain = connection.SessionOptions.DomainName;
In this case, that would be "xyz.gov". If you need just "gov", then you can just take everything after the last dot:
var tld = domain.Substring(domain.LastIndexOf('.') + 1);
I am writing an ASP.NET program where I need to store the users password in the database. But I get a password mismatched when I Compare the password from the database with the user input password. Even if the users password is correct.
Password Hashing:
string PasswordSalt = Crypto.HashPassword(DateTime.Now.ToString());
string hashPassword = Crypto.HashPassword(formcollection["PassWord"]); //Hash User PassWord
user.PassWord = Crypto.HashPassword(PasswordSalt + hashPassword);//Add Salt to Password For Futher Security
user.PassWordSalt = PasswordSalt;
Password Verification:
Users ThisUser = Users.UsersGetByEmail((string)Session["email"]);
string checkpassword = ThisUser.PassWord;
//User Inputed password.
string password = user.PassWord;
if (password != null)
{
//Need to fix.
string encrypt_password = Crypto.HashPassword(password);
string salted_password = Crypto.HashPassword(ThisUser.PassWordSalt + encrypt_password);
//bool does_password_match = Crypto.VerifyHashedPassword(checkpassword, password);
if (checkpassword == salted_password)
{
//Check if the inputed password matches the password from the Database.
//Remember to give session based on the user_id.
Session["user_id"] = ThisUser.Id;
return RedirectToAction("Promise");
}
else
{
ModelState.AddModelError("PassWord", "Wrong Password, Please Enter Correct Password");
return View(user);
}
I've never used it, but based on the documentation...
Crypto.HashPassword adds the salt for you and returns a base-64 encoded string with all the details in it to verify the password. So, you do NOT need to add a salt yourself.
All you need to do is store the hash result (base64EncodedHash below) in the DB, and then use it with VerifyHashedPassword to authenticate later. E.g. make a unit test like so:
var base64EncodedHash = Crypto.HashPassword("password");
Assert.IsTrue( Crypto.VerifyHashedPassword( base64EncodedHash, "password" ) );
Assert.IsFalse( Crypto.VerifyHashedPassword( base64EncodedHash, "otherPass") );
https://msdn.microsoft.com/en-us/library/system.web.helpers.crypto.verifyhashedpassword(v=vs.111).aspx
To translate this to your code:
user.PassWord = Crypto.HashPassword(formcollection["PassWord"]);
Then to verify (comments added for quirks I see):
//Why are you storing "email" in Session before user is validated??? Seems off.
Users ThisUser = Users.UsersGetByEmail((string)Session["email"]);
string userInputPassword = user.PassWord; //this should be coming from POST
if( ThisUser != null && Crypto.VerifyHashedPassword(ThisUser.PassWord, userInputPassword) ) {
Session["user_id"] = ThisUser.Id;
return RedirectToAction("Promise");
}
else {
ModelState.AddModelError("PassWord","Your username or password are incorrect");
return View(user);
}
Ideally, as I somewhat indicated by my change of your error text...you also want to give the user the same error message whether the username/email or password are wrong. Your code, as is, probably returns a different error if the email doesn't return an account, but you don't want to give that much info to brute-force attackers.
You also need to put in some brute-force checking so that if they attempt too many times with failures, block that IP address for X amount of time., etc.
And, as someone said...when it comes to security...until you're the
expert...it's best to use pre-existing code/frameworks to mitigate you
risks.
I'm using MailSystem.NET library for sendting and recieving e-mails. Everything works fine except Pop3Client authentication using SSL when there is a backslash in the username.
Let's say that I have the following code:
var client = new Pop3Client();
var username = #"xxx\admin";
var password = #"passW0rd";
var host = #"abc.def.gh";
var port = 995;
var result = client.ConnectSsl(host, port, true);
Console.WriteLine(result);
result = client.Authenticate(username, password, SaslMechanism.Login);
Console.WriteLine(result);
And the output is:
+OK The Microsoft Exchange POP3 service is ready.
Command "auth login" failed : -ERR Protocol error. 14
So, what the heck? When I try to connect and authenticate e.g. to a google with username such a johnnyboy#gmail.com, it works. But if there is a backslash in it and I go against MS Exchange, it doesn't work.
The credentials are ok, I doublechecked them using PegasusMail.
Can someone explain what could be wrong?
Ok, answer is simple.
Since 2003, Exchange does not support obsolete SASL mechanism AUTH LOGIN. There must be used at least AUTH PLAIN. But to do it, the whole authentication must be reworked.
After AUTH PLAIN there should be username and password in one command with \000 char as a leading and as a separator. So, the resulting command should be base64 encoded string like:
\000username\000password
see Connecting to POP/SMTP Server via Telnet
So, what I did was simple. I extended Pop3Client class and created a new method Authenticate(string username, string password) without SaslMechanism.
public class Pop3ClientExt : Pop3Client
{
public string Authenticate(string username, string password)
{
var nullchar = '\u0000';
var auth = nullchar + username + nullchar + password;
this.Command("auth plain");
string response = this.Command(System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(auth)));
return response;
}
}
And now, in case of Microsoft Exchange server, I'll call this Authenticate method instead of the old one.
I have a WPF C# client app with an embedded webbrowser control. I have all of the proper hooks in place so that if the site I'm browsing to requires authentication, I handle the IAuthenticate and pass in the required credentials (the user has already logged in to the client app itself). That works great, except...
If user "Bob" visits the site through IE and enters his username and password, then someone uses the client app and logs in as "Steve", "Bob"s session is still authenticated and the site never asks for new credentials, so the client connects as "Bob".
What I really want to do is every time the embedded browser connects to this site, I want to send the credentials and force the browser and site to use those credentials.
Any ideas?
Note that this is more of an issue during testing when I need to impersonate different users.
Use Kerr Credentials
The first thing you need to do is write the user credentials of the proxy to windows user credentials cache.
public static void SetCredentials(string username, string password, string proxydomain)
{
Credential deleteCredentials = new Credential
{
Target = proxydomain
};
if (deleteCredentials.Exists())
deleteCredentials.Delete();
Credential proxyCredential = new Credential
{
Username = username,
Password = password ,
Target = proxydomain,
Type = CredentialType.Generic,
PersistanceType = PersistanceType.Enterprise
};
proxyCredential.Save();
}
then you need to add the info to the registry.
public static void setProxyRegistry(string proxyhost, bool proxyEnabled, string username, string password)
{
const string userRoot = "HKEY_CURRENT_USER";
const string subkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
const string keyName = userRoot + "\\" + subkey;
Registry.SetValue(keyName, "ProxyServer", proxyhost, RegistryValueKind.String);
Registry.SetValue(keyName, "ProxyEnable", proxyEnabled ? "1" : "0", RegistryValueKind.DWord);
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
{
Registry.SetValue(keyName, "ProxyPass", password, RegistryValueKind.String);
Registry.SetValue(keyName, "ProxyUser", username, RegistryValueKind.String);
}
//<-loopback>;<local>
Registry.SetValue(keyName, "ProxyOverride", "*.local", RegistryValueKind.String);
// These lines implement the Interface in the beginning of program
// They cause the OS to refresh the settings, causing IP to realy update
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
}
My current WCF REST Method is defined as:
[OperationContract]
[WebGet(UriTemplate = "{username}/{password}", ResponseFormat =
WebMessageFormat.Json)]
string Login(string username, string password);
An android client app is going to connect to the service, let's say it is http://service.com/login.svc/login...
But I don't want the username and password to be passed in the url like I have specified in the UriTemplate. How can I receive the username and password from the android app into my service, or better yet, how can I change my login method to retrieve the username and password in some POST parameters that I can process in my login function and validate the user against a sql membership database.
We have done this via using the "Authorization" header. The clients pass along an encrypted set of credentials and we generate a token for them on our side. Here is an example of the BeginRequest method of an HttpModule that handles authentication. We use a custom principal to handle the token:
private void BeginRequest(Object source, EventArgs e)
{
if (null == HttpContext.Current || String.IsNullOrEmpty(HttpContext.Current.Request.Headers["Authorization"]))
{
HttpContext.Current.Response.StatusCode = (Int32)HttpStatusCode.Unauthorized;
HttpContext.Current.Response.End();
}
HttpContext context = HttpContext.Current;
Regex matcher = new Regex(WfmConfigurationManager.GetAppSetting("AuthenticationPath"));
if (!matcher.IsMatch(context.Request.Url.ToString(),0))
{
String authHeader = context.Request.Headers["Authorization"];
IIdentity tokenIdentity = new TokenIdentity(authHeader);
if (!tokenIdentity.IsAuthenticated)
{
HttpContext.Current.Response.StatusCode = (Int32)HttpStatusCode.Unauthorized;
HttpContext.Current.Response.End();
}
IPrincipal tokenPrincipal = new TokenPrincipal(tokenIdentity, TokenAuthentication.GetRolesForUser(tokenIdentity));
HttpContext.Current.User = tokenPrincipal;
}
}