|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionMicrosoft IssueVision is a developer sample application that demonstrates the best practices for building Smart Client applications. It was developed specifically for the DevDays 2004 Smart Client track, and it provides a sample help desk management application. The original source code can be downloaded here. In this article, I describe an implementation of adding WS-SecureConversation to the Microsoft IssueVision sample application using WSE 2.0. The WS-SecureConversation specification allows clients and Web Services to establish a token-based, secure conversation for the duration of a session. It is analogous to the Secure Sockets Layer (SSL) protocol that provides on-demand, secure communications over the HTTP transport channel. Secure conversation is based on security tokens that are procured by a service token provider. This process involves an initial amount of overhead, but once the channel is established, the client and service exchange a lightweight, signed security context token, which optimizes message delivery time compared with using regular security tokens. The security context token enables the same signing and encryption features as other security tokens, like UsernameToken or X509SecurityToken. System Requirements
InstallationAfter copying the contents of the source zip file to a location on your local disk, we must complete the following setup steps before we can compile and run the application. 1. Installing the Server CertificateThis application uses the sample certificates included in WSE 2.0. Follow the procedures below to install the server certificate. (Note: you should not use these sample certificates in a production environment. Instead, contact a certificate authority, and request your own certificate.)
Note: this certificate will be used to encrypt messages between the applications. The client application will use the public key to encrypt the message and the service will use the private key to decrypt the message. The client needs to have the public portion of the certificate available in the Current User store.
Note: this certificate only contains the public portion of Server Private.pfx. The client will use this to encrypt messages and the server will use the private key installed in the Local Machine store to decrypt the messages. 2. Installing the IssueVision Sample DatabaseTo install the sample database, please run DataReset.cmd included in the source zip file. The IssueVision XML Web Service requires SQL Server authentication (mixed mode) to be enabled on the SQL Server 2000 server. Follow the procedures below to verify:
3. Creating the Required Virtual DirectoryFinally, run the CreateSampleVdir.vbs script included in the source zip file to automatically create the required virtual directory. (You can later uninstall the virtual directory using the DeleteSampleVdir.vbs script.) Code DescriptionA secure conversation is initiated by a client that requires an on-demand secure communication session with a Web Service, and it consists of the following three steps: 1. The client issues a signed request to the security token service provider for a security context tokenThe client initiates the secure conversion by issuing a signed request to the secure token service (STS) provider for a security context token. The client uses UsernameToken to sign the security token request, and uses X509SecurityToken to encrypt the SOAP message sender's entropy value. If the request is successful, the client caches the security context token for further communication. The following function public static SecurityContextToken RequestSCTByUsername(String username,
String password)
{
SecurityContextToken sct = null;
// Request a new security context token
// if one was not available from m_sctData
if (m_sctData.m_username == string.Empty ||
m_sctData.m_password == string.Empty ||
m_sctData.m_username != username || m_sctData.m_password !=
password || m_sctData.m_sct.IsExpired)
{
// Create a UsernameToken to use as the base
// for the security context token
SecurityToken token = new UsernameToken(username,
password, PasswordOption.SendPlainText);
// Retrieve the server certificate
SecurityToken issuerToken = GetServerToken(true);
// Create a SecurityContextTokenServiceClient
// (STSClient) that will get the SecurityContextToken
String secureConvEndpoint = ConfigurationSettings.AppSettings["tokenIssuer"];
SecurityContextTokenServiceClient STSClient = new
SecurityContextTokenServiceClient(new Uri(secureConvEndpoint));
// Request the security context token,
// use the client's signing token as the base
sct = STSClient.IssueSecurityContextTokenAuthenticated(token, issuerToken);
// Cache the security context token in m_sctData
m_sctData = new SctData(username, password, sct);
}
return m_sctData.m_sct;
}
2. The security token service provider verifies the request and issues a security context token back to the clientOn the server side, in order to validate digital signatures for incoming SOAP messages created using a UsernameToken, we override the [SecurityPermissionAttribute(SecurityAction.Demand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public class CustomUsernameTokenManager : UsernameTokenManager
{
protected override String AuthenticateToken( UsernameToken token )
{
// This method returns the password for the provided username
// WSE will make the determination if they match
DataSet dataSet = new DataSet();
string dbPasswordHash;
try
{
SqlConnection conn = new SqlConnection(Common.ConnectionString);
SqlCommand cmd = new SqlCommand("GetUser", conn);
cmd.Parameters.Add("@UserName", token.Username);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(dataSet);
}
catch (Exception ex)
{
EventLogHelper.LogFailureAudit(string.Format("The GetUser" +
" stored procedure encounted a problem: {0}",
ex.ToString()));
throw new SoapException(string.Empty,
SoapException.ServerFaultCode, "Database");
}
// does the user exist?
if (dataSet.Tables[0].Rows.Count == 0)
{
EventLogHelper.LogFailureAudit(string.Format("The username" +
" {0} does not exist.", token.Username));
throw new SoapException(string.Empty,
SoapException.ClientFaultCode, "Security");
}
else
{
// we found the user, verify the password
// hash by compare the Salt + PasswordHash
DataRow dataRow = dataSet.Tables[0].Rows[0];
dbPasswordHash = (string)dataRow["PasswordHash"];
string dbPasswordSalt = (string)dataRow["PasswordSalt"];
// create a hash based on the user's salt and the input password
string passwordHash = HashString(dbPasswordSalt + token.Password);
// does the computed hash match the database hash?
if (string.Compare(dbPasswordHash, passwordHash) != 0)
{
EventLogHelper.LogFailureAudit(string.Format("The password" +
" for the username {0} was incorrect.",
token.Username));
throw new SoapException(string.Empty,
SoapException.ClientFaultCode, "Security");
}
else
{
return token.Password;
}
}
}
// generates a hash of the input plain text
private static string HashString(string textToHash)
{
SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider();
byte[] byteValue = System.Text.Encoding.UTF8.GetBytes(textToHash);
byte[] byteHash = SHA1.ComputeHash(byteValue);
SHA1.Clear();
return Convert.ToBase64String(byteHash);
}
}
3. The client and the Web Service use the security context token for secure communicationFrom the client side, every call to the Web Service starts by requesting the security context token cached from the above steps. We then use the security context token to sign and encrypt the SOAP request message: public static IVDataSet SendReceiveIssues(DataSet changedIssues,
DateTime lastAccessed)
{
IVDataSet data = null;
IssueVisionServicesWse dataService = GetWebServiceReference();
try
{
// Request the security context token
SecurityContextToken sct =
Wse2HelperClient.RequestSCTByUsername(UserSettings.Instance.Username,
UserSettings.Instance.Password);
// Use the security context token to sign
// and encrypt a request to the Web service
SoapContext requestContext = dataService.RequestSoapContext;
requestContext.Security.Tokens.Add(sct);
requestContext.Security.Elements.Add( new MessageSignature( sct ) );
requestContext.Security.Elements.Add( new EncryptedData( sct ) );
data = dataService.SendReceiveIssues(changedIssues, lastAccessed);
}
catch (WebException)
{
HandleWebServicesException(WebServicesExceptionType.WebException);
}
catch (SoapException soapEx)
{
if (soapEx.Actor == "Security")
{
HandleWebServicesException(WebServicesExceptionType.SoapException);
}
else
{
HandleWebServicesException(WebServicesExceptionType.WebException);
}
}
catch (Exception)
{
HandleWebServicesException(WebServicesExceptionType.Exception);
}
return data;
}
On the server side, every incoming request is first verified whether it is signed and encrypted. Also, we check whether it is signed by a security context token. When all these conditions are met, we use the same security context token to sign and encrypt the response SOAP message: [WebMethod(Description="Synchronize data by" +
" send and recieving from the remote client.")]
public IVDataSet SendReceiveIssues(DataSet changedIssues, DateTime lastAccessed)
{
SoapContext requestContext = RequestSoapContext.Current;
// Reject any requests which are not valid SOAP requests
Wse2HelperServer.VerifyMessageParts(requestContext);
Wse2HelperServer.VerifyMessageSignature(requestContext);
Wse2HelperServer.VerifyMessageEncryption(requestContext);
// Check if the Soap Message is Signed with an SCT.
SecurityContextToken sct =
Wse2HelperServer.GetSigningToken(requestContext)
as SecurityContextToken;
if (sct == null)
{
throw new SoapException("The request is not signed with an SCT.",
SoapException.ServerFaultCode, "Security");
}
// Use the SCT to sign and encrypt the response
SoapContext responseContext = ResponseSoapContext.Current;
responseContext.Security.Tokens.Add(sct);
responseContext.Security.Elements.Add(new MessageSignature(sct));
responseContext.Security.Elements.Add(new EncryptedData(sct));
return new IVData().SendReceiveIssues(changedIssues, lastAccessed);
}
To monitor the actual encrypted and signed SOAP messages sent between the client and the web server, we can check the input and output message trace files InputTrace.webinfo and OutputTrace.webinfo on either the client or the web server side. Further Work
History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||