Git Product home page Git Product logo

hcert-kotlin's Introduction

Electronic Health Certificate Kotlin Multiplatform Library

This library implements a very basic validation and creation chain for electronic health certificates (HCERT):

  • Encode in CBOR
  • Wrap in a CWT structure
  • Sign and embed in COSE
  • Compress with ZLib
  • Encode in Base45
  • Prepend with Context Identifier
  • Encode as QR Code

All services are implemented according to https://github.com/ehn-digital-green-development/hcert-spec, Version 1.0.5 from 2021-04-18.

The schemata for data classes are imported from https://github.com/ehn-digital-green-development/ehn-dgc-schema, up to Version 1.3.2, from 2022-07-07.

Several other git repositories are included as submodules. Please clone this repository with git clone --recursive or run git submodule init && git submodule update --recursive afterwards.

This Kotlin library is a mulitplatform project, with targets for JVM and JavaScript.

Usage (JVM)

The main class for encoding and decoding HCERT data is ehn.techiop.hcert.kotlin.chain.Chain. For encoding, pass an instance of a GreenCertificate (data class conforming to the DCC schema) and get a ChainResult. That object will contain all revelant intermediate results as well as the final result (step5Prefixed). This final result can be passed to a DefaultTwoDimCodeService that will encode it as a 2D QR Code.

Correct implementations of the service interfaces reside in ehn.techiop.hcert.kotlin.chain.impl. These "default" implementations will be used when the chain is constructed using DefaultChain.buildCreationChain() or DefaultChain.buildVerificationChain().

Example for creation services:

// Load the private key and certificate from somewhere ...
String privateKeyPem = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADAN...";
String certificatePem = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
CryptoService cryptoService = new FileBasedCryptoService(privateKeyPem, certificatePem);
Chain chain = DefaultChain.buildCreationChain(cryptoService); //optional custom prefix, e.g. "AT1:" to support AT-specific exemption certificates

// Load the input data from somewhere ...
String json = "{ \"sub\": { \"gn\": \"Gabriele\", ...";
GreenCertificate input = Json.Default.decodeFromString(GreenCertificate.Companion.serializer(), json);

// Apply all encoding steps from the Chain
ChainResult result = chain.encode(input);

// Optionally encode it as a QR-Code with 350 pixel in width and height
TwoDimCodeService qrCodeService = new DefaultTwoDimCodeService(350);
byte[] encodedImage = qrCodeService.encode(result.getStep5Prefixed());
String encodedBase64QrCode = Base64.getEncoder().encodeToString(encodedImage);

// Then include in an HTML page or something ...
String html = "<img src=\"data:image/png;base64," + encodedBase64QrCode + "\" />";

Example for the verification side, i.e. in apps:

// Load the certificate from somewhere ...
String certificatePem = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
CertificateRepository repository = new PrefilledCertificateRepository(certificatePem);
Chain chain = DefaultChain.buildVerificationChain(repository);  //optional parameter atRepository to verify vaccination exemptions (prefix AT1:) against

// Scan the QR code from somewhere ...
String input = "HC1:NCFC:MVIMAP2SQ20MU...";

DecodeResult result = chain.decode(input);
// Read metaInformation like expirationTime, issuedAt, issuer
VerificationResult verificationResult = result.getVerificationResult();
boolean isValid = verificationResult.getError() == null;
// See list below for possible Errors, may be null
Error error = verificationResult.getError();
// Result data may be null
GreenCertificate greenCertificate = result.getChainDecodeResult().getEudgc();

You may also load a trust list from a server, that contains several trusted certificates:

// PEM-encoded signer certificate of the trust list
String trustListAnchor = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
CertificateRepository trustAnchor = new PrefilledCertificateRepository(trustListAnchor);
// Download trust list content, binary, e.g. from https://dgc.a-sit.at/ehn/cert/listv2
byte[] trustListContent = new byte[0];
// Download trust list signature, binary, e.g. from https://dgc.a-sit.at/ehn/cert/sigv2
byte[] trustListSignature = new byte[0];
SignedData trustList = new SignedData(trustListContent, trustListSignature);
CertificateRepository repository = new TrustListCertificateRepository(trustList, trustAnchor);
Chain chain = DefaultChain.buildVerificationChain(repository);

// Continue as in the example above ..

Faulty Implementations

The usage of interfaces for all services (CBOR, CWT, COSE, ZLib, Context) in the chain may seem over-engineered at first, but it allows us to create wrongly encoded results, by passing faulty implementations of the service. Those services reside in a separate artifact named ehn.techiop.hcert:hcert-kotlin-jvmdatagen in the namespace ehn.techiop.hcert.kotlin.chain.faults and should, obviously, not be used for production code.

Sample data objects are provided in SampleData, with special thanks to Christian Baumann.

Debug Chain

In addition to the default (spec-compliant) verification behaviour, it is possible to continue verification even after certain errors. While a faulty encoding or garbled CBOR structure will still result in fatal errors, an expired certificate, or unknown KID, will not terminate the verification procedure, when using the debug chain. For details, see DebugChain.kt.

Anyonymising Personal Data (JVM only)

Both the ChainDecodeResult and the GreenCertificate classes allow for blanking personal information (name, date of birth), through the lazy-initialised anonymizedCopy property. For debugging purposes, the DecodeResult features a toJsonString(anonymize: Boolean) method.

NOTE: This is blanking of personal data is limited to humanly comprehensible representations of processed data. As such, even anonymised DecodeResults and ChainDecodeResults will contain unaltered QR code content, the vanilla CWT and so forth. All such unmodified data can thus be parsed without issue and will still yield all personal data.


DO LOG OR PROCESS THIS DATA, EVEN WHEN USING ANONYMISED COPIES! YOU HAVE BEEN WARNED.


Usage (JS)

The build result of this library for JS is a module in UMD format, located under build/distributions/hcert-kotlin.js. This script runs in a web browser environment and can be used in the following way (see demo.html). In addition, we also (experimentally) support node as target environment (also based on a bundled UMD) by passing the node flag to gradle (see the sample node project).

Build the module either for development or production for a browser target:

./gradlew jsBrowserDevelopmentWebpack
./gradlew jsBrowserProductionWebpack

Build the module either for development or production (NodeJS target):

./gradlew -Pnode jsBrowserDevelopmentWebpack
./gradlew -Pnode jsBrowserProductionWebpack

To verify a single QR code content:

// PEM-encoded DSC
let pemCert = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
// Would also accept more than one DSC
let verifier = new hcert.VerifierDirect([pemCert]); //optional second parameter: array of pem encoded certs to verify vaccination exemptions (prefix AT1:) against

// Scan the QR code from somewhere ...
let qr = "HC1:NCFC:MVIMAP2SQ20MU...";
let result = verifier.verify(qr);

let isValid = result.isValid;
// Read metaInformation like expirationTime, issuedAt, issuer
let metaInformation = result.metaInformation;
// See list below for possible Errors, may be null
let error = result.error;
// Result data may be null, contains decoded HCERT
let greenCertificate = result.greenCertificate;

An alternative way of initializing the Verifier is by loading a trust list:

// PEM-encoded signer certificate of the trust list
let trustListAnchor = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
// Download trust list content, binary, e.g. from https://dgc.a-sit.at/ehn/cert/listv2
let trustListContent = new ArrayBuffer(8);
// Download trust list signature, e.g. from https://dgc.a-sit.at/ehn/cert/sigv2
let trustListSignature = new ArrayBuffer(8);

let verifier = new hcert.VerifierTrustList(trustListAnchor, trustListContent, trustListSignature);  //optional isAT flag as fourth parameter to
                                                                                                    //update AT-specific trust ancors to verify
                                                                                                    //vaccination exemptions (prefix AT1:) against
// Continue with example above with verifier.verify()

If you want to save the instance of verifier across several decodings, you can update the TrustList afterwards, too:

// PEM-encoded signer certificate of the trust list
let trustListAnchor = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
// Download trust list content, binary, e.g. from https://dgc.a-sit.at/ehn/cert/listv2
let trustListContent = new ArrayBuffer(8);
// Download trust list signature, e.g. from https://dgc.a-sit.at/ehn/cert/sigv2
let trustListSignature = new ArrayBuffer(8);
verifier.updateTrustList(trustListAnchor, trustListContent, trustListSignature);

// Continue with example above with verifier.verify();

The meta information contains extracted data from the QR code contents, e.g.:

{
  "expirationTime": "2021-11-02T18:00:00Z",
  "issuedAt": "2021-05-06T18:00:00Z",
  "issuer": "AT",
  "certificateValidFrom": "2021-05-05T12:41:06Z",
  "certificateValidUntil": "2023-05-05T12:41:06Z",
  "certificateValidContent": [
    "TEST",
    "VACCINATION",
    "RECOVERY"
  ],
  "certificateSubjectCountry": "AT",
  "content": [
    "VACCINATION"
  ],
  "error": null
}

Encoding of HCERT data, i.e. generating the input for an QR Code, as well as the QR Code picture:

// Create a new, random EC key with 256 bits key size, i.e. on P-256
let generator = new hcert.GeneratorEcRandom(256);
// Provide valid HCERT data
let input = JSON.stringify({"ver": "1.2.1", "nam": { ... }});

// Get a result with all intermediate steps
let result = generator.encode(input);

// Print the contents of the QR code
console.info(result.step5Prefixed);

// Alternative: Get a data URL of the encoded QR code picture, e.g. "data:image/gif;base64,AAA..."
let moduleSize = 4;
let marginSize = 2;
let qrCode = generator.encodeToQrCode(input, moduleSize, marginSize);

You may also load a fixed key pair with certificate:

// PEM-encoded private key info, i.e. PKCS#8
let pemKey = "-----BEGIN PRIVATE KEY-----\nME0CAQAwE...";
// PEM-encoded certificate
let pemCert = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
// Load the private key and certificate
let generator = new hcert.GeneratorFixed(pemKey, pemCert);
// Provide valid HCERT data
let input = JSON.stringify({"ver": "1.2.1", "nam": { ... }});

// Continue with example above with generator.encode()

An alternative to calling verfiy(qr) is to call verifyDataClass(qr) which returns the greenCertificate as an JS object, like this:

{
  "schemaVersion": "1.0.0",
  "subject": {
    "familyName": "Musterfrau-Gößinger",
    "familyNameTransliterated": "MUSTERFRAU<GOESSINGER",
    "givenName": "Gabriele",
    "givenNameTransliterated": "GABRIELE"
  },
  "dateOfBirthString": "1998-02-26",
  "vaccinations": [
    {
      "target": {
        "key": "840539006",
        "valueSetEntry": {
          "display": "COVID-19",
          "lang": "en",
          "active": true,
          "system": "http://snomed.info/sct",
          "version": "http://snomed.info/sct/900000000000207008/version/20210131",
          "valueSetId": null
        }
      },
      "vaccine": {
        "key": "1119305005",
        "valueSetEntry": {
          "display": "SARS-CoV-2 antigen vaccine",
          "lang": "en",
          "active": true,
          "system": "http://snomed.info/sct",
          "version": "http://snomed.info/sct/900000000000207008/version/20210131",
          "valueSetId": null
        }
      },
      "medicinalProduct": {
        "key": "EU/1/20/1528",
        "valueSetEntry": {
          "display": "Comirnaty",
          "lang": "en",
          "active": true,
          "system": "https://ec.europa.eu/health/documents/community-register/html/",
          "version": "",
          "valueSetId": null
        }
      },
      "authorizationHolder": {
        "key": "ORG-100030215",
        "valueSetEntry": {
          "display": "Biontech Manufacturing GmbH",
          "lang": "en",
          "active": true,
          "system": "https://spor.ema.europa.eu/v1/organisations",
          "version": "",
          "valueSetId": "vaccines-covid-19-auth-holders"
        }
      },
      "doseNumber": 1,
      "doseTotalNumber": 2,
      "date": "2021-02-18T00:00:00.000Z",
      "country": "AT",
      "certificateIssuer": "BMSGPK Austria",
      "certificateIdentifier": "urn:uvci:01:AT:10807843F94AEE0EE5093FBC254BD813P"
    }
  ],
  "recoveryStatements": null,
  "tests": null,
  "dateOfBirth": "1998-02-26T00:00:00.000Z"
}

Debug Chain

In addition to the default (spec-compliant) verification behaviour, it is possible to continue verification even after certain errors. While a faulty encoding or garbled CBOR structure will still result in fatal errors, an expired certificate, or unknown KID, will not terminate the verification procedure, when using the debug chain. Simply add a true as the additional parameter to verifier constructor calls, such as new hcert.VerifierDirect([pemCert], true).

Errors

The field error in the resulting structure (DecodeResult) may contain the error code. The list of possible errors is the same as for ValidationCore:

  • GENERAL_ERROR:
  • INVALID_SCHEME_PREFIX: The prefix does not conform to the expected one, e.g. HC1:
  • DECOMPRESSION_FAILED: Error in decompressing the input
  • BASE_45_DECODING_FAILED: Error in Base45 decoding
  • COSE_DESERIALIZATION_FAILED: not used
  • CBOR_DESERIALIZATION_FAILED: Error in decoding CBOR or CWT structures
  • SCHEMA_VALIDATION_FAILED: Data does not conform to schema (on iOS, this is a CBOR_DESERIALIZATION_FAILED)
  • CWT_EXPIRED: Timestamps in CWT are not correct, e.g. expired before issuing timestamp
  • CWT_NOT_YET_VALID: Timestamps in CWT are not correct, e.g. issued after the current time
  • QR_CODE_ERROR: not used
  • CERTIFICATE_QUERY_FAILED: not used
  • USER_CANCELLED: not used
  • TRUST_SERVICE_ERROR: General error when loading Trust List or Business Rules
  • TRUST_LIST_EXPIRED: Trust List (or Business Rules) has expired
  • TRUST_LIST_NOT_YET_VALID: Trust List (or Business Rules) is not yet valid
  • TRUST_LIST_SIGNATURE_INVALID: Signature on Trust List (or Business Rules) is not valid
  • KEY_NOT_IN_TRUST_LIST: Certificate with KID not found
  • PUBLIC_KEY_EXPIRED: Certificate used to sign the COSE structure has expired
  • PUBLIC_KEY_NOT_YET_VALID: Certificate used to sign the COSE structure is not yet valid
  • UNSUITABLE_PUBLIC_KEY_TYPE: Certificate has not the correct extension for signing that content type, e.g. Vaccination entries
  • KEY_CREATION_ERROR: not used
  • KEYSTORE_ERROR: not used
  • SIGNATURE_INVALID: Signature on COSE structure could not be verified

On JavaScript, the methods updateTrustList and VerifierTrustList may throw an error of the type VerificationException directly. The object has the following structure:

{
  "message_8yp7un$_0": "Hash not matching",
  "cause_th0jdv$_0": null,
  "stack": "n/</e.captureStack@file:///...",
  "name": "VerificationException",
  "error": {
    "name$": "TRUST_LIST_SIGNATURE_INVALID",
    "ordinal$": 14
  }
}

The important bits are name (which should always be VerificationException) and error.name$, which contains the error code from the list above, e.g. TRUST_LIST_SIGNATURE_INVALID. See also <demo.html>.

TrustList

There is also an option to create (e.g. on a web service) a list of trusted certificates for verification of HCERTs:

// Load the private key and certificate from somewhere ...
String privateKeyPem = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADAN...";
String certificatePem = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
CryptoService cryptoService = new FileBasedCryptoService(privateKeyPem, certificatePem);
int validityHours = 48;
TrustListV2EncodeService trustListService = new TrustListV2EncodeService(cryptoService, validityHours);

// Load the list of trusted certificates from somewhere ...
Set<CertificateAdapter> trustedCerts = new HashSet<>(cert1, cert2, ...);
SignedData trustList = trustListService.encode(trustedCerts);
// Write content file
new FileOutputStream(new File("trustlist.bin")).write(trustList.getContent());
// Write signature file
new FileOutputStream(new File("trustlist.sig")).write(trustList.getSignature());

Clients may load these files to get the Trusted Certificates plus meta information:

// PEM-encoded signer certificate of the trustList
CertificateRepository trustAnchor = new PrefilledCertificateRepository("-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...");
// Download trust list content, binary, e.g. from https://dgc.a-sit.at/ehn/cert/listv2
byte[] trustListContent = new byte[0];
// Download trust list signature, binary, e.g. from https://dgc.a-sit.at/ehn/cert/sigv2
byte[] trustListSignature = new byte[0];
SignedData trustList = new SignedData(trustListContent, trustListSignature);

TrustListDecodeService service = new TrustListDecodeService(trustAnchor);
Pair<SignedDataParsed, TrustListV2> result = service.decode(trustList);
// Contains "validFrom", "validUntil"
SignedDataParsed parsed = result.getFirst();
// Contains a list of certificates in X.509 encoding
TrustListV2 trustListContainer = result.getSecond();
for (TrustedCertificateV2 cert : trustListContainer.getCertificates()) {
    // Parse it into your own data class
    System.out.println(ExtensionsKt.asBase64(cert.getCertificate()));
}

or in JavaScript:

// PEM-encoded signer certificate of the trust list
let trustListAnchor = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
// Download trust list content, binary, e.g. from https://dgc.a-sit.at/ehn/cert/listv2
let trustListContent = new ArrayBuffer(8);
// Download trust list signature, binary,, e.g. from https://dgc.a-sit.at/ehn/cert/sigv2
let trustListSignature = new ArrayBuffer(8);

let result = hcert.SignedDataDownloader.loadTrustList(trustListAnchor, trustListContent, trustListSignature);
// Contains "validFrom" and "validUntil" as JS Dates
console.log(result.first);
// Contains an array of "certificates", each with "kid" and "certificate" as Int8Array
console.log(result.second);

Business Rules

There is also an option to create (e.g. on a web service) a list of business rules, that may be used to further verify HCERTs:

// Load the private key and certificate from somewhere ...
String privateKeyPem = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADAN...";
String certificatePem = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
CryptoService cryptoService = new FileBasedCryptoService(privateKeyPem, certificatePem);
int validityHours = 48;
BusinessRulesV1EncodeService rulesService = new BusinessRulesV1EncodeService(cryptoService, validityHours);

// Load the list business rules
List<String> inputStrings = new ArrayList<>();
List<BusinessRule> input = inputStrings.stream().map(it -> new BusinessRule("identifier", it)).collect(Collectors.toList());
SignedData rules = rulesService.encode(input);
// Write content file
new FileOutputStream(new File("rules.bin")).write(rules.getContent());
// Write signature file
new FileOutputStream(new File("rules.sig")).write(rules.getSignature());

Clients may load these files to get a list of trusted Business Rules plus meta information:

// PEM-encoded signer certificate of the rules
CertificateRepository trustAnchor = new PrefilledCertificateRepository("-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...");
// Download rules content, binary, e.g. from https://dgc.a-sit.at/ehn/rules/v1/bin
byte[] rulesContent = new byte[0];
// Download rules signature, binary, e.g. from https://dgc.a-sit.at/ehn/rules/v1/sig
byte[] rulesSignature = new byte[0];
SignedData rulesSigned = new SignedData(rulesContent, rulesSignature);

BusinessRulesDecodeService service = new BusinessRulesDecodeService(trustAnchor);
Pair<SignedDataParsed, BusinessRulesContainer> result = service.decode(rulesSigned);
// Contains "validFrom", "validUntil"
SignedDataParsed parsed = result.getFirst();
// Contains a list of business rules as raw JSON
BusinessRulesContainer rules = result.getSecond();
for (BusinessRule rule : rules.getRules()) {
    // Parse it into your own data class
    System.out.println(rule.getRule());
}

or in JavaScript:

// PEM-encoded signer certificate of the rules
let rulesAnchor = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
// Download rules content, binary, e.g. from https://dgc.a-sit.at/ehn/rules/v1/bin
let rulesContent = new ArrayBuffer(8);
// Download rules signature, binary,, e.g. from https://dgc.a-sit.at/ehn/rules/v1/sig
let rulesSignature = new ArrayBuffer(8);

let result = hcert.SignedDataDownloader.loadBusinessRules(rulesAnchor, rulesContent, rulesSignature);
// Contains "validFrom" and "validUntil" as JS Dates
console.log(result.first);
// Contains an array of "rules", each with a "identifier" and "rule" (raw JSON string)
console.log(result.second);

Value Sets

There is also an option to create (e.g. on a web service) a list of value sets, that may be used to enrich data in HCERTs:

// Load the private key and certificate from somewhere ...
String privateKeyPem = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADAN...";
String certificatePem = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
CryptoService cryptoService = new FileBasedCryptoService(privateKeyPem, certificatePem);
        int validityHours = 48;
ValueSetV1EncodeService valueSetService = new ValueSetV1EncodeService(cryptoService, validityHours);

// Load the list value sets
List<String> inputStrings = new ArrayList<>();
List<ValueSet> input = inputStrings.stream().map(it -> new ValueSet(it)).collect(Collectors.toList());
SignedData valueSet = valueSetService.encode(input);
// Write content file
new FileOutputStream(new File("valueSet.bin")).write(valueSet.getContent());
// Write signature file
new FileOutputStream(new File("valueSet.sig")).write(valueSet.getSignature());

Clients may load these files to get a list of trusted Value Sets plus meta information:

// PEM-encoded signer certificate of the valueSet
CertificateRepository trustAnchor = new PrefilledCertificateRepository("-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...");
// Download valueSet content, binary, e.g. from https://dgc.a-sit.at/ehn/values/v1/bin
byte[] valueSetContent = new byte[0];
// Download valueSet signature, binary, e.g. from https://dgc.a-sit.at/ehn/values/v1/sig
byte[] valueSetSignature = new byte[0];
SignedData valueSetSigned = new SignedData(valueSetContent, valueSetSignature);

ValueSetDecodeService service = new ValueSetDecodeService(trustAnchor);
Pair<SignedDataParsed, ValueSetContainer> result = service.decode(valueSetSigned);
// Contains "validFrom", "validUntil"
SignedDataParsed parsed = result.getFirst();
// Contains a list of value sets as raw JSON
ValueSetContainer valueSet = result.getSecond();
for (ValueSet vs : valueSet.getValueSets()) {
    // Parse it into your own data class
    System.out.println(vs.getValueSet());
}

or in JavaScript:

// PEM-encoded signer certificate of the rules
let vaulesAnchor = "-----BEGIN CERTIFICATE-----\nMIICsjCCAZq...";
// Download values content, binary, e.g. from https://dgc.a-sit.at/ehn/values/v1/bin
let valuesContent = new ArrayBuffer(8);
// Download values signature, binary,, e.g. from https://dgc.a-sit.at/ehn/values/v1/sig
let valuesSignature = new ArrayBuffer(8);

let result = hcert.SignedDataDownloader.loadBusinessValues(valuesAnchor, valuesContent, valuesSignature);
// Contains "validFrom" and "validUntil" as JS Dates
console.log(result.first);
// Contains an array of "valueSets", each with a "name" and "valueSet" (raw JSON string)
console.log(result.second);

Data Classes

Classes in ehn.techiop.hcert.kotlin.data provide Kotlin data classes that conform to the JSON schema. They can be de-/serialized with Kotlin Serialization, i.e. Cbor.encodeToByteArray() or Cbor.decodeFromByteArray<GreenCertificate>().

These classes also use ValueSetEntry objects, that are loaded from the valuesets of the dgc-schema. These provide additional information, e.g. for the key "EU/1/20/1528" to map to the vaccine "Comirnaty".

This implementation is on purpose lenient when parsing HCERT data, since there may be some production data out there, that includes timestamps in date objects, or whitespaces in keys for value sets.

For JS, you can call verifyDataClass(qr) (istead of verify(qr)) to get an instance of a monkey-patched GreenCertificate. This class is essentially the same as GreenCertificate for the JVM target, but holds JS Date objects instead of the JVM types for dates and instants. In contrast to the simple call to verify(qr), you'll get a valueSetEntry (if one is found) and descriptive property names.

Configuration

Nearly every object in this library can be configured using constructor parameters. Most of these parameters have opinionated, default values, e.g. Clock.System for clock, used to get the current timestamp.

With the default configuration, schema validation of HCERT data is done against a very relaxed JSON schema, e.g. no maxLength, no format, no pattern for all fields. This is done to work around several faulty implementations of some countries, whose HCERT data would not be accepted by verifiers otherwise.

One example: The validity for the TrustList, as well as the validity of the HCERT in CBOR can be passed as a validity parameter (instance of a Duration) when constructing the objects:

CryptoService cryptoService = new RandomEcKeyCryptoService(256); // or some fixed key crypto service
HigherOrderValidationService higherOrderValidationService = new DefaultHigherOrderValidationService();
SchemaValidationService schemaValidationService = new DefaultSchemaValidationService(); // pass "false" to disable fallback schema validation
CborService cborService = new DefaultCborService();
CwtService cwtService = new DefaultCwtService("AT", 24); // validity for HCERT content
CoseService coseService = new DefaultCoseService(cryptoService);
CompressorService compressorService = new DefaultCompressorService(9); // level of compression
Base45Service base45Service = new DefaultBase45Service();
ContextIdentifierService contextIdentifierService = new DefaultContextIdentifierService("HC1:");


Chain chain = new Chain(higherOrderValidationService, schemaValidationService, cborService, cwtService, coseService, compressorService, base45Service, contextIdentifierService);
ChainResult result = chain.encode(input);

Implementers may load values for constructor parameters from a configuration file, e.g. with Spring Boot's configuration properties.

Logging

Configurability also holds true for logging, which is based on Napier and is shipped with a JS+JVM basic debug logger (see Enabling logging). This should probably be configured differently in production.

As for JS logging, the following functions are available:

  • hcert.setLogLevel(level), where level is one of the following string values: VERBOSE, DEBUG, INFO, WARNING, ERROR, ASSERT
    Setting any other value (or null/undefined) will disable logging, although it will not remove any loggers (see below). By default, a basic console logger is present. Therefore, a call to setLogLevel suffices to enable console-based logging. The default logger is exposed as hcert.defaultLogger.
  • hcert.setLogger(loggingFunction: (level: String, tag: String?, stackTrace: String?, message: String?) -> Unit, keep: Boolean? = false): JsLogger, which allows for configuring custom logging callbacks.
    If keep is omitted, existing loggers will be replaced, otherwise the newly added logger will be invoked in addition to existing loggers. As such, a call to this function without setting keep=true will result in the default console logger to be replaced.
    This function returns the newly created logger instance, which allows for later removal of any added logging callback (see below).
  • hcert.addLogger(logger:Antilog)/hcert.removeLogger(logger:Antilog) functions enable adding/removing any logger created through setLogger(), as well as the default console logger exposed though hcert.defaultLogger. It is therefore sensible to store the return value of setLogger to cater tor complex logging setups.

On other platforms, Napier's respective default (or custom) platform-specific loggers should be used according to the Napier API.

Publishing

The library is also published on jitpack.io.

Use it in your project like this:

repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation 'com.github.ehn-dcc-development.hcert-kotlin:hcert-kotlin-jvm:master-SNAPSHOT'
}

If you are planning to use this library, we'll suggest to fork it (internally), and review incoming changes. We can not guarantee non-breaking changes between releases.

Android

If you plan to use this library on Android versions below 8, be sure to include the following snippet in your build.gradle:

android {
    defaultConfig {
        // Required when setting minSdkVersion to 20 or lower
        multiDexEnabled true
    }

    compileOptions {
        // Flag to enable support for the new language APIs
        coreLibraryDesugaringEnabled true
        // Sets Java compatibility to Java 8
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
    // may be needed to make desugaring work in release builds too
    implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.2.1'
}

See these links for details:

Changelog

Version NEXT:

  • tbd

Version 1.5.0:

  • Update dependencies:
    • Common: Kotlin 1.7.10, Datetime 0.4.0, Napier 2.6.1, Gradle 7.5.1
    • JVM: Zxing 3.5.0, Bouncycastle 1.70, JSON Schema validator 0.36
    • JS: pki.js 2.4.0, cbor 8.1.0, ajv (JSON Schema validator) 8.11.0, QR Code 3.3.0, Bignumber 9.1.0

Version 1.4.1:

  • Import ehn-dcc-schema version 1.3.1 and 1.3.2

Version 1.4.0:

  • Add second respository for trust anchors to verify AT-specific vaccination exemptions (prefix AT1:) against (see buildVerificationChain)
  • Fix constructors and overloads for Java callers
  • Introduce a debug verification chain
  • Introduce possibility to anonymise personal data (JVM only)
  • Update dependencies:
    • Common: Kotlin: 1.5.31, kotlinx.serialization: 1.3.0, kotlinx.datetime: 0.3.0, Kotest: 4.6.3, Napier (Logging): 2.1.0
    • JVM: Bouncy Castle: 1.69, Json Schema Validation Lib: 2.1.0
    • JS: pako (ZLib): 2.0.4, pkijs: 2.1.97, util: 0.12.4, cbor: 8.0.2, node-inspect-extracted: 1.0.8, ajv (JSON schema validator): 8.6.3, ajv-formats: 2.1.1
  • JS:
    • Switch to upstream cose-js 0.7.0 (deprecates forked version)
    • Fix deprecated calls to Buffer constructor (possibly not all calls yet)
    • Switch intermediate (=node) output from CommonJS to UMD
    • Experimental NodeJS support
      • Enable outputting a bundled js module (UMD) targeting NodeJS if desired
      • the Gradle npm-publish plugin does not work as desired

Version 1.3.2:

  • Export SignedDataDownloader to JS

Version 1.3.1:

  • Rework verification of timestamps in the HCERT CWT, to work around some weird codes appearing in production
  • New error codes CWT_NOT_YET_VALID and PUBLIC_KEY_NOT_YET_VALID, see above

Version 1.3.0:

  • Parse a missing dr value in HCERT Test entries correctly
  • Add class SignedData to hold content and signature of a TrustList
  • Add services to encode and decode Business Rules (also called Validation Rules)
  • Add services to encode and decode Vaule Sets

Version 1.2.0:

  • Split faulty implementations, sample data, to separate artifact: ehn.techiop.hcert:hcert-kotlin-jvmdatagen
  • Add option to get a data class with "nice" names when validating in JS (equivalent to JVM)
  • API change: GreenCertificate now uses arrays for test/vaccination/recovery
  • Add certificateSubjectCountry to VerificationResult, to get the country of the HCERT's signature certificate
  • Relax schema validation once more to allow explicit null values for nm, ma in HCERT Test entries
  • JS: Fix logging API (previous implementation was incomplete, preventing any logging on JS)

Version 1.1.1:

  • Change tc (testingFacility) in HCERT Test entries to optional, i.e. nullable String

Version 1.1.0:

  • Try to parse as many dates and datetimes as possible
  • Perform a very relaxed schema validation by default
  • Add errors for trust list loading
  • Support lower Android targets by not using java.util.Base64
  • Publish library on jitpack.io

Version 1.0.1:

  • Validate schema on JVM
  • Fix usage of this project as a library, e.g. on Android

Version 1.0.0:

  • Convert to a Kotlin multiplatform project, therefore some details may have changed when calling from JVM
  • Implements encoding and decoding data on JS and JVM targets
  • Some testcases from dgc-testdata still fail

Version 0.4.0:

  • Update ehn-dgc-schema to 1.2.1
  • Include dgc-testdata as a git submodule

Version 0.3.1:

  • Implement a TrustList V2, with details is hcert-service-kotlin

Version 0.3.0:

  • Rename the previous CborService to CwtService, as the new name matches the implementation more closely
  • Introduce new CborService that just encodes HCERT as CBOR
  • Bugfix: Compression with ZLIB is in fact not optional when decoding QR codes
  • Bugfix: In CBOR, Dates need to be serialized as ISO 8601 compatible Strings, e.g. 2021-02-20T12:34:56Z, not 1613824496000

Version 0.2.2:

  • Changes to validity parameter for creating TrustList, HCERTs (TrustListEncodeService and DefaultCborService)
  • More options for creating 2D codes (DefaultTwoDimCodeService)
  • Implement first shot of reading standardized test cases in a JSON format (see src/test/resources/testcase01.json)
  • Use ValueSet instead of fixed enums for data in GreenCertificate
  • Update dgc-schema to version 1.0.0 from 2021-04-30

Version 0.2.1:

  • TrustList encodes public keys in PKCS#1 format (instead of PKCS#8/X.509)
  • Interface of TwoDimCodeService now returns a ByteArray instead of a String, callers need to encode the result to manually.

Libraries

This library uses the following dependencies:

For the JVM target:

For the JS target:

Tip: Run ./gradlew generateLicenseReport.

hcert-kotlin's People

Contributors

asit-fdraschbacher avatar asitplus-pteufl avatar jesusmccloud avatar jsiwrk avatar lazka avatar nodh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hcert-kotlin's Issues

Question: Publishing

Hello there,

I would like to ask you if you are planning to publish the library to a public repository so I dont have to create a github access token to get it. The main reason is becasue I am thinking about using this library in our green pass applications and it seems like the most effective way is to have one good library shared across countries rather than having custom implementations.

CWT_Expired Error on Check with a new 3/3 valid cert

Hello,

we got a issue with new 3/3 certs (not all but mine) doesnt work, it writes CWT_Expired as an error and returns valid false
Its a new GreenPass (9.11).

Error

On request i can provide the qrcode.

Android 10
implementation 'com.github.ehn-dcc-development.hcert-kotlin:hcert-kotlin-jvm:1.3.0'

Best regards,
Dominik

Certificate Loading in JS

JS certificate loading still has some issues, like throwing all sorts of ugly exceptions and not correctly handling different key sizes.

Integration questions

Not really an issue but a question. Sorry for the utter noobism to write an issue which isn't one.

I am currently trying to integrate any covid SDK into a cordova app to verify certificates. Because we are located in switzerland, it would have been great to support swiss "light" certificates. But because the Swiss government only offers Android and iOS SDKs, which we unsuccessfully tried to wrap into a cordova plugin, I integrated the hcert JavaScript library you are offering.

This works like a charm for all EU test certificates, but unfortunately neither for regular Swiss certificates nor for test ones. Swiss certs should officially be supported by EU implementations since friday (09.07.2021).

So my questions are:

  1. Are the sig- and trustlist-binaries from https://dgc.a-sit.at/ehn/ open to use?
  2. Are they up-to-date?
  3. Can they only be used for live certificates?
  4. Should Swiss certs be supported?

Many thanks in advance
Michel

Empty "dr" field

https://github.com/eu-digital-green-certificates/dcc-quality-assurance/blob/main/SpecialCaseHandling.md
https://github.com/eu-digital-green-certificates/dcc-quality-assurance/blob/main/NL/1.3.0/specialcases/TEST_EMPTY_DR_FIELD.png

this one is not working due to:
QR Code contains Version 1.3 Schema, but field "dr" is empty string and as optional in the Test QR Code

the quick fix might be

object InstantParser {
    fun parseInstant(decoder: Decoder): Instant {
        val value = decoder.decodeString()
        val fixOffset = value.replace(Regex("\\+(\\d{2})(\\d{2})")) { "+${it.groupValues[1]}:${it.groupValues[2]}" }
        val fixZulu = if (fixOffset.contains('Z') || fixOffset.contains("+")) fixOffset else fixOffset + 'Z'
        return Instant.parse(fixZulu)
    }
}

object LenientInstantParser : KSerializer<Instant> {

    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): Instant {
        return InstantParser.parseInstant(decoder)
    }

    override fun serialize(encoder: Encoder, value: Instant) {
        encoder.encodeString(value.toString())
    }
}

object NullableLenientInstantParser : KSerializer<Instant?> {

    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): Instant? {
        return try {
            InstantParser.parseInstant(decoder)
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

    override fun serialize(encoder: Encoder, value: Instant?) {
        encoder.encodeString(value.toString())
    }
}

but then there is an action:

Verifiers should remove all date format checks from date-time values/dates

@nodh

Replace java.util.Base64 for Android sdk below 26

For Android sdk below 26 (Android 8.0), there will be an exception since java.util.Base64 is unknown.

This can be modified in https://github.com/ehn-dcc-development/hcert-kotlin/blob/main/src/jvmMain/kotlin/ehn/techiop/hcert/kotlin/chain/Extensions.kt

Instead of java.util.Base64.getEncoder().encodeToString(this) and java.util.Base64.getDecoder().decode(this)
org.bouncycastle.util.encoders.Base64.toBase64String(this) et org.bouncycastle.util.encoders.Base64..ecode(this) can be used

Question: greenPass app

Hello, just curious, are you planning to download the trustList to your greenPass application that regular users with certificates will use ? Or the greenPass app is just a "gallery" for qr codes?

Null values instead of missing field JSON validation

@JesusMcCloud
@nodh

Hello there,

I wanted to ask if you also tested JSON schema validation if the values are null

for example

{ "t": [ { "ci": "urn:uvci:01:lv:e047f5373a6ea7794a7edd34eb204a12", "co": "LV", "dr": "2021-05-31T19:11:40Z", "is": "Nacionālais veselības dienests", "ma": null, "nm": null, "sc": "2021-05-31T15:44:52Z", "tc": "CENTRĀLĀ LABORATORIJA, SIA", "tg": "840539006", "tr": "260415000", "tt": "LP6464-4" } ], "dob": "1966-10-25", "nam": { "fn": "Darbiņš", "gn": "Aldis", "fnt": "DARBINS", "gnt": "ALDIS" }, "ver": "1.0.0" }

is failing

https://github.com/eu-digital-green-certificates/dcc-quality-assurance/blob/main/LV/1.0.0/specialcases/TEST_NULL_values_in_nm_ma.png

Encoding Chain in JS

https://github.com/ehn-dcc-development/hcert-kotlin/tree/feature/encoding-js contains some commits, that add code to encode HCERT data on the JS target.
It works, but one general problem remains though:

This encodes all keys inside the CWT as Strings, but they should be Integers:

return Encoder.encode(map.mapToJson()).toByteArray()

This works around this issue on the decoding side:

((map as Json).get(key.toString()) as Buffer?)?.toByteArray()

So we'll need to find a way, to encode the keys of a map as integers.

Remove Inline Javascript

On some occasions we are still using inline JS code.
It would be really nice to get rid of

  • inline JS code
  • dynamic/.asDynamic()
  • unsafe casts

This requires some more effort, since there are certain situations where the generated externals effectively force us to do so, since the externals themselves are faulty, for example.
In addition, some JS functions take JS objects for parameters, therefore we need to make absolutely sure that our type-safe code actually compiles to this very specific object structure. This is especially tricky for higher-order functions edge cases like using numbers for keys…

Java Maven Integration

Dear authors & contributors,

I experience problems when including hcert-kotlin into a java spring boot project using maven. As discussed in the readme, I added the dependency to the built maven artifact using:

<dependency>
	<groupId>com.github.ehn-dcc-development</groupId>
	<artifactId>hcert-kotlin</artifactId>
	<version>1.3.2</version>
</dependency>

....
<repository>
	<id>jitpack.io</id>
	<url>https://jitpack.io</url>
</repository>

Unfortunately, when trying to test the decode example, compiling fails with:
(this is also same for other jvm-examples from the readme page)

The method buildVerificationChain(CertificateRepository, Clock) in the type DefaultChain is not applicable for the arguments (CertificateRepository)

Next, I tried to build the hcert-kotlin.jar (v 1.4.0-SNAPSHOT) locally using gradle and import and use it in my local maven repository. Unfortunately, I could only generate the hcert-kotlin-jvm.jar (maybe wrong gradle use, please advise). Using this import the compile error is resolved, but a java runtime error about missing kotlin libraries is thrown.

For further reference during my troubleshooting I found issue https://youtrack.jetbrains.com/issue/KT-35716, which could explain the missing correct static java method.

Introduce Tags for Versioning

Currently, every time the library is updated, it's very difficult and cumbersome to try and figure out exactly which Snapshot version contains which github code (ie: at what point was the snapshot published?)

May I request that the library is tagged with the exact snapshots, and everytime there are official releases, tags are maintained?

Cose.kt?5e47:24 Uncaught TypeError: sign$Companion.createSync is not a function

Usage (JS)
./gradlew jsBrowserDevelopmentWebpack;
just testing demo.html

everything is okay but the Random or Fixed Generator.
It's "stucking" on QR Code Contents: Generating...
Nothing happen (no code and no qr). I've got this error:

Cose.kt?5e47:24 Uncaught TypeError: sign$Companion.createSync is not a function
at Cose.sign_4mr2q2$ (Cose.kt?5e47:24)
at CoseCreationAdapter.sign_x1bysv$ (CoseCreationAdapter.kt?1914:28)
at DefaultCoseService.encode_fqrh44$ (DefaultCoseService.kt?863c:32)
at Chain.encode (Chain.kt?e7ee:40)
at Generator.encode (JsInterface.kt?35f0:148)
at generateRandom (demo.html:38)
at HTMLButtonElement.onclick (demo.html:312)

for... return sign.createSync(header, Buffer(input.toUint8Array()), signer)

file:
.\src\jsMain\kotlin\ehn\techiop\hcert\kotlin\crypto\Cose.kt

package ehn.techiop.hcert.kotlin.crypto

import Buffer
import cose.Signer
import cose.Verifier
import cose.sign
import ehn.techiop.hcert.kotlin.chain.toByteArray
import ehn.techiop.hcert.kotlin.chain.toUint8Array

internal object Cose {
fun verifySync(signedBitString: ByteArray, pubKey: PubKey): ByteArray {
val key = (pubKey as JsPubKey).toCoseRepresentation()
val verifier = object : Verifier {
override val key = key
}
return sign.verifySync(Buffer.from(signedBitString.toUint8Array()), verifier).toByteArray()
}

fun sign(header: dynamic, input: ByteArray, privKey: PrivKey): Buffer {
    val key = (privKey as JsPrivKey).toCoseRepresentation()
    val signer = object : Signer {
        override val key = key
    }
    return sign.createSync(header, Buffer(input.toUint8Array()), signer)
}

}

Certificate validity and expiration time bug

I think there is a bug with DecisionService.

In line 33, this service is verifying that signing certificate's expiration date is not after DGC expiration time. This does not seem ok because the validity of the signing certificate must be greater than the validity off the signed document.

31      verificationResult.expirationTime?.let { expirationTime ->
32            verificationResult.certificateValidUntil?.let { certValidUntil ->
33                if (certValidUntil.isAfter(expirationTime))
34                    return VerificationDecision.FAIL
35            }
36            if (expirationTime.isBefore(Instant.now()))
37                return VerificationDecision.FAIL
38        }

javascript: Possible to inject clock?

It seem like all decoders (trust list, value,sets rules) check the parsed data against the current clock and fail if they are not longer valid. In combination with the official test data which is only valid for two days this makes it hard to integrate this into unit tests.

Is there any way to fake the current time for testing? (or to disable those checks for testing)

Limit size of decompressed byte array

In the Swiss DCC repo, it was pointed out that decompressing malicious input data can potentially lead to excessive memory usage.

This repo uses a similar approach, so it suffers the same problem:
https://github.com/ehn-digital-green-development/hcert-kotlin/blob/0e212a730be414e9c41f9920dc8d12f0d4f81fd7/src/jvmMain/kotlin/ehn/techiop/hcert/kotlin/chain/impl/CompressorAdapter.kt#L13

As I mentioned in the linked issue, the impact is negligible if only data from barcodes is ever passed to the library function.
However, you never know in what ways others use your library, so it should be mitigated.
You can find our fix here.

JVM library missing public constructors

We use the jvm library in a Java11 project and are missing public constructors for ehn.techiop.hcert.kotlin.chain.impl.TrustListCertificateRepository and ehn.techiop.hcert.kotlin.trust.TrustListDecoderService.
Basically we added kotlin-hcert-jvm-1.3.0.jar to pom as well as its dependencies and thats it.
Are we missing out on some required steps?

Debugging Issue with v.1.3.0

Good Morning,

at the moment im trying to add the hcert-kotlin as a dependencie to my app.
I've tried a lot way to add, server versions and it always its the same.

Current way to add it:

implementation 'com.github.ehn-dcc-development.hcert-kotlin:hcert-kotlin:1.3.0'
This comes from jitpack.io

Kotlin Version 1.3.21
Build Variant: Debug

But its failing with the following error message (Trying to build with android studio)

Execution failed for task ':flave:checkDebugDuplicateClasses'.
Could not resolve all files for configuration ':flave:debugRuntimeClasspath'.
Could not resolve com.github.ehn-dcc-development.hcert-kotlin:hcert-kotlin:1.3.0.
Required by:
project :flave
> Cannot choose between the following variants of com.github.ehn-dcc-development.hcert-kotlin:hcert-kotlin:1.3.0:
- jvmDataGenRuntimeElements-published
- jvmRuntimeElements-published
All of them match the consumer attributes:
- Variant 'jvmDataGenRuntimeElements-published' capability com.github.ehn-dcc-development.hcert-kotlin:hcert-kotlin:1.3.0:
- Unmatched attributes:
- Required com.android.build.api.attributes.BuildTypeAttr 'debug' but no value provided.
- Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but no value provided.
- Found ehn.techiop.hcert.faults 'true' but wasn't required.
- Found org.gradle.libraryelements 'jar' but wasn't required.
- Found org.gradle.status 'release' but wasn't required.
- Compatible attributes:
- Required org.gradle.usage 'java-runtime' and found compatible value 'java-runtime'.
- Required org.jetbrains.kotlin.platform.type 'androidJvm' and found compatible value 'jvm'.
- Variant 'jvmRuntimeElements-published' capability com.github.ehn-dcc-development.hcert-kotlin:hcert-kotlin:1.3.0:
- Unmatched attributes:
- Required com.android.build.api.attributes.BuildTypeAttr 'debug' but no value provided.
- Required com.android.build.gradle.internal.dependency.AndroidTypeAttr 'Aar' but no value provided.
- Found ehn.techiop.hcert.faults 'false' but wasn't required.
- Found org.gradle.libraryelements 'jar' but wasn't required.
- Found org.gradle.status 'release' but wasn't required.
- Compatible attributes:
- Required org.gradle.usage 'java-runtime' and found compatible value 'java-runtime'.
- Required org.jetbrains.kotlin.platform.type 'androidJvm' and found compatible value 'jvm'.

Maybe there is something i'm missing. But this wont go away, even if i change the version.
Thanks for the help in advance.

Probably Wrong expirationTime?

Hey.
I tried to validate some examples from https://github.com/eu-digital-green-certificates/dgc-testdata/tree/main/AT#ra-test.

Problem/Question
The fourth example should be invalid since its supposed to be expired. Is the test data wrong or something in the implementation?

image

  "isValid": true,
  "error": null,
  "metaInformation": {
    "expirationTime": "2021-11-02",
    "issuedAt": "2021-05-06",
    "issuer": "AT",
    "certificateValidFrom": "2021-05-05",
    "certificateValidUntil": "2023-05-05",
    "certificateValidContent": ["TEST", "VACCINATION", "RECOVERY"],
    "certificateSubjectCountry": "AT",
    "content": ["TEST"],
    "error": null
  },
  "greenCertificate": {
    "ver": "1.0.0",
    "nam": {
      "fn": "Musterfrau-Gößinger",
      "fnt": "MUSTERFRAU<GOESSINGER",
      "gn": "Gabriele",
      "gnt": "GABRIELE"
    },
    "dob": "1998-02-26",
    "v": null,
    "r": null,
    "t": [
      {
        "tg": "840539006",
        "tt": "LP217198-3",
        "nm": null,
        "ma": "1232",
        "sc": "2021-02-20T12:34:56Z",
        "dr": null,
        "tr": "260415000",
        "tc": "Testing center Vienna 1",
        "co": "AT",
        "is": "Ministry of Health, Austria",
        "ci": "URN:UVCI:01:AT:71EE2559DE38C6BF7304FB65A1A451EC#3"
      }
    ]
  }
}```

Nullable FTN

Hello,

I am implementing a POC currently, and in the version 0.2.2 there is a nullable fnt and nonNullable gn which might be incorrect according to the json schemma.

Screenshot 2021-05-14 at 07 30 17

Screenshot 2021-05-14 at 07 30 50

Truncate kid

I browse a bit trough the implementations, and I couldn't see where the Kid gets truncated (at 8 bytes).

Is this implementation following this specs ?

Update JS to IR Backend

We currently rely on some specifics of the JS Legacy backend. It would be nice to update to IR, because we cannot update kotlinx-serialization and Kotest.
In addition, the current hard dependency on the legacy backend is not future-proof.

feature/update lifted all dependency versions as high as possible with the legacy backend, highlighting the issue of outdated dependencies.

Moreover, generated and hand-crafted glue to include JS libraries needs to be revised to reflect the IR-backend's constraints

NameNaa type

Hello,

is this correct? it seems they should be both type of ValueSetEntryAdapter

    @SerialName("nm")
    val nameNaa: String? = null,

    @SerialName("ma")
    val nameRat: ValueSetEntryAdapter? = null,

Clarify Requirements for Schema Validation

Currently, both the JVM target and the JS target do not support all JSON schema features.
On JS, schema validation is known to not cover all aspects, on the JVM, further investigation is needed.

Offline Validation for more than 48h 7 VerificationException: Expiration<clock.now()

hello,

i need to do verification of greenpass in offline environment. devices will be offline for longer period of time (maybe months). my problem is, that i'll get
ehn.techiop.hcert.kotlin.chain.VerificationException: Expiration<clock.now()
when the trustlist wasn't updated for 2 (?) days.

I'm using the trustlist from a-sit.at

Is there a way to make the verification process accept older data?

thanks!
exception

metaInformation missing time/timezone information

A minor issue. I currently get something like this with JS:

{
  "expirationTime": "2022-08-01",
  "issuedAt": "2021-08-06",
  "issuer": "AT",
  "certificateValidFrom": "2021-06-02",
  "certificateValidUntil": "2023-06-02",
  "certificateValidContent": [
    "TEST",
    "VACCINATION",
    "RECOVERY"
  ],
  "certificateSubjectCountry": "AT",
  "content": [
    "VACCINATION"
  ],
  "error": null
}

If I'm not mistaken all the dates are missing the time and timezone information.

cbor+BigInt and Safari <14

This is more of a heads-up, so feel free to close.

The included cbor library requires BigInt support which is only in Safari 14+

It's not really used when verifying a real HCERT, so I've mocked BigInt during the script import for now, which seems to work fine here and make HCERT validation work with Safari 12.1/13, which is our baseline atm. In case anyone is in a similar situation.

Random RSA Keys in JS

Implement feature to create random RSA keys (and import them from PEM encoded files), just as the EC part, in JS.

Question: Expiration time

Hello @nodh

I just wanted to ask if there is a requirement from EU to throw away the data if the signing is expired. I was searching for it but could not find.

    /**
     * `exp` claim SHALL hold a timestamp.
     * Verifier MUST reject the payload after expiration.
     * It MUST not exceed the validity period of the DSC.
     */
    var expirationTime: Instant? = null

Where can I find SignedDataDownloader?

The README example give various JS examples using SignedDataDownloader, but that class isn't available in the JS bundle. Am I missing something?

thanks

Improve verification error handling

Originally raised by @jsiwrk in #32 (comment)

Although we removed the errorMessage from the VerificationResult, as such information should be logged, not returned.

I would tend to agree with that in the general sense, if we were talking about an application or a service. However, since this is a reusable library that will presumably be used to build the former, wouldn't it be more appropriate to leave that choice to the library user?

For example, let's consider these scenarios:

  • An application using this library wants to print a detailed error to the user indicating why the hcert is "expired". The CWT_EXPIRED error code can be returned for various reasons (hcert issued before signer cert, hcert issued in the future, hcert expired, hcert expiration exceeds signer cert expiration, expiration field not present, etc.), so the code itself is not enough to know the cause. Let's also assume this application is targeted to a type of user who is genuinelly interested about these details (maybe a second-line border control officer that needs to explain to a hcert holder why their document was rejected by the first-line control), and that he/she prefers to see this detail in a user-friendly way (rather than looking at some internal logs).
  • An application or service using this library wants to record a log with the full details why the validation has failed, and wants to record the log in its own way. Maybe it needs to send a JSON-formatted log to a SIEM system (e.g. Splunk).

If these are deemed valid use cases for this library, then it seems that including an errorMessage (or, generally speaking, detailed information about the error) in the VerificationResult would be the right thing to do. And the library client would be responsible for making a proper use of this field. What do you think?

Missing hcert-kotlin.js

Hi,

i can't find hcert-kotlin.js path and a file. There is no path like this: build/distributions/hcert-kotlin.js. My demo.html file also can't load js data, because there is no javascript file.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.