📜 ⬆️ ⬇️

Authentication via ESIA OAuth2

In this article I will write how I screwed ESIA to the site, other scenarios are possible.

The first step is to create a certificate for generating a signature in pfx format with a private key.
For this, I used the "PFX Certificate Generator". In the settings, service information and information for hashing are specified: sha256, key length 2048.

After that, you need to install the certificate. Add a snap for certificates. open the local storage branch, then import our generated certificate there. Then copy it to the Trusted Root branch.
After that, you need to put permission to it. In the “Personal” branch we find our imported certificate, right-click on it and select “All tasks” then “Manage private keys”. Next, add permissions to the group “All”.
')
After that, it is necessary to export our certificate in the cer format and transfer it to the ESIA, there it will be registered.

Now let's start writing the code. I used NancyFx. So…

public class EsiaModule : NancyModule { static string client_id = "123456"; // ,      static string state = Guid.NewGuid().ToString("D"); //    static string server_url = "https://esia.gosuslugi.ru/aas/oauth2/ac"; //       static string server_url_2 = "https://esia.gosuslugi.ru/aas/oauth2/te"; //     static string server_url_prns = "https://esia.gosuslugi.ru/rs/prns/"; //          //  /* static string server_url = "https://esia-portal1.test.gosuslugi.ru/aas/oauth2/ac"; static string server_url_2 = "https://esia-portal1.test.gosuslugi.ru/aas/oauth2/te"; static string server_url_prns = "https://esia-portal1.test.gosuslugi.ru/rs/prns/";*/ public EsiaModule() { Get["/ESIA"] = _ => { //string scope = "openid"; string scope = "fullname"; string timestamp = DateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000"); string access_type = "online"; string response_type = "code"; string redirect_uri = Request.Url.Scheme + "://" + Request.Url.HostName + (Request.Url.Port == 80 ? "" : (":" + Request.Url.Port.ToString())) + "/ESIA-OK"; // ,         string client_secret = ""; //      string msg = scope + timestamp + client_id + state; byte[] msgBytes = Encoding.UTF8.GetBytes(msg); var signerCert = DetachedSignature.GetSignerCert(); byte[] encodedSignature = DetachedSignature.SignMsg(msgBytes, signerCert); client_secret = HttpServerUtility.UrlTokenEncode(encodedSignature); //    RequestBuilder builder = new RequestBuilder(); builder.AddParam("client_id", client_id); builder.AddParam("client_secret", client_secret); builder.AddParam("redirect_uri", redirect_uri); builder.AddParam("scope", scope); builder.AddParam("response_type", response_type); builder.AddParam("state", state); builder.AddParam("timestamp", timestamp); builder.AddParam("access_type", access_type); string red_url = server_url + "?" + builder.ToString().Replace("+", "%2b"); return Response.AsRedirect(red_url); }; Get["/ESIA-OK"] = _ => { //   ,   state      ,   code string state_r = Request.Query["state"]; string code = Request.Query["code"]; if (state == state_r) { //string scope = "openid"; string scope = "fullname"; string timestamp = DateTime.UtcNow.ToString("yyyy.MM.dd HH:mm:ss +0000"); string redirect_uri = Request.Url.Scheme + "://" + Request.Url.HostName + (Request.Url.Port == 80 ? "" : (":" + Request.Url.Port.ToString())) + "/ESIA-OK"; string client_secret = ""; string msg = scope + timestamp + client_id + state; byte[] msgBytes = Encoding.UTF8.GetBytes(msg); var signerCert = DetachedSignature.GetSignerCert(); byte[] encodedSignature = DetachedSignature.SignMsg(msgBytes, signerCert); client_secret = HttpServerUtility.UrlTokenEncode(encodedSignature); string result; // post     { RequestBuilder builder = new RequestBuilder(); builder.AddParam("client_id", client_id); builder.AddParam("code", code); builder.AddParam("grant_type", "authorization_code"); builder.AddParam("client_secret", client_secret); builder.AddParam("state", state); builder.AddParam("redirect_uri", redirect_uri); builder.AddParam("scope", scope); builder.AddParam("timestamp", timestamp); builder.AddParam("token_type", "Bearer"); var httpWebRequest = (HttpWebRequest)WebRequest.Create(server_url_2); httpWebRequest.ContentType = "application/x-www-form-urlencoded"; httpWebRequest.Method = "POST"; httpWebRequest.Timeout = int.MaxValue; httpWebRequest.Proxy = p; using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream())) { streamWriter.Write(builder.ToString().Replace("+", "%2b")); streamWriter.Flush(); streamWriter.Close(); } var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) { result = streamReader.ReadToEnd(); } } ESIA_Marker_Answer marker = JsonConvert.DeserializeObject<ESIA_Marker_Answer>(result);//  string[] marker_parts = marker.access_token.Split('.'); string header = Encoding.UTF8.GetString(base64urldecode(marker_parts[0])); string payload = Encoding.UTF8.GetString(base64urldecode(marker_parts[1])); string oid = (JsonConvert.DeserializeObject<dynamic>(payload))["urn:esia:sbj_id"]; //       string user_info = ""; { var httpWebRequest = (HttpWebRequest)WebRequest.Create(server_url_prns + oid); httpWebRequest.ContentType = "application/x-www-form-urlencoded"; httpWebRequest.Method = "GET"; httpWebRequest.Headers["Authorization"] = "Bearer " + marker.access_token; httpWebRequest.Timeout = int.MaxValue; var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse(); using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) { user_info = streamReader.ReadToEnd(); } } string firstName = JsonConvert.DeserializeObject<dynamic>(user_info)["firstName"]; string lastName = ""; try { lastName = JsonConvert.DeserializeObject<dynamic>(user_info)["lastName"]; } catch { } string middleName = ""; try { middleName = JsonConvert.DeserializeObject<dynamic>(user_info)["middleName"]; } catch { } //             MyEntities db = new MyEntities(); Helper h = new Helper(); users u; if (db.users.Any(a => a.esia_oid.Trim() == oid.Trim())) { u = db.users.FirstOrDefault(a => a.esia_oid.Trim() == oid.Trim()); } else { //create user u = new users { mail = oid, name = firstName, pass = h.HashWithSalt(oid), patronymic = middleName ?? "", surname = lastName ?? "", token = Guid.NewGuid().ToString("N"), role_id = 2, esia_oid = oid }; db.users.Add(u); db.SaveChanges(); } //      DateTime expires = DateTime.UtcNow.AddYears(20); List<NancyCookie> cs = new List<NancyCookie> { new NancyCookie("id", u.id.ToString(), false) { Expires = expires }, new NancyCookie("token", u.token, false) { Expires = expires } }; var resp = Response.AsRedirect("/"); resp.AddCookie(cs[0]); resp.AddCookie(cs[1]); return resp; } else { return Response.AsJson(new { err = 1 }); } }; } static byte[] base64urldecode(string arg) { string s = arg; s = s.Replace('-', '+'); // 62nd char of encoding s = s.Replace('_', '/'); // 63rd char of encoding switch (s.Length % 4) // Pad with trailing '='s { case 0: break; // No pad chars in this case case 2: s += "=="; break; // Two pad chars case 3: s += "="; break; // One pad char default: throw new System.Exception( "Illegal base64url string!"); } return Convert.FromBase64String(s); // Standard base64 decoder } } public class RequestBuilder { List<RequesItemClass> items = new List<RequesItemClass>(); public void AddParam(string name, string value) { items.Add(new RequesItemClass { name = name, value = value }); } override public string ToString() { return string.Join("&", items.Select(a => a.name + "=" + a.value)); } } class DetachedSignature { static public X509Certificate2 GetSignerCert() { X509Store storeMy = new X509Store(StoreName.My, StoreLocation.LocalMachine); storeMy.Open(OpenFlags.ReadOnly); X509Certificate2Collection certColl = storeMy.Certificates.Find(X509FindType.FindBySubjectKeyIdentifier, "12 34 56 78 90 00 00 00 11 11 11 11 11 11 11 11 11 22 33 44", false); storeMy.Close(); return certColl[0]; } static public byte[] SignMsg(Byte[] msg, X509Certificate2 signerCert) { ContentInfo contentInfo = new ContentInfo(msg); SignedCms signedCms = new SignedCms(contentInfo, true); CmsSigner cmsSigner = new CmsSigner(signerCert); signedCms.ComputeSignature(cmsSigner); return signedCms.Encode(); } static public bool VerifyMsg(Byte[] msg, byte[] encodedSignature) { ContentInfo contentInfo = new ContentInfo(msg); SignedCms signedCms = new SignedCms(contentInfo, true); signedCms.Decode(encodedSignature); try { signedCms.CheckSignature(true); } catch (System.Security.Cryptography.CryptographicException e) { return false; } return true; } } public class ESIA_Marker_Answer { public string state { get; set; } public string token_type { get; set; } public int expires_in { get; set; } public string refresh_token { get; set; } public string id_token { get; set; } public string access_token { get; set; } } public class RequesItemClass { public string name; public string value; } 


That's basically it. One request, redirect + 2 requests to get a token and user information.
In addition, you can verify the signature of the marker.

Source: https://habr.com/ru/post/276313/


All Articles