
Rutoken WEB solution allows you to implement strong authentication for web-resources using an electronic signature according to GOST R 34-10.2001. More details about the algorithms can be found in
this article . Here we show how the current use of Rutoken WEB is made on sites running Asp.net and provide assembly instructions.
Making everything work is really easy.
')
Rutoken WEB solution consists of the following components:
- USB-token Rutoken WEB (no driver installation required)
- client cross-platform multi-browser plugins
- client scripts to work with the plugin
- server components
Browser plugin can be downloaded
here . It remains to make the server component, client-side javascript, and put it all together.
Authentication algorithm
User authentication, regardless of platform, implies the presentation of a certain subject identifier, identifier verification and access decision making. For example, presenting a login with a password, checking data on the database, and setting an authenticating cookie if successful.
In our case, the identifier will be the EDS generated on the client. We will check the correctness of the data signature. Upon successful verification, we consider authentication to be successful. In general, we use the classic handshake.
Algorithm implementation
Training.In order for the server to verify the signature, it needs to know the client's public key.
Therefore, first of all we will create a container containing a key pair on the client, in its device Rutoken WEB, and transmit to the server the public key and the unique identifier of the device Rutoken WEB. The private key is not retrievable, respectively, does not leave the device.
We call the container not anyhow either, but according to the {login} #% # {sitename} {port} scheme. For example, yandex@gmail.com #% # dotnet.rutokenweb.ru:80. The name will be used later when displaying the list of logins on the token.
On the server, we get the public key and token id and bind them to an existing client. We need to know who will try to gain access.
The preparation phase is complete, you can authenticate clients.
Authentication.- The client sends a request to the server containing an identifier and a sign that it is necessary to authenticate.
- The server generates random data, such as a string; hashes the data, stores it in the session and sends it to the client. Let's call this data s1.
- The client receives the hash of the data, generates its random data (s2), forms the hash of the sum of rows and signs the given hash (we obtain the digital signature). Further, the client sends to the server the data that it generated itself (s2) and the EDS sums of rows.
- The server receives random client data (s2) and EDS, similarly generates a hash of the sum of random client data (s2) and data generated at the beginning by the server (s1).
- As a result, the server has data (hash s1 + s2) and the signature of this data. It remains only to verify the correctness of the signature.
C # implementation example
In my case, Rutoken WEB authentication needed to be screwed to 3 sites. 2 of them use Forms authentication, another one works with Windows Identity Foundation, uses STS service for authentication. All three sites are powered by WebForms.
Let's make for them
WebControl with the necessary functionality, in fact, two controls. One will be used during authentication, the other will be used to control the Rutoken WEB bindings, for example, in a personal account.
All requests to the server will be ajax requests, without a full postback. Thus, controls are needed, by and large, for presenting necessary elements and javascript on the page, and httpHandler will
handle the processing of Ajax requests. He will give the client localized javascript.
And, finally, with the rest of the site, the control and the handler will interact with the help of an object that implements the
ITokenProcessor interface, where the specific site-specific methods needed by our solution are declared. For example, getting a public key, getting a username and so on.
Schematically, it all looks like this:

Preparation for authentication, as already mentioned, is reduced to the formation of a WEB container with a private and public key on Rutoken and transfer to the server the public key and the token id, with data binding to the user account. This operation should be available to already authenticated users, and the control itself with functionality can be placed, for example, in a personal account. This will be handled by the control with the rare name Administration, and the control with the name Login will deal with the authentication process.
HttpHandler implementation
Tasks of our handler:
1. Handle ajax client request with Rutoken WEB.Handler will process ajax requests only with headers known to it ('X-Requested-With', 'XhrRutoken').
Let's make a class to parse the request (CMessageRequest) and a class to form a response (CMessageResponse). When requesting, create an instance of the class to parse the request, assigning it to the handler member. Analysis takes place in the constructor.
_mRequest = new CMessageRequest(context);
The request passes the name of the method, which we run, if we find, of course, with reflection.
GetType().InvokeMember(_mRequest.act, BindingFlags.InvokeMethod, null, this, new object[] {});
In the method the request is processed, as a result we create an instance of the class with the answer. Eventually, the response is serialized in json and transmitted in Response.
2. Give localized javascript to the page.Javascript is added to the page like this -
<script src=" /RutokenWebSTS/rutokenweb/ajax.rtw?getRutokenJavaLocal=1" type="text/javascript"></script>
The markup gives the control (about this below). In the case of a request with getRutokenJavaLocal = 1, we again use our handler, this time to return javascript.
All javascript added to the assembly as Embedded Resource. It would be limited to simply adding a resource. Initially it was. But then a customer from White Arabian appeared and wanted the possibility of localization. Therefore, we add not a simple, but a golden, localized version, like this:
private void SendLocalizeScript() { using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream( "RutokenWebPlugin.javascript.tokenadmin.js")) { if (stream != null) { var reader = new StreamReader(stream); HttpContext.Current.Response.Write(Utils.LocalizeScript(reader.ReadToEnd())); } } }
LocalizeScript produces the already localized version of the script, for which the script parsit and outputs the line we need, replacing all occurrences of LOCALIZE (resource_key) with a line from the resource file RutokenLocalText.resx
private static Regex REGEX = new Regex(@"LOCALIZE\(([^\))]*)\)", RegexOptions.Singleline | RegexOptions.Compiled); … public static string LocalizeScript(string text) { var matches = REGEX.Matches(text); foreach (Match match in matches) { string strResourceStringID = match.Groups[1].Value; string str = (string)HttpContext.GetGlobalResourceObject("RutokenLocalText", strResourceStringID) ?? strResourceStringID; text = str != strResourceStringID ? text.Replace(match.Value, MakeValidString(str)) : text.Replace(match.Value, string.Format("'LOCALIZE.{0}'", str)); } return text; }
Resources are in the source code of the example.
ITokenController
Our controls and handler will interact with the site / application via the ITokenController interface. Interface methods are described in detail in the source code. They implement site-specific functionality. For example, getting / saving keys, getting user name, etc.
To make it work, an object that implements this interface must be passed to the control method, for example:
class CustomTokenProcessor : ITokenProcessor ...
the method actually puts the object into the session
public void SetRequired(ITokenProcessor processor, string successurl) { var session = HttpContext.Current.Session; if (session != null) { if (session["TokenProcessor"] == null) { session["TokenProcessor"] = processor; } session["SuccessUrl"] = successurl; } }
And the object becomes available to the handler.
The event handler also uses event OnSuccessAuth, which is triggered by successful authentication. And you can subscribe to an event in the control, and not in the handler. This is done so that you can access the session in the method that is added to the event. In this case, the object is transferred to the session
if ((OnSuccessAuth = (EventHandler) _mContext.Session["OnSuccessAuth"]) != null) { OnSuccessAuth(_mContext.Session, new EventArgs()); _mContext.Session["OnSuccessAuth"] = null; }
And in a method we receive session so
private void tokenlogin_OnSuccessAuth(object sender, EventArgs e) { HttpSessionState session = (HttpSessionState) sender; if (session != null) {
Implementation of controls.
For a start we will make the parent of both controls. Its main tasks are as follows:
1. Providing the ability to set a TemplateDo so
[TemplateContainer(typeof (AdministrationData)), TemplateInstance(TemplateInstance.Single)] public virtual ITemplate Template { get; set; }
2. Adding to the page an object for working with Rutoken WEBWorking with a browser plugin is reduced to calls to methods of a specially declared object. Declared in this format:
<object id="cryptoPlugin" type="application/x-rutoken" width="0" height="0"></object>
for this in onLoad control we do
private void EnsureRutokenPlugin() { var rtObject = new HtmlGenericControl("object") {ClientIDMode = ClientIDMode.Static, ID = JStokenObjectID}; rtObject.Attributes.Add("type", "application/x-rutoken"); rtObject.Attributes.Add("width", "0"); rtObject.Attributes.Add("height", "0"); var rtParam = new HtmlGenericControl("param") {TagName = "onload"}; rtParam.Attributes.Add("value", "pluginit"); rtObject.Controls.Add(rtParam);
There is one feature here. The plug-in object should not be in a hidden element whose display: none; for example, and then refuses to work. And we will post it in PlaceHolder, which we will announce specifically for this on the main page with our controls. If masterpage is used, then on it, and immediately after the Form tag.
<form id="form1" runat="server"> <asp:PlaceHolder ID="tokenPlaceHolder" runat="server"></asp:PlaceHolder>
This will avoid inadvertently hitting the token object in the hidden element of the page.
Now we will deal with the implementation of controls - heirs. One to manage tokens, the second to authenticate clients. The controls are templated, so they need to set up markup on the page, and certain template markers with certain names must be present in the template. Buttons, labels, etc. Availability is checked in the code.
In both controls, override CreateChildControls:
protected override void CreateChildControls() { if (Template != null)
Next, in the method, we will find buttons, tables, etc., set properties for them if necessary. For example, the token binding button:
var rtwConnect = (Button)administrationData.FindControl("rtwConnect");
and also add variables to the page - pointers to these dom objects, as properties of the global javascript variable $ grd_ctrls
IdToJavaScript(rtwConnect, JScontrolVar, "rtwConnect", Page); public static void IdToJavaScript(Control ctrl, string jsvar, string field, Page page) { page.ClientScript.RegisterStartupScript(typeof (Control), field, jsvar + "." + field + " = rtwGID('" + ctrl.ClientID + "'); ", true); }
So, we will have the necessary markup and links to these markup elements as properties of $ grd_ctrls.
Consider the control templates:
Administration
The layout of this control is quite cumbersome. But all the data is there.
<token:Administration ID="backoffice" runat="server" Port="12345"> <template> <label> :</label> <asp:GridView runat="server" ID="rtwEnable" CssClass="OrdersGr" AutoGenerateColumns="False" GridLines="None" ShowHeaderWhenEmpty="False"> <EmptyDataTemplate> </EmptyDataTemplate> <Columns> <asp:TemplateField HeaderText="Token Id"> <ItemTemplate> <%# ((uint)Container.DataItem) %> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText=""> <ItemTemplate> <asp:Label ID="rtwEnabledToken" runat="server"></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText=""> <ItemTemplate> <asp:Button runat="server" ID="rtwEnableSwitch" OnClientClick="return false;"/> <asp:Button ID="rtwRemove" runat="server" Text=" " OnClientClick="return false;" ClientIDMode="Predictable"/> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> <br /> <label> Web:</label> <asp:Button ID="rtwConnect" runat="server" Text=" "/> <br /> <asp:Image ID="rtwAjaxImg" runat="server" ImageUrl="~/ajax_loader.gif" /> <br /> <asp:Label ID="rtwErrorMessage" runat="server" CssClass="errLabel" /> <asp:Label ID="rtwMessage" runat="server" CssClass="status ok" /> </template> </token:Administration>
In principle, here, only a table with tokens, buttons for linking, unlinking and switching tokens, as well as two spans for informational messages and error messages.
Data for the table of tokens is given by the method of the ITokenController interface GetUserTokens
Login, Remember
Control for authentication or access recovery. Recovery is possible
without using a token , you need to enter your login and recovery code specified on the Rutoken WEB card (supplied with the token)
Example of markup login:
<aktivlogin:Login ID="tokenlogin" runat="server" LoginType="Login"> <Template> <asp:Literal ID="rtwUsers" runat="server" /> <asp:Label ID="rtwErrorMessage" runat="server" CssClass="rutoken error" style="display: block; color: #c00;" /> <asp:Label ID="rtwMessage" runat="server" CssClass="rutoken message" style="display: block; color: green;" /> <asp:Button ID="rtwLogin" runat="server" OnClientClick="return false;" Text="" style="margin-top:12px;" /> <asp:Image ID="rtwAjaxImg" runat="server" ImageUrl="~/ajax_loader.gif" /> </Template> </aktivlogin:Login>
There is a Literal control that will produce select as a result. It would be possible to use DropDownList, but in the select we will javascript add the list of logins on the token and if there is a postback, the EventValidation of the page will be cursed. In order not to turn it off, let's draw select ourselves.
rtwUsers.Text = "<select id=\"rtwUsers\"></select>";
Example of access recovery markup:
<aktivlogin:Login ID="tokenlogin" runat="server" LoginType="Remember"> <Template> : <asp:TextBox ID="rtwRepairUser" runat="server" /><br /> : <asp:TextBox ID="rtwRepair" runat="server" /><br /> <asp:Label ID="rtwErrorMessage" runat="server" style="display: block; color: #c00;" /> <asp:Label ID="rtwMessage" runat="server" style="display: block; color: green;" /> <asp:Button ID="rtwRepairBtn" runat="server" OnClientClick="return false;" Text="" style="margin-top:12px;" /> <asp:Image ID="rtwAjaxImg" runat="server" ImageUrl="/ajax_loader.gif" /> </Template> </aktivlogin:Login>
As you can see, they differ by specifying LoginType = Login or Remember.
Javascript
The main javascript is located in tokenadmin.js, given by the handler. The script connects the elements of the user interface, plugin and server.
The interface elements are bound to the properties of the global variable $ grd_ctrls, bind in the code of the controls, placing the variables on the page using page.ClientScript.RegisterStartupScript. The plugin object is $ grd_ctrls.token.
Tokenadmin.js does the following: at the beginning we check if the plugin is available and if there is a token (if it is a login). Then we do the processing of user requests with callbacks. For example, during authentication, the script first reads all the logins on the token and adds them to select (rtwUsers).
var containerCount = g.token.rtwGetNumberOfContainers(); for (i = 0; i < containerCount; i++) { var contName = g.token.rtwGetContainerName(i); g.rtwUsers.options[i] = new Option(contName.replace("#%#", " - "), contName); }
The user selects the desired login and presses the "Login" button.
We send to the server a request with the rnd command and id token. If everything is ok, we get in response json of the form
{"Text": "94156e9a6642d42a47fc94c6f4b1b8c000dab4bfd24f321f5976e4d3a5a4e994", "type": "Notify"}
This is a sequence generated by the server, to which, according to the algorithm, we need to add our random data. The callback function generates this data, makes a concatenation with what the server has sent, considers the hash and signs it in the browser plugin. Signing data requires entering a pincode. The user enters a pin. If everything is OK and the pin is correct, send a signature and random data to the server. The server performs string concatenation and signature verification. If the signature is correct, we get the answer:
{"Text": "True", "type": "Notify", "url": "\ / RutokenWebSTS \ / Admin \ /"}
Along with the answer, an authentication cookie should also arrive, so we redirect the user to the url sent. Authentication passed.
Signature verification
All cryptography delivered in a separate dll. Three methods look outside:
- Random hash generation
- String hash calculation
- Signature checks
You can make your own implementation of the authentication algorithm using only this build.
And finally a short assembly instruction.
(.net 4.0, tested under iis 7.5)
1. Add the RutokenWebPlugin.dll and Rutoken.dll assemblies to the project
2. Add httpHandler to Web.config
<system.webServer> <handlers> <add name="AjaxHandler" path="/RutokenWebSTS/rutokenweb/ajax.rtw" verb="*" type="RutokenWebPlugin.TokenAjaxHandler" resourceType="Unspecified" requireAccess="Script" preCondition="integratedMode" /> </handlers> </system.webServer>
Path must end with '/rutokenweb/ajax.rtw'. If the site / application is installed in a virtual directory, as in the example above, include it in the path.
And if necessary, you must make the handler available to everyone.
<location path="rutokenweb"> <system.web> <authorization> <allow users="*" /> </authorization> </system.web> </location>
3. We implement the ITokenProcessor interface
public class CustomTokenProcessor : ITokenProcessor { …..
The most crucial moment, an example implementation with comments is in the source
4. Add a control to manage tokens (personal account)
<%@ Register TagPrefix="token" Namespace="RutokenWebPlugin" Assembly="RutokenWebPlugin" %>
And the control template (an example of the template was in the article)
<token:Administration ID="backoffice" runat="server" Port="12345" > <Template> …..
We specify the port if the application does not work on port 80.
5. In the codebehind of the administration control we add an object that implements ITokenProcessor
protected override void OnInit(EventArgs e) { base.OnInit(e);
6. Add control for authentication to login page
<%@ Register TagPrefix="aktivlogin" Namespace="RutokenWebPlugin" Assembly="RutokenWebPlugin" %>
and his pattern
<aktivlogin:Login ID="tokenlogin" runat="server" SuccessUrl="http://localhost/Secured/" LoginType="Login"> <Template> …….
8. In the Codebehind control with login, add an object that implements ITokenProcessor
protected override void OnInit(EventArgs e) { base.OnInit(e);
After that, the login page will appear on the login page, and in your account there will be the ability to manage tokens - attach to your account, switch activity. What was required to do.
The above example will not be difficult to modify to fit your needs, or you can use the signature-checking library and do everything from scratch.
The sample sources with a test site and a database script can be downloaded
here.