рдореИрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддрд╛ рд╣реВрдБ рдХрд┐ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдЕрдзрд┐рдХрд╛рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЖрдк рдХрдЬрд╛рдЦрд╕реНрддрд╛рди рдЧрдгрд░рд╛рдЬреНрдп рдХреЗ рдЗрд▓реЗрдХреНрдЯреНрд░реЙрдирд┐рдХ рд╕рд░рдХрд╛рд░реА рдкреЛрд░реНрдЯрд▓реЛрдВ рджреНрд╡рд╛рд░рд╛ рдЙрддреНрдкрдиреНрди рджрд╕реНрддрд╛рд╡реЗрдЬреЛрдВ рдореЗрдВ рдирд┐рд╣рд┐рдд рдХреНрдпреВрдЖрд░ рдХреЛрдб рд╕реЗ рдбреЗрдЯрд╛ рдХрд╛ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдФрд░ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, https://egov.kz )ред
рдЗрд▓реЗрдХреНрдЯреНрд░реЙрдирд┐рдХ рджрд╕реНрддрд╛рд╡реЗрдЬреЛрдВ рдореЗрдВ, рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╢рдмреНрджрд╛рдВрдХрди рдореМрдЬреВрдж рд╣реИ:
* рдмрд╛рд░рдХреЛрдб рдореЗрдВ рдЬреАрдбреАрдмреА рдкреАрдПрдЪ рдХреА рд╕реВрдЪрдирд╛ рдкреНрд░рдгрд╛рд▓реА рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдбреЗрдЯрд╛ рд╣реЛрддрд╛ рд╣реИ рдФрд░ рд╕реНрдЯреЗрдЯ рдХреЙрд░рдкреЛрд░реЗрд╢рди рдХреА рд╢рд╛рдЦрд╛ тАЬрдирд╛рдЧрд░рд┐рдХ рдХреЗ рд▓рд┐рдП рд╕рд░рдХрд╛рд░тАЭ NAO рдХреЗ рдПрдХ рдЗрд▓реЗрдХреНрдЯреНрд░реЙрдирд┐рдХ рдбрд┐рдЬрд┐рдЯрд▓ рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХреЗ рд╕рд╛рде рд╣рд╕реНрддрд╛рдХреНрд╖рд░рд┐рдд рд╣реЛрддрд╛ рд╣реИред
рдЬрд╣рд╛рдБ рддрдХ рдореБрдЭреЗ рдкрддрд╛ рд╣реИ, рдХреНрдпреВрдЖрд░ рдХреЛрдб рдореЗрдВ рдбреЗрдЯрд╛ рдирд┐рдХрд╛рд▓рдиреЗ рдФрд░ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдХреЗ рд▓рд┐рдП рдХреЛрдИ рддреИрдпрд╛рд░ рдЙрдкрдХрд░рдг рдирд╣реАрдВ рд╣реИрдВред
рдореЗрд░рд╛ рдЕрдВрддрд┐рдо рд▓рдХреНрд╖реНрдп рд╣рд╕реНрддрд╛рдХреНрд╖рд░рд┐рдд рдбреЗрдЯрд╛ рдФрд░ рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдирд┐рдХрд╛рд▓рдирд╛ рд╣реИ, рд╣рд╕реНрддрд╛рдХреНрд╖рд░рд┐рдд рдбреЗрдЯрд╛ рдХреА рдЕрдЦрдВрдбрддрд╛ рдХреЛ рд╕рддреНрдпрд╛рдкрд┐рдд рдХрд░рдирд╛ред рдЗрд╕ рдиреЛрдЯ рдореЗрдВ рдХреЗрд╡рд▓ рдПрдХ рд╣реИрд╢ рдХреА рдкреБрд╖реНрдЯрд┐ рдХрд░рддреЗ рд╣реБрдП, рдбрд┐рдЬрд┐рдЯрд▓ рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХреЛ рд╕рддреНрдпрд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреА рдХреЛрдИ рдмрд╛рдд рдирд╣реАрдВ рд╣реЛрдЧреАред рдпрджрд┐ рдЗрд╕ рд╡рд┐рд╖рдп рдореЗрдВ рдЬрдирддрд╛ рд░реБрдЪрд┐ рджрд┐рдЦрд╛рддреА рд╣реИ, рддреЛ рднрд╡рд┐рд╖реНрдп рдореЗрдВ рдбрд┐рдЬрд┐рдЯрд▓ рд╣рд╕реНрддрд╛рдХреНрд╖рд░реЛрдВ рдХреЗ рд╕рддреНрдпрд╛рдкрди рдХрд╛ рд╡рд┐рд╡рд░рдг рд╡рд░реНрдгрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред
рдорд╣рддреНрд╡рдкреВрд░реНрдг: рдпрд╣ рдиреЛрдЯ рд╣реИрдХрд┐рдВрдЧ рддрдХрдиреАрдХреЛрдВ рдХрд╛ рд╡рд░реНрдгрди рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ рдФрд░ рдбреЗрдЯрд╛ рддрдХ рдЕрдирдзрд┐рдХреГрдд рдкрд╣реБрдВрдЪ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдореЗрдВ рдорджрдж рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ; рд╣рдо рдбреЗрдЯрд╛ рдХреЛ рдПрдХ рджреГрд╢реНрдп рд╕реЗ рджреВрд╕рд░реЗ рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реЗрдВрдЧреЗред
рдореИрдВ рджрд┐рдЦрд╛рдКрдВрдЧрд╛ рдХрд┐ рдореВрд▓ рдкреАрдбреАрдПрдл рдлрд╛рдЗрд▓реЛрдВ рдХреЛ рдХреИрд╕реЗ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдЬреЛ рдХрдЬрд╛рдХрд┐рд╕реНрддрд╛рди рдЧрдгрд░рд╛рдЬреНрдп рдХреА рдЗрд▓реЗрдХреНрдЯреНрд░реЙрдирд┐рдХ рд╕рд░рдХрд╛рд░ рдХреЗ рдбрд╛рдЙрдирд▓реЛрдб рдкреЛрд░реНрдЯрд▓реНрд╕ рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИред рдЗрди рдкреАрдбреАрдПрдл рдлрд╛рдЗрд▓реЛрдВ рдореЗрдВ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдПрдореНрдмреЗрдбреЗрдб рдЫрд╡рд┐рдпреЛрдВ рдХреЗ рд░реВрдк рдореЗрдВ рдХреНрдпреВрдЖрд░ рдХреЛрдб рд╣реЛрддреЗ рд╣реИрдВред
рдореИрдВ рдмрд┐рдирд╛ рдХрд┐рд╕реА рдЖрдкрд░рд╛рдзрд┐рдХ рд░рд┐рдХреЙрд░реНрдб рдХреЗ рдкреНрд░рдорд╛рдг рдкрддреНрд░ рдкрд░ рдкреНрд░рдпреЛрдЧ рдХрд░реВрдВрдЧрд╛ред
рдореИрдВ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реВрдВрдЧрд╛:
- PDF.js рдПрдХ рдкреАрдбреАрдПрдл рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рд╕реЗ рдЫрд╡рд┐рдпреЛрдВ рдХреЛ рдирд┐рдХрд╛рд▓рдиреЗ рдХреЗ рд▓рд┐рдП;
- QR рдХреЛрдб рдбрд┐рдХреЛрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП jsQR ;
- рдЬрд╝рд┐рдк рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рдЕрдирдкреИрдХ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП JSZip ;
- XML рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд▓рд┐рдП XMLDSIGjs ;
- WebCrypto GOST (gostCrypto) рдХрдВрдкреНрдпреВрдЯрд┐рдВрдЧ рд╣реИрд╢ рдФрд░ рдПрдиреНрдХреЛрдбрд┐рдВрдЧ / рдбрд┐рдХреЛрдбрд┐рдВрдЧ рдбреЗрдЯрд╛ рдХреЗ рд▓рд┐рдПред
0. ArrayBuffer рдореЗрдВ рдкреАрдбреАрдПрдл рдлрд╛рдЗрд▓ рдкрдврд╝реЗрдВ
рдЯреИрдЧ <input type="file">
рдФрд░ рдЗрд╕рдХреА рд╡рд┐рд╢реЗрд╖рддрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдорд╛рдирдХ HTML рдЯреВрд▓ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкреАрдбреАрдПрдл рдлрд╛рдЗрд▓ рддрдХ рдкрд╣реБрдВрдЪ рд╕рдВрднрд╡ рд╣реИ files
ред
рдЖрдзреБрдирд┐рдХ рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдореЗрдВ, рдЖрдк рдПрдХ рдлрд╝рд╛рдЗрд▓ рдХреА рд╕рд╛рдордЧреНрд░реА ArrayBuffer
рдЗрд╕ рдкреНрд░рдХрд╛рд░ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ :
const fileContents = await fileInput.files[0].arrayBuffer();
1. рдПрдХ рдкреАрдбреАрдПрдл рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рд╕реЗ рдЪрд┐рддреНрд░ рдирд┐рдХрд╛рд▓реЗрдВ
PDF.js , https://mozilla.imtqy.com/pdf.js/examples/index.html#interactive-examples
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
PDF.js . , :
const ops = [
pdfjsLib.OPS.paintJpegXObject,
pdfjsLib.OPS.paintImageXObject,
];
PDF :
const loadingTask = pdfjsLib.getDocument(fileContents);
const pdf = await loadingTask.promise;
const objIDs = [];
const images = [];
await (async function () {
for (let pageIndex = 1; pageIndex <= pdf.numPages; pageIndex += 1) {
const page = await pdf.getPage(pageIndex);
const operators = await page.getOperatorList();
for (let i = 0; i < operators.fnArray.length; i++) {
const fn = operators.fnArray[i];
if (ops.indexOf(fn) !== -1) {
const objID = operators.argsArray[i][0];
if (objIDs.indexOf(objID) === -1) {
objIDs.push(objID);
try {
const imageInfo = page.objs.get(objID);
images.push(imageInfo);
} catch (err) {
console.log(err);
}
}
}
}
}
})()
2. QR
jsQR RGBA PDF RGB, RGB RGBA:
function extractRGBAData(image) {
if (image.kind === 3) {
return image.data;
}
if (image.kind !== 2) {
throw new Error(`Image kind "${image.kind}" is not supported.`);
}
const data = new Uint8ClampedArray(image.width * image.height * 4);
let destPosition = 0;
for (let srcPosition = 0; srcPosition < image.data.length;) {
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = 255;
}
return data;
}
:
const qrCodes = [];
images.forEach((image) => {
if (image.data) {
const data = extractRGBAData(image);
try {
const code = jsQR(data, image.width, image.height);
console.log(code);
qrCodes.push(code);
} catch (err) {
console.log(err);
}
}
});
7 тАФ QR . URL тАФ QR , . 6 XML ( ):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<BarcodeElement xmlns="http://barcodes.pdf.shep.nitec.kz/">
<creationDate>...</creationDate>
<elementData>...</elementData>
<elementNumber>1</elementNumber>
<elementsAmount>6</elementsAmount>
<FavorID>...</FavorID>
</BarcodeElement>
:
<elementData>...</elementData>
тАФ<elementNumber>1</elementNumber>
тАФ<elementsAmount>6</elementsAmount>
тАФ
3.
:
const qrCodesBlocks = [];
function addQRCodeBlock(code) {
if (!code || !code.data) {
return;
}
const elementsAmountRegexp = /<elementsAmount>((.|\r|\n)+?)<\/elementsAmount>/;
const elementsAmountResult = elementsAmountRegexp.exec(code.data);
if (!elementsAmountResult || elementsAmountResult.length <= 2) {
return;
}
const elementsAmount = +elementsAmountResult[1];
if (!Number.isSafeInteger(elementsAmount)) {
throw new Error(' <elementsAmount>');
}
if (qrCodesBlocks.length === 0) {
for (let i = 0; i < elementsAmount; i++) {
qrCodesBlocks.push('');
}
} else {
if (qrCodesBlocks.length !== elementsAmount) {
throw new Error(` QR QR : "${qrCodesBlocks.length}" "${elementsAmount}"`);
}
}
const elementNumberRegexp = /<elementNumber>((.|\r|\n)+?)<\/elementNumber>/;
const elementNumberResult = elementNumberRegexp.exec(code.data);
if (!elementNumberResult || elementNumberResult.length < 2) {
throw new Error(` QR "<elementNumber>"`);
}
const elementNumber = +elementNumberResult[1];
if (!Number.isSafeInteger(elementNumber)) {
throw new Error(`"<elementNumber>" QR `);
}
if (elementNumber > elementsAmount) {
throw new Error(` QR "${elementNumber}" QR "${elementsAmount}"`);
}
if (qrCodesBlocks[elementNumber - 1] !== '') {
throw new Error(` QR "${elementNumber}" `);
}
const elementDataRegexp = /<elementData>((.|\r|\n)+?)<\/elementData>/;
const elementDataResult = elementDataRegexp.exec(code.data);
if (!elementDataResult || elementDataResult.length < 2) {
throw new Error(' QR "<elementData>"');
}
const elementData = elementDataResult[1];
qrCodesBlocks[elementNumber - 1] = elementData;
}
.
qrCodes.forEach(addQRCodeBlock);
if (qrCodesBlocks.length === 0) {
throw new Error(' QR : QR ');
}
const foundBlocks = qrCodesBlocks.filter((block) => !!block);
if (qrCodesBlocks.length !== foundBlocks.length) {
throw new Error(' QR : QR ');
}
4.
ZIP Base64.
Base64:
const zippedParts = qrCodesBlocks.map(block => new Uint8Array(gostCrypto.coding.Base64.decode(block)));
:
const totalLength = zippedParts.reduce((accumulator, part) => accumulator + part.length, 0);
const zippedData = new Uint8Array(totalLength);
let zippedDataIndex = 0;
zippedParts.forEach((part) => {
zippedData.set(part, zippedDataIndex);
zippedDataIndex += part.length;
});
:
const zip = await JSZip.loadAsync(zippedData, { checkCRC32: true });
one
, :
const file = zip.file('one');
if (!file) {
throw new Error(' "one"');
}
const recoveredContents = await file.async("string");
5.
тАФ XML ( ):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p1001Response>
<SystemInfo>
<messageId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<chainId>...</chainId>
<messageDate>...</messageDate>
`1`
<responseInfoRu> </responseInfoRu>
<responseInfoKz> </responseInfoKz>
<digiSign>...</digiSign>
</SystemInfo>
<ResponseData>
<ResponseType>UNJUDGED</ResponseType>
<Person>
<IIN>...</IIN>
<SurName>...</SurName>
<Name>...</Name>
<MiddleName>...</MiddleName>
<BirthDate>...</BirthDate>
<BirthPlace>
<Country>...</Country>
<CountryKz>...</CountryKz>
<District>...</District>
<DistrictKz>...</DistrictKz>
<City>...</City>
<CityKz>...</CityKz>
<Locality>...</Locality>
<LocalityKz>...</LocalityKz>
</BirthPlace>
</Person>
<Untried/>
<CheckDate>...</CheckDate>
</ResponseData>
</p1001Response>
digiSign
тАФ XML Base64. , .
XML:
const regexp = /<digiSign>((.|\r|\n)+?)<\/digiSign>/;
const regexpResult = regexp.exec(recoveredContents);
if (!regexpResult && regexpResult.length !== 2) {
throw new Error(' XML "<digiSign>"');
}
const digiSignBytes = gostCrypto.coding.Base64.decode(regexpResult[1]);
const xmlDataAndSignature = gostCrypto.coding.Chars.encode(digiSignBytes, 'utf8');
6.
XML ( ):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ResponseData>
<ResponseType>UNJUDGED</ResponseType>
<Person>
<IIN>...</IIN>
<SurName>...</SurName>
<Name>...</Name>
<MiddleName>...</MiddleName>
<BirthDate>...</BirthDate>
<BirthPlace>
<Country>...</Country>
<CountryKz>...</CountryKz>
<District>...</District>
<DistrictKz>...</DistrictKz>
<City>...</City>
<CityKz>...</CityKz>
<Locality>...</Locality>
<LocalityKz>...</LocalityKz>
</BirthPlace>
</Person>
<Untried/>
<CheckDate>...</CheckDate>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gost34310-gost34311"/>
<ds:Reference URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gost34311"/>
<ds:DigestValue>...</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</ResponseData>
XML , , <ResponseType>UNJUDGED</ResponseType>
, <Person>...</Person>
.
7.
XML .
XML :
const xml = XmlDSigJs.Parse(xmlDataAndSignature);
<ds:DigestValue>...</ds:DigestValue>
:
const xmlSignatures = XmlDSigJs.Select(xml, "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']");
if (xmlSignatures.length === 0) {
throw new Error(` ( "<Signature>"): "${xmlDataAndSignature}"`);
}
if (xmlSignatures.length > 1) {
throw new Error(` ( "<Signature>"): "${xmlDataAndSignature}"`);
}
const hashElementsInSignature = XmlDSigJs.Select(xmlSignatures[0], "//*[local-name(.)='DigestValue']");
if (hashElementsInSignature.length === 0) {
throw new Error(` XML ( "<DigestValue>"): "${xmlDataAndSignature}"`);
}
if (hashElementsInSignature.length > 1) {
throw new Error(` XML ( "<DigestValue>"): "${xmlDataAndSignature}"`);
}
const hashInSignature = hashElementsInSignature[0].textContent;
тАФ <ds:Transforms>...</ds:Transforms>
XML :
const xmlDsigEnvelopedSignatureTransform = new XmlDSigJs.XmlDsigEnvelopedSignatureTransform();
xmlDsigEnvelopedSignatureTransform.LoadInnerXml(xml.documentElement);
xmlDsigEnvelopedSignatureTransform.GetOutput();
const xmlDsigC14NWithCommentsTransform = new XmlDSigJs.XmlDsigC14NWithCommentsTransform();
xmlDsigC14NWithCommentsTransform.LoadInnerXml(xml.documentElement);
const signedDataXML = xmlDsigC14NWithCommentsTransform.GetOutput();
const dataToHash = gostCrypto.coding.Chars.decode(signedDataXML, 'utf8');
"http://www.w3.org/2001/04/xmldsig-more#gost34311"
, 34.311-95 GOST R 34.11-94
gostCrypto
. D-TEST
.
:
const hashBytes = await gostCrypto.subtle.digest({name: 'GOST R 34.11-94', version: 1994, sBox: 'D-TEST'}, dataToHash);
const signedDataXMLHash = gostCrypto.coding.Base64.encode(hashBytes);
:
if (signedDataXMLHash !== hashInSignature) {
throw new Error(` XML "${signedDataXMLHash}" "${hashInSignature}"`);
}
, . , тАФ , XML .
, : XML , digiSign
Base64, HTML , . .
: