panzi / verify-ehc

Simple Python script to decode and verify an European Health Certificate QR-code
60 stars 17 forks source link

Running verify-ehc as web-service, provide a --server option #30

Closed bluepuma77 closed 2 years ago

bluepuma77 commented 2 years ago

It would be great to be able to use verify-ehc as a simple web service in a larger micro-service environment.

With a command line flag like --server 0.0.0.0:45678/validate it could open up a web-server and listen on IP 0.0.0.0, port 45678 and wait for a POST on /validate with the HC1:... string as payload, then send back the validation result, preferably in JSON.

The service could be easily packaged as a Docker container, just make sure to restart once in a while for fresh certificates.

I know this is asking a lot, I am just not a python developer, so it would probably take me a week to get this up and running, maybe you see this as a little coding challenge to improve your web-service skills :-)

panzi commented 2 years ago

Sorry, this is out of scope of this simple script. I have no interest in such a feature.

bluepuma77 commented 2 years ago

If someone needs a simple certificate check web-service, this may be a solution in NodeJS using Italian verificac19-sdk. It supports revoked certificates, but needs a MongoDB to run.

It is mainly for API calls curl -X POST -d 'HC1:...' server.tld/validate but also has a tiny GUI.

const express = require('express')
const bodyParser = require('body-parser')
const { Certificate, Service, Validator } = require('verificac19-sdk')

const app = express()
const port = 3000
app.use(bodyParser.text({ type: "*/*" }))

app.get('/', (req, res) => {
  let html = `<html>
    <body>
      <h1>Certificate Validator</h1>
      <p>Enter a certificate starting with <b>HC1:...</b></p>
      <textarea id="cert" cols="55" rows="15"></textarea>
      <br />
      <button onclick="clickValidate()">Validate</button>
      <pre id="result"></pre>
    </body>
    <script>
      function clickValidate() {
        document.getElementById('result').innerHTML = 'Waiting for response'
        var xhr = new XMLHttpRequest();
        xhr.onload = function() { document.getElementById('result').innerText = this.response };
        xhr.open("POST", '/validate');
        xhr.send(document.getElementById("cert").value);
      }
    </script>
  </html>`
  res.send(html)
})

app.post('/validate', async (req, res) => {
  try {
    cert = req.body
    if (!cert || !cert.startsWith('HC1:')) {
      return res.status(400).json({ error: { message: "Invalid certificate, needs to start with 'HC1:'" } })
    }

    const certificate = await Certificate.fromRaw(cert)
    const rules = await Validator.checkRules(certificate).result;
    const signature = await Validator.checkSignature(certificate);
    const validation = await Validator.validate(certificate)
    delete certificate.dcc

    res.setHeader('Content-Type', 'application/json');
    res.status(200).end(JSON.stringify({ certificate, rules, signature, validation }, null, 2) + '\n');
  } catch (err) {
    return res.status(400).json({ error: { message: err.message } })
  }
})

const run = async () => {
  console.log('Starting...');
  await Service.updateAll();

  app.listen(port, '0.0.0.0', () => {
    console.log(`App listening on port: ${port}`)
  })
}

run();

Example JSON response:

{
  "certificate": {
    "person": {
      "standardisedFamilyName": "Y",
      "familyName": "Y",
      "standardisedGivenName": "X",
      "givenName": "X"
    },
    "dateOfBirth": "1900-01-01",
    "vaccinations": [
      {
        "disease": "840539006",
        "vaccine": "J07BX03",
        "medicinalProduct": "EU/1/20/1528",
        "manufacturer": "ORG-100030215",
        "doseNumber": 2,
        "totalSeriesOfDoses": 2,
        "dateOfVaccination": "2021-10-01",
        "countryOfVaccination": "FR",
        "certificateIssuer": "CNAM",
        "certificateIdentifier": "URN:UVCI:01:FR:XY"
      }
    ],
    "kid": "XYFOjX/4aJs="
  },
  "signature": true,
  "validation": {
    "person": "X Y",
    "date_of_birth": "1900-01-01",
    "result": false,
    "code": "REVOKED",
    "message": "UVCI is in blacklist"
  }
}