Analyse du contenu des codes QR dans les documents du gouvernement électronique de la République du Kazakhstan dans le frontend

Je vais vous montrer comment en utilisant JavaScript directement dans le navigateur, vous pouvez extraire et analyser des données à partir de codes QR contenus dans des documents générés par les portails électroniques du gouvernement de la République du Kazakhstan (par exemple, https://egov.kz ).


Dans les documents électroniques, la formulation suivante est présente:


* le code-barres contient des données reçues du système d'information du GDB PH et signées avec une signature numérique électronique de la Branche de la Corporation d'État «Government for Citizens» NAO.

Pour autant que je sache, il n'y a pas d'outils prêts à l'emploi pour extraire et analyser les données dans les codes QR.


Mon but ultime est d'extraire les données signées et la signature, de vérifier l'intégrité des données signées. Il ne sera pas question de vérifier une signature numérique dans cette note, seulement de vérifier un hachage. Les détails de la vérification des signatures numériques pourront être décrits à l'avenir, au cas où le public s'intéresserait à ce sujet.


Important: cette note ne décrit pas les techniques de piratage et n'aide pas à obtenir un accès non autorisé aux données; nous parlerons de la conversion des données d'une vue à une autre.


Je vais montrer comment traiter les fichiers PDF originaux qui se forment et fournir des portails de téléchargement du gouvernement électronique de la République du Kazakhstan. Ces fichiers PDF contiennent des codes QR sous forme d'images intégrées distinctes.


.


:



0. PDF ArrayBuffer


PDF HTML <input type="file"> files.


ArrayBuffer :


const fileContents = await fileInput.files[0].arrayBuffer();

1. PDF


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) { // ImageKind.RGBA_32BPP  https://github.com/mozilla/pdf.js/blob/master/src/shared/util.js
    return image.data;
  }

  if (image.kind !== 2) { // ImageKind.RGB_24BPP  https://github.com/mozilla/pdf.js/blob/master/src/shared/util.js
    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 , . .


:



All Articles