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:
')
- Signature - contains the signature data, including the signature itself and the certificate.
- SignedInfo - contains information about the data to be signed and algorithms that will be used when generating a signature.
- CanonicalizationMethod - specifies the canalization algorithm that will be applied to SignedInfo before calculating the signature.
- SignatureMethod - specifies the algorithm used to generate and validate the signature. The algorithm receives the caned-localized SignedInfo tag.
- Reference can occur 1 or more times. Contains information about the data being signed, including the location of the data in the document, the algorithm for calculating the hash from the data, the transformation, and the hash itself.
- Transforms and Transform transform data. The input of the first transform comes the result of dereferencing the URI attribute of the Reference tag. The result of the previous one comes to the input of each subsequent transform, the result of the last transform comes to the input of the algorithm specified in the DigestMethod. Usually in transform specify XPath, defining protected parts of the document.
- DigestMethod is a hash calculation algorithm for Transforms results.
- DigestValue is a hash value from Transforms results. Often this is a hash of the data referenced by the Reference URI.
- SignatureValue is the signature itself, for the formation of which everything is being started.
- KeyInfo - information about the key; here you are interested in the X509Certificate tag, which contains the base64encoded certificate from the key with which the data is signed.
So, the source data:
- on the server we have an xml document to be signed. I also used CryptoPro .Net on the server, but it can be done without it.
- on the client, we need an OS that supports working with CryptoPro CSp 3.6 (in my case it was Windows 7), a browser that supports working with CryptoPro Pro EDS Browser Plugin, and, actually, the key that we are going to sign (in my case, the key was on the flash drive) .
Client preparation:
- install CryptoPro CSP 3.6
- Installing CryptoPro EDS Browser Plugin
- Install the certificate from the USB flash drive in the local storage (see instructions
Clause 2.5.2.2. Installing a personal certificate stored in a private key container ")
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 # 1The algorithm is as follows (taken
from here with additions. In the disputed places left the original text):
- the first character "<", the last ">".
- all intermediate spaces inside tags are preserved (original All leading space characters inbetween are retained.)
- view elements
<tag />
replace with <tag></tag>
- end of lines replace with LF (0x0A).
- expose namespace xmlns = " www.w3.org/2000/09/xmldsig# " to the SignedInfo tag (The English namepace The xmlns = " www.w3.org/2000/09/xmldsig# " .)
- tag attributes must be arranged alphabetically within tags (this problem manifested itself when generating a signature containing SignatureProperties)
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();
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;
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 1Step 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.