pühapäev, 10. veebruar 2019

ASP.NET digiallkirjastamine IIS veebiserveris

Mõnikord siis vaja ka ASP.NET veebirakendusse digiallkirjastamine lisada, üks võimalus siis nii, et teed digidoc4j põhjal Java Spring Boot rakenduse mille käivitad Windows Serveris kui Wrapper teenuse.

Sellest juhendist Windows Service Wrapper täitsa töötab
https://www.baeldung.com/spring-boot-app-as-a-service

Idee siis selline, et näidisrakendust https://github.com/rvillido/digidoc4j-hwcrypto-demo
on muudetud nii, et Java osas toimub signeerimine ja valideerimine aga andmete salvestamine on Java Spring Boot rakendusest välja toodud ja tehakse ASP.NET ja MS SQL SERVER-i sees
https://github.com/open-eid/digidoc4j/wiki/Examples-of-using-it

Java Spring Boot rakendus asub siin, kõigepealt tuleb serverisse Java 1.8 paigaldada
https://github.com/kuidokylm/KampusBdoc

Kui saad selle Windows Service Wrapperi abil paigaldatud siis ava Windows Serveris sirvik ja vaata, mis seal http://localhost:8083 vastu vaatab, peaks tulema selline pilt


Java Spring Boot töötab GET ja POST päringute töötlemisega URL-i pihta, ehk serveri peal
sirviku http://localhost:8083/port päring peaks andma sellise vastuse


Kõik test URL-id leiab  Java @RestController public class TestController-ist


Digiallkirjastamise jaoks tuleb sõlmida SK ID Solutions leping Ajatempliteenuse kehtivuskinnituse kasutamiseks, kui see on tehtud ja IP aadress avatud siis serveri peal http://localhost:8083/refreshtsl


peab tagastama ok, ehk tehakse TSL(trusted sources list) nimekirja värskendus. Edasi on juba ASP.NET koodi osa, ehk lisad projekti ASMX veebiteenuse, näites on nimi bdoc.asmx

ja tema code-behind koodi päisesse

[System.Web.Script.Services.ScriptService]
public class bdoc : System.Web.Services.WebService

ehk lubad JavaScriptil/jQueryl ASP.NET veebiteenusega suhelda.

ASMX veebiteenuse meetod Port, mis tootab nii, et ASP.NET koodis veebilehel pöördutakse jQuery kaudu ASMX meetodi Port poole, mis teeb päringu Java Spring Boot Service Wrapperisse http://localhost:8083/port, saab sealt vastuse ja tagastab tagasi veebilehele

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
    public void Port()
    {
        string responseFromServer = "";
        string url = "http://localhost:8083/port";     
        try
        {
            WebRequest request = WebRequest.Create(url);
            ((HttpWebRequest)request).UserAgent = "Kampus Web";
            WebResponse response = request.GetResponse();
            Stream dataStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(dataStream);
            responseFromServer = reader.ReadToEnd();

            reader.Close();
            dataStream.Close();
            response.Close();

        }
        catch (WebException ex)
        {
            responseFromServer = ex.Message;
            if (ex.InnerException != null)
            {
                responseFromServer += (" " + ex.InnerException.Message);
            }
        }
        catch (SystemException ex)
        {
            responseFromServer = ex.Message;
        }
        Digest vastus = new Digest();
        vastus.result = responseFromServer;
        vastus.hex = responseFromServer;
        responseFromServer = Newtonsoft.Json.JsonConvert.SerializeObject(vastus);
        Context.Response.Clear();
        Context.Response.ContentType = "application/json;charset=UTF-8";  //see peab olema, muidu ei hakka jquery front-endis tööle, vajab JSON formaati
        Context.Response.AddHeader("content-length", responseFromServer.Length.ToString());
        Context.Response.Flush();
        Context.Response.Output.Write(responseFromServer);
    }

jQuery väljakutse ASPX lehe peal olevast HTML ButtonPort input nupust

input type="button" id="ButtonPort" class="actionButton" value="Kontrolli ASMX BDOC"

Päring töötab nii, ehk

1. Sirvikust jQuery pöördumine ASP.NET /bdoc.asmx/Port meetodisse
2. ASMX meetod Port teeb serveris päringu Java Spring Boot http://localhost:8083/port aadressil
3. Saadud vastuse tagastab ASMX meetod Port sirviku jQueryle


$("#ButtonPort").on("click", function () {
 console.log("Port ");                 
 $.get("/bdoc.asmx/Port", function (data, status) {
 console.log("Port " + data.hex);
 $("#<%=LabelBdoc.ClientID %>").text(status);
 alert("Data: " + data.hex + "\nStatus: " + status);
});
});



Sama Java Spring Boot http://localhost:8083/port väljakutse serveri poolel, ehk ASP.NET code-behind nupu Click meetod kus serveri back-end pöördub otse Java Spring Boot Windows Service wrapperi poole

protected void ButtonBDOC_Click(object sender, EventArgs e)
    {
        string url = "http://localhost:8083/port";
        this.LabelBdoc.Visible = true;
        //https://docs.microsoft.com/en-us/dotnet/framework/network-programming/how-to-request-data-using-the-webrequest-class
        try
        {
            WebRequest request = WebRequest.Create(url);
            ((HttpWebRequest)request).UserAgent = "Kampus WebAdmin ButtonBDOC_Click";
            WebResponse response = request.GetResponse();
            Stream dataStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(dataStream);
            string responseFromServer = reader.ReadToEnd();
            this.LabelBdoc.Text = responseFromServer;
            reader.Close();
            dataStream.Close();
            response.Close();

        }
        catch (WebException ex)
        {
            this.CustomValidator1.ErrorMessage = ex.Message;
            this.CustomValidator1.IsValid = false;
            if (ex.InnerException != null)
            {
                this.LabelBdoc.Text = url + " " + ex.InnerException.Message;
            }
        }
        catch (SystemException ex)
        {
            this.CustomValidator1.ErrorMessage = url + " " + ex.Message;
            this.CustomValidator1.IsValid = false;
        }
    }


Kaks võimalust, kuidas Java Spring Boot Windows Service wrapperi kaudu suhelda, kas front-endis kus pöördutakse läbi ASMX veebimeetodi või server-side kus minnakse otse.

Kui on vaja ASP.NET koodist ASMX veebimeetodile Session -i kaudu väärtust edasi anda
siis ASMX meetodi päisesse lisad [WebMethod(EnableSession = true)]

[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public void generateFileHash(string certInHex)
{
int failiId = Convert.ToInt32(HttpContext.Current.Session["bdocid"].ToString());
}

nii saad ASP.NET code-behind seatud Session muutuja väärtuse kätte ASMX veebimeetodis

Kui näiteks on GridView nimekiri failidest mida vaja digiallkirjastada




asp:Button ID="BtnSigneeri"  Text="Digiallkirjasta leping" CausesValidation="false" CommandName="Signeeri" CommandArgument='<%# Eval("ID") %>' runat="server"


protected void GridViewFailid_RowCommand(object sender, GridViewCommandEventArgs e)
    {
        if (e.CommandName.Equals("Signeeri") )
        {
            this.Session["bdocid"] = e.CommandArgument.ToString();  //faili ID
            Button butt = (Button)e.CommandSource;
            if (!this.Page.ClientScript.IsClientScriptBlockRegistered("signeeriDokument()"))
            {
                //kuna oleme updatepaneli sees, tuleb seda ScriptManager.RegisterStartupScript meetodit kasutada
                //kutsume välja selle nupuga seotud javascripti
                ScriptManager.RegisterStartupScript(butt, typeof(Button), butt.ClientID, "signeeriDokument()", true);         
            }
        }

Ülal olevas koodis on ka näidatud, kuidas saab serveri poolt code-behind välja kutsuda JavaScripti meetodit
ScriptManager.RegisterStartupScript(butt, typeof(Button), butt.ClientID, "signeeriDokument()", true);

javascripti meetod oma korda kutsub välja jQuery funktsioonide promised

function signeeriDokument() {
    console.log("signeeriDokument klikiti");
    signfile();
}

signfile() on jQuery funktsioonide komplekt promiseid, kus post funktsiooni sees url: "/bdoc.asmx/" + url määratakse ära väljakutsutav ASMX veebiteenuse meetod

$(function () {

    post = function (url, data) {
        console.log("post query:" + url);
        return new Promise(function (resolve, reject) {
            $.ajax({
                url: "/bdoc.asmx/" + url,
                type: "POST",
                data: data
            }).done(function (data) {
                console.log("post done data.result " + data.result);
                if (data.result !== "ok") {
                    $("#LabelBdoc").text(data.result);
                    reject(Error(data.result))
                } else {
                    resolve(data);
                }
            }).fail(function () {
                reject(Error("Post operation failed"));
            });
        });
    };


    //olemasolevalt konteinerilt räsi küsimine
    getContainerToSign = function (certInHex) {
        console.log("getContainerToSign " + certInHex);
        return post("getContainerToSign", { certInHex: certInHex })
    };

    fetchFileHash = function (certInHex) {
        console.log("generateFileHash ");
        return post("generateFileHash", { certInHex: certInHex })
    };

    createContainer = function (signatureInHex) {
        console.log("createContainer " + signatureInHex);
        return post("createContainer", { signatureInHex: signatureInHex });
    };

    addSignToContainer = function (signatureInHex) {
        console.log("addSignToContainer " + signatureInHex);
        return post("addSignToContainer", { signatureInHex: signatureInHex });
    };


    signfile = function () {
        var cert;
        naitanupp("LabelBdoc");
        $("#LabelBdoc").text("Loen ID-kaardilt sertifikaati");
        console.log("enne signfile window.hwcrypto.getCertificate");
        window.hwcrypto.getCertificate({ lang: 'en' }).then(function (certificate) {
            cert = certificate;
            $("#LabelBdoc").text("Koostan räsi");
            console.log("getCertificate päring ");
            return fetchFileHash(certificate.hex);
        }).then(function (digest) {              
            console.log("digest " + digest.hex);
            $("#LabelBdoc").text("Signeerin");
            return window.hwcrypto.sign(cert, { type: 'SHA-256', hex: digest.hex }, { lang: 'en' });
        }).then(function (signature) {
            console.log("createcontainer");
            $("#LabelBdoc").text("Loon konteinerit");
            return createContainer(signature.hex);
        }).then(function (result) {
            console.log("container is ready for download " + result.result);                         
            $("#LabelBdoc").text(result.result);              
            window.location = window.location;
        }).catch(err => {
            $("#LabelBdoc").text(err);              
        });
    };
});

ASP.NET Labelil nimega LabelBdoc peab ClientIDMode="Static" olema määratud, et tema ID ei muutuks kompileerimise käigus jQuery kirjutab sinna sisse infot protsessi edenemise kohta $("#LabelBdoc").text("Signeerin");

asp:Label ID="LabelBdoc" CssClass="globalNote" ClientIDMode="Static" runat="server"

Kogu protsessi juhib jQuery signfile funktsioon, küsib ID kaardilt sertifikaadi, käib ASMX veebimeetoditelt küsimas faili räsi, signeerib selle ja lõpuks saadab päringuga andmed konteineri tegemiseks.

ASMX meetodid mis käivad JavaSpring Boot Windows Service wrapperisse andmeid saatmas ja salvestavad vastuvõetu SQL SERVER-i tabelitesse


[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public void generateFileHash(string certInHex)
    {
        Digest vastus = new Digest();
        byte[] byteArray = null;
        string fileName = "";
        int failiId =  Convert.ToInt32(HttpContext.Current.Session["bdocid"].ToString());     
        string contype = "";
        try
        {
            String connection = Configuration.ConnectionString;
            using (SqlConnection konn = new SqlConnection(connection))
            {
                SqlCommand komm = new SqlCommand("[dbo].[CONTRACT_FILE_KUIDO_S]", konn);
                komm.CommandType = CommandType.StoredProcedure;
                komm.Parameters.AddWithValue("@fileid", failiId);
                konn.Open();
                SqlDataReader red = komm.ExecuteReader();
                while (red.Read())
                {
                    fileName = red["FILE_NAME"].ToString();
                    contype = red["ADVERTISING_FILE_CONTENT_TYPE"].ToString();
                    byteArray = (byte[])red["FILE_CONTENT"];
                    break;
                }
                red.Close();
                konn.Close();
            }
            string requestURL = "http://localhost:8083/uploadforhash";
            WebClient wc = new WebClient();
            Dictionary<string, object> postParameters = new Dictionary<string, object>();
            // Add your parameters here
            postParameters.Add("file", new BdocUpload.FileParameter(byteArray, Path.GetFileName(fileName), contype));
            postParameters.Add("certInHex", certInHex);
            string userAgent = "KAMPUS generateFileHash";
            HttpWebResponse webResponse = BdocUpload.MultipartFormPost(requestURL, userAgent, postParameters, null, null);
            // Process response
            StreamReader responseReader = new StreamReader(webResponse.GetResponseStream());
            string responseFromServer = responseReader.ReadToEnd();
            webResponse.Close();
            DigestObject djson = JsonConvert.DeserializeObject<DigestObject>(responseFromServer);
            vastus.hex = djson.hex;
            vastus.result = djson.result;
            using (SqlConnection konn = new SqlConnection(connection)) //salvestame Conteineri sisu ajutiselt ära
            {
                SqlCommand komm = new SqlCommand("DBO.CONTRACT_FILE_BDOC_TEMP_U", konn);
                komm.CommandType = CommandType.StoredProcedure;
                komm.Parameters.AddWithValue("@fileid", Convert.ToInt32(this.Session["bdocid"].ToString()));
                komm.Parameters.AddWithValue("@tempdatatosign", djson.datatosign);
                komm.Parameters.AddWithValue("@tempcontainersisu", djson.container);
                konn.Open();
                komm.ExecuteNonQuery();
                konn.Close();
            }
        }
        catch (System.Web.HttpException ex)
        {
            vastus.result = ex.Message;
        }
        catch (SystemException ex)
        {
            vastus.result = ex.Message;
        }
        string jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(vastus);  //teeme vastuse json stringiks

        Context.Response.Clear();
        Context.Response.ContentType = "application/json;charset=UTF-8";  //see peab olema, muidu ei hakka jquery front-endis tööle, vajab JSON formaati
        Context.Response.AddHeader("content-length", jsonString.Length.ToString());
        Context.Response.Flush();
        Context.Response.Output.Write(jsonString);
    }

    [WebMethod(EnableSession = true)]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public void createContainer(string signatureInHex)
    {
        int failiId = Convert.ToInt32(HttpContext.Current.Session["bdocid"].ToString());
     
        byte[] datatosign = null;
        byte[] container = null;
        String connection = Configuration.ConnectionString;
        using (SqlConnection konn = new SqlConnection(connection))
        {
            SqlCommand komm = new SqlCommand("DBO.CONTRACT_FILE_KUIDO_S", konn);
            komm.CommandType = CommandType.StoredProcedure;
            komm.Parameters.AddWithValue("@fileid", failiId);
            konn.Open();
            SqlDataReader red = komm.ExecuteReader();
            while (red.Read())
            {
                container = (byte[])red["TEMPCONTAINERSISU"];
                datatosign = (byte[])red["TEMPDATATOSIGN"];
                break;
            }
            red.Close();
            konn.Close();
        }
        Digest vastus = new Digest();
        string requestURL = "http://localhost:8083/createContainer";
        WebClient wc = new WebClient();
        Dictionary<string, object> postParameters = new Dictionary<string, object>();
        // Add your parameters here  "tempcontainer", "bytearray" on koha täiteks
        postParameters.Add("file", new BdocUpload.FileParameter(container, "tempcontainer", "bytearray"));
        postParameters.Add("dfile", new BdocUpload.FileParameter(datatosign, "tempdatatosign", "bytearray"));
        postParameters.Add("signatureInHex", signatureInHex);
        string userAgent = "KAMPUS createContainer";
        HttpWebResponse webResponse = BdocUpload.MultipartFormPost(requestURL, userAgent, postParameters, null, null);
        // Process response
        StreamReader responseReader = new StreamReader(webResponse.GetResponseStream());
        string responseFromServer = responseReader.ReadToEnd();
        webResponse.Close();
        DigestObject djson = JsonConvert.DeserializeObject<DigestObject>(responseFromServer);
        vastus.result = djson.result;
        try
        {
            using (SqlConnection konn = new SqlConnection(connection)) //salvestame BDOC Conteineri sisu
            {
                SqlCommand komm = new SqlCommand("DBO.CONTRACT_FILE_BDOC_U", konn);
                komm.CommandType = CommandType.StoredProcedure;
                komm.Parameters.AddWithValue("@fileid", Convert.ToInt32(failiId));
                komm.Parameters.AddWithValue("@bdocfailisisutyyp", djson.hex);
                komm.Parameters.AddWithValue("@bdocfailisisu", djson.container);
                //komm.Parameters.AddWithValue("@tempcontainersisu", djson.datatosign);
                konn.Open();
                komm.ExecuteNonQuery();
                konn.Close();
            }
        }
        catch (SystemException ex)
        {
            vastus.result = ex.Message;
        }
        string jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(vastus);  //teeme vastuse json stringiks
        Context.Response.Clear();
        Context.Response.ContentType = "application/json;charset=UTF-8";  //see peab olema, muidu ei hakka jquery front-endis tööle, vajab JSON formaati
        Context.Response.AddHeader("content-length", jsonString.Length.ToString());
        Context.Response.Flush();
        Context.Response.Output.Write(jsonString);
    }

Java Spring Boot @RestController public class SigningController teeb ära digiallkirjastamise osad.


ASMX saadab faile Java Spring Boot Service Wrapperile multipart form data-na
https://www.c-sharpcorner.com/article/upload-any-file-using-http-post-multipart-form-data/

Selleks, et sirvikus jQuery saaks üldse teha päringuid ASMX veebimeetodite vastu tuleb web.config failis lubada vastavad veebiteenuste protokollid.



JSON andmevahtuseks ASMX ja Java Spring Booti vahel tuleb Java Spring Boot klassid kloonida ASP.NET rakendusse, Newtonsoft.Json package kasutamiseks

public class Signatuur
{
    public Signatuur()
    {
    }
    public string SubjectName { get; set; }
    public DateTime ClaimedSigningTime { get; set; }
    public String issuerName { get; set; }

    public String errors { get; set; }
}

public class Signatuurid
{
    public Signatuurid()
    {
    }
    public string result { get; set; }
    public List<Signatuur> signatuurid { get; set; }
}


public class DigestObject
{
    public DigestObject()
    {
    }

    public string result { get; set; }
    public string hex { get; set; }
    public byte[] datatosign { get; set; }
    public byte[] container { get; set; }
}