• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

ehn-dcc-development/hcert-kotlin: Kotlin multiplatform implementation of the HCE ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

ehn-dcc-development/hcert-kotlin

开源软件地址(OpenSource Url):

https://github.com/ehn-dcc-development/hcert-kotlin

开源编程语言(OpenSource Language):

Kotlin 94.6%

开源软件介绍(OpenSource 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.0, from 2021-06-11.

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);

热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap