📜 ⬆️ ⬇️

Mixed (client / server) xmlDsig digital signature generation algorithm based on CryptoPro Browser Plugin

On Habré, there was already an overview article about the mechanisms for creating EDS in the browser, where it was told about the Krypto Pro CSP + bundle of their own browser plug-in. As it was said there, the preliminary requirements for work are the presence of a CryptoPro CSP on a computer and the installation of a certificate with which we are going to sign. The option is quite working, moreover, in version 1.05.1418 of the plug-in, work with the XMLDsig signature has been added. If there is an opportunity to drive files to the client and back, then in order to sign the document on the client, it is enough to read CryptoRead help. Everything is done in javascript by calling a couple of methods.
However, what if the files are on the server and you want to minimize the traffic and sign them without driving the client entirely?
Interesting?
So, the client / server XMLDSig digital signature generation algorithm.
Information about the specification on XMLDsig can be found at the address here .
I will consider the formation of an enveloping signature for an xml document.
A simple example of signed xml:

<MyTestXml> <MySomeData>....</MySomeData> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /> <SignatureMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102001-gostr3411" /> <Reference URI=""> <Transforms> <Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"> <XPath xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">not(ancestor-or-self::dsig:Signature)</XPath> </Transform> </Transforms> <DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr3411" /> <DigestValue>...</DigestValue> </Reference> </SignedInfo> <SignatureValue>...</SignatureValue> <KeyInfo> <X509Data> <X509Certificate>...</X509Certificate> </X509Data> </KeyInfo> </Signature> </MyTestXml> 


To better understand what an enveloping signature is, I offer a brief translation of the description of tags from the specification:
')


So, the source data:



Client preparation:



Step number 1. (server)
Prepare a signature template for the document that we are going to sign.
At this stage, we need to obtain a Signature tagging with calculated hashes (DigestValue) from the protected data. The algorithm for manually calculating these hashes is described in detail here , but since we bought CryptoPro .Net in our office and based on it we wrote an internal library for working with signatures, I simply signed the document on the server with another key using this library, and as a result I got the necessary I have a template with calculated hashes from the data, but with invalid SignatureValue and X509Certificate.

Step number 2. (server)
We can canonize SignedInfo, formed in step # 1
The algorithm is as follows (taken from here with additions. In the disputed places left the original text):


C # code that worked in my case:
  XmlNode xmlNode = xmlElement.GetElementsByTagName("SignedInfo")[0]; XmlDocument xmlDocumentSignInfo = new XmlDocument(); xmlDocumentSignInfo.PreserveWhitespace = true; xmlDocumentSignInfo.LoadXml(xmlNode.OuterXml); result = Canonicalize(xmlDocumentSignInfo); 


Where:

  public string Canonicalize(XmlDocument document) { XmlDsigExcC14NTransform xmlTransform = new XmlDsigExcC14NTransform(); xmlTransform.LoadInput(document); string result = new StreamReader((MemoryStream)xmlTransform.GetOutput()).ReadToEnd(); //C#      XPath  result = s.Replace("<XPath>", "<XPath xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">"); return result ; } 


Step number 3.
We take hash from kanokalizirovanny SignedInfo.
There are 2 options, server and client.
3.1) Hash capture on client. That is what I use, so I will describe it first:
On the server, encode the SignedInfo to base64
C #:
  string b64CanonicalizeSignedInfo= Convert.ToBase64String(Encoding.UTF8.GetBytes(s)); 

and send this data to the client.
On the client, we take the hash with the help of the cryptographic plugin
Javascript:

  var CADESCOM_HASH_ALGORITHM_CP_GOST_3411 = 100; var CADESCOM_BASE64_TO_BINARY = 1; var hashObject = CreateObject("CAdESCOM.HashedData"); hashObject.Algorithm = CADESCOM_HASH_ALGORITHM_CP_GOST_3411; hashObject.DataEncoding = CADESCOM_BASE64_TO_BINARY; hashObject.Hash(hexCanonicalSignedInfo); 


You can view the hash using hashObject.Value
3.2) We consider the hash on the server and send it to the client. This option did not work for me, but to be honest, I did not really try.

We take hash (server C #):
  HashAlgorithm myhash = HashAlgorithm.Create("GOST3411"); byte[] hashResult = myhash.ComputeHash(anonicalSignedInfoByteArr); 

Perhaps the hash should be converted to base64.

We send to the client, there we use
 var hashObject = CreateObject("CAdESCOM.HashedData"); hashObject.SetHashValue(hashFromServer); 

It was on the hashObject.SetHashValue method that I had an error. I did not understand, but a crypto forum was told that it was possible to somehow make it work.

If you are thinking of implementing a server-side algorithm for generating a hash, then here are a couple of useful tips:
1) Calculate the hash on the client and on the server from an empty string. it must match, it means your algorithms are the same.
For GOST3411 these are the following values:
base64: mB5fPKMMhBSHgw + E + 0M + E6wRAVabnBNYSsSDI0zWVsA =
hex: 98 1e 5f 3c a3 0c 84 14 87 83 0f 84 fb 43 3e 13 ac 11 01 56 9b 9c 13 58 4a c4 83 23 4c d6 56 c0
2) Ensure that you have hashes for arbitrary data generated on the client and on the server.
After that, you can only send the hash from SignedInfo instead of the entire SignedInfo to the client.

Step number 4. (client)
Generate SignatureValue and send to the SignatureValue server and certificate information.
 var certNumber=2; //      var CAPICOM_CURRENT_USER_STORE = 2; var CAPICOM_MY_STORE = "my"; var CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED = 2; var oStore = CreateObject("CAPICOM.Store"); oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED); var certificate=oStore.Certificates.Item(certNumber) var rawSignature = CreateObject("CAdESCOM.RawSignature"); var signatureHex = rawSignature.SignHash(hashObject, certificate); // base64   var binReversedSignatureString = utils.reverse(utils.hexToString(signatureHex)); var certValue = certificate.Export(certNumber); 


We return to the server binReversedSignatureString and certValue.

The code of functions from utils I do not post. I was prompted to him on the cryptoPro forum and can be viewed in this thread.

Step number 5. (server)
Replace the Signature tag values ​​of the SignatureValue and X509Certificate tags with the values ​​received from the client in the Signature tag generated in Step 1

Step number 6. (server)
We verify the card.
If the verification was successful, then everything is fine. As a result, we get a document signed with a client key on the server, without going back and forth the file itself.

Note: if work is carried out with a document that already contains signatures, they should be disconnected from the document before step No. 1 and attached to the document back after step No. 6

In conclusion, I would like to say a big thank you for helping us find the algorithm for the participants of the CryptoPro forum by dmishin and Fomich.
Without their advice I would flop with it many times longer.

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


All Articles