@open-charging-cloud/chargy-core 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import { ed448ph, ed448 } from '@noble/curves/ed448.js';
5
5
  import { p521, p384, p256 } from '@noble/curves/nist.js';
6
6
  import { secp256k1 } from '@noble/curves/secp256k1.js';
7
7
  import { ml_dsa87, ml_dsa65, ml_dsa44 } from '@noble/post-quantum/ml-dsa.js';
8
- import { sha256 as sha256$1 } from '@noble/hashes/sha2.js';
8
+ import { sha256 as sha256$1, sha512 as sha512$1, sha384 as sha384$1 } from '@noble/hashes/sha2.js';
9
9
  import { fileTypeFromBuffer } from 'file-type';
10
10
  import isURL from 'is-url-superb';
11
11
  import jsQR from 'jsqr';
@@ -3073,10 +3073,10 @@ var NobleCompatibleCurve = class {
3073
3073
  keyFromPublic(publicKey, encoding) {
3074
3074
  if (encoding.toLowerCase() !== "hex")
3075
3075
  throw new TypeError("Only hexadecimal public keys are supported by the compatibility API.");
3076
- const publicKeyBytes = normalizeSEC1PublicKey(publicKey, this.coordinateLength);
3077
- if (!this.suite.isValidPublicKey(publicKeyBytes))
3076
+ const publicKeyBytes2 = normalizeSEC1PublicKey(publicKey, this.coordinateLength);
3077
+ if (!this.suite.isValidPublicKey(publicKeyBytes2))
3078
3078
  throw new TypeError("Public key is not a valid point on the selected curve.");
3079
- return new NobleCompatiblePublicKey(this.suite, publicKeyBytes, this.coordinateLength);
3079
+ return new NobleCompatiblePublicKey(this.suite, publicKeyBytes2, this.coordinateLength);
3080
3080
  }
3081
3081
  };
3082
3082
  var suites = {
@@ -5910,7 +5910,7 @@ async function signJSONMessage(JSONMessage, KeyPairs, options) {
5910
5910
  continue;
5911
5911
  const publicKeyEncoding = isRawSignatureAlgorithm(algorithm) ? "raw" : "sec1";
5912
5912
  const signatureEncoding = options?.signatureEncoding ?? suite.signatureEncoding;
5913
- const publicKeyBytes = KeyPair.publicKey ?? suite.getPublicKey(KeyPair.privateKey);
5913
+ const publicKeyBytes2 = KeyPair.publicKey ?? suite.getPublicKey(KeyPair.privateKey);
5914
5914
  const signatureBytes = suite.sign(
5915
5915
  plainText,
5916
5916
  KeyPair.privateKey,
@@ -5920,8 +5920,8 @@ async function signJSONMessage(JSONMessage, KeyPairs, options) {
5920
5920
  algorithm,
5921
5921
  publicKeyEncoding,
5922
5922
  signatureEncoding,
5923
- publicKey: bytesToBase64(publicKeyBytes),
5924
- publicKeyHEX: bytesToHex(publicKeyBytes),
5923
+ publicKey: bytesToBase64(publicKeyBytes2),
5924
+ publicKeyHEX: bytesToHex(publicKeyBytes2),
5925
5925
  signature: bytesToBase64(signatureBytes),
5926
5926
  signatureHEX: bytesToHex(signatureBytes)
5927
5927
  };
@@ -5932,7 +5932,7 @@ async function signJSONMessage(JSONMessage, KeyPairs, options) {
5932
5932
  const signatureIsValid = suite.verify(
5933
5933
  plainText,
5934
5934
  signatureBytes,
5935
- publicKeyBytes,
5935
+ publicKeyBytes2,
5936
5936
  signatureOptions(options, signatureEncoding)
5937
5937
  );
5938
5938
  if (!signatureIsValid)
@@ -5975,11 +5975,11 @@ async function verifyJSONSignatureResult(JSONMessage, signature, options) {
5975
5975
  const resolvedOptions = resolveSignOptions(options);
5976
5976
  const algorithm = signature.algorithm ?? resolvedOptions.algorithm;
5977
5977
  const suite = getSignatureSuite(algorithm);
5978
- const publicKeyBytes = hexToBytes(signature.publicKeyHEX);
5978
+ const publicKeyBytes2 = hexToBytes(signature.publicKeyHEX);
5979
5979
  const signatureBytes = hexToBytes(signature.signatureHEX);
5980
5980
  const signatureEncoding = signature.signatureEncoding ?? (isRawSignatureAlgorithm(algorithm) ? "raw" : "der");
5981
5981
  try {
5982
- const publicKeyIsValid = suite.isValidPublicKey(publicKeyBytes);
5982
+ const publicKeyIsValid = suite.isValidPublicKey(publicKeyBytes2);
5983
5983
  if (!publicKeyIsValid)
5984
5984
  return verificationResult(
5985
5985
  "InvalidPublicKey" /* InvalidPublicKey */,
@@ -5995,7 +5995,7 @@ async function verifyJSONSignatureResult(JSONMessage, signature, options) {
5995
5995
  const signatureIsValid = suite.verify(
5996
5996
  plainText,
5997
5997
  signatureBytes,
5998
- publicKeyBytes,
5998
+ publicKeyBytes2,
5999
5999
  signatureVerificationOptions(signature, options, signatureEncoding)
6000
6000
  );
6001
6001
  return signatureIsValid ? verificationResult("True" /* True */) : verificationResult(
@@ -8324,10 +8324,10 @@ var OCMF = class {
8324
8324
  try {
8325
8325
  if (typeof PublicKey !== "string")
8326
8326
  throw new TypeError("EdDSA and ML-DSA public keys must use a raw string encoding.");
8327
- const publicKeyBytes = decodeRawOCMFBytes(PublicKey, PublicKeyEncoding);
8327
+ const publicKeyBytes2 = decodeRawOCMFBytes(PublicKey, PublicKeyEncoding);
8328
8328
  const signatureBytes = OCMFJSONDocument.signatureBytes;
8329
8329
  const rawPayload = OCMFJSONDocument.rawPayload;
8330
- if (!signatureSuite.isValidPublicKey(publicKeyBytes))
8330
+ if (!signatureSuite.isValidPublicKey(publicKeyBytes2))
8331
8331
  throw new TypeError("Public key is invalid for the selected signature algorithm.");
8332
8332
  if (signatureBytes == null)
8333
8333
  throw new TypeError("Missing raw signature bytes.");
@@ -8336,7 +8336,7 @@ var OCMF = class {
8336
8336
  const signatureValid = signatureSuite.verify(
8337
8337
  new TextEncoder().encode(rawPayload),
8338
8338
  signatureBytes,
8339
- publicKeyBytes
8339
+ publicKeyBytes2
8340
8340
  );
8341
8341
  if (!signatureValid)
8342
8342
  this.AddValidationError(OCMFJSONDocument, "Verification_SignatureMismatch");
@@ -8353,7 +8353,7 @@ var OCMF = class {
8353
8353
  if (curve === null)
8354
8354
  throw new Error(`Unsupported ECC curve '${OCMFJSONDocument.signature.SA ?? ""}'!`);
8355
8355
  OCMFJSONDocument.publicKey ??= PublicKey;
8356
- let publicKeyBytes = null;
8356
+ let publicKeyBytes2 = null;
8357
8357
  if (typeof OCMFJSONDocument.publicKey === "string") {
8358
8358
  const ECPoint = this.chargy.asn1.define("ECPoint", function() {
8359
8359
  this.seq().obj(
@@ -8367,36 +8367,36 @@ var OCMF = class {
8367
8367
  if (PublicKeyEncoding != null && PublicKeyEncoding.length > 0) {
8368
8368
  switch (PublicKeyEncoding.toLowerCase()) {
8369
8369
  case "hex":
8370
- publicKeyBytes = Buffer.from(OCMFJSONDocument.publicKey, "hex");
8370
+ publicKeyBytes2 = Buffer.from(OCMFJSONDocument.publicKey, "hex");
8371
8371
  break;
8372
8372
  case "base32":
8373
- publicKeyBytes = Buffer.from(this.chargy.base32Decode(OCMFJSONDocument.publicKey, "RFC4648"));
8373
+ publicKeyBytes2 = Buffer.from(this.chargy.base32Decode(OCMFJSONDocument.publicKey, "RFC4648"));
8374
8374
  break;
8375
8375
  case "base64":
8376
- publicKeyBytes = Buffer.from(OCMFJSONDocument.publicKey, "base64");
8376
+ publicKeyBytes2 = Buffer.from(OCMFJSONDocument.publicKey, "base64");
8377
8377
  break;
8378
8378
  }
8379
8379
  }
8380
- if (publicKeyBytes == null) {
8380
+ if (publicKeyBytes2 == null) {
8381
8381
  const hexRegex = /^[0-9A-Fa-f]+$/;
8382
8382
  const base32Regex = /^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?$/;
8383
8383
  const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
8384
8384
  if (hexRegex.test(OCMFJSONDocument.publicKey)) {
8385
8385
  PublicKeyEncoding = "hex";
8386
- publicKeyBytes = Buffer.from(OCMFJSONDocument.publicKey, "hex");
8386
+ publicKeyBytes2 = Buffer.from(OCMFJSONDocument.publicKey, "hex");
8387
8387
  } else if (base32Regex.test(OCMFJSONDocument.publicKey)) {
8388
8388
  PublicKeyEncoding = "base32";
8389
- publicKeyBytes = Buffer.from(this.chargy.base32Decode(OCMFJSONDocument.publicKey, "RFC4648"));
8389
+ publicKeyBytes2 = Buffer.from(this.chargy.base32Decode(OCMFJSONDocument.publicKey, "RFC4648"));
8390
8390
  } else if (base64Regex.test(OCMFJSONDocument.publicKey)) {
8391
8391
  PublicKeyEncoding = "base64";
8392
- publicKeyBytes = Buffer.from(OCMFJSONDocument.publicKey, "base64");
8392
+ publicKeyBytes2 = Buffer.from(OCMFJSONDocument.publicKey, "base64");
8393
8393
  }
8394
8394
  }
8395
- if (publicKeyBytes == null) {
8395
+ if (publicKeyBytes2 == null) {
8396
8396
  OCMFJSONDocument.validationStatus = "UnknownPublicKeyFormat" /* UnknownPublicKeyFormat */;
8397
8397
  return OCMFJSONDocument.validationStatus;
8398
8398
  }
8399
- const decodedPublicKeyASN1 = ECPoint.decode(publicKeyBytes, "der");
8399
+ const decodedPublicKeyASN1 = ECPoint.decode(publicKeyBytes2, "der");
8400
8400
  const publicKeyASN1 = decodedPublicKeyASN1;
8401
8401
  const coordinates = publicKeyASN1.pubKey.data.subarray(1);
8402
8402
  const halfLength = coordinates.length / 2;
@@ -12057,7 +12057,9 @@ function toSessionVerificationResults(CTR, noRecordsMessage = { "en": "No charge
12057
12057
  var IChargeTransparencyLiveLink_exports = {};
12058
12058
  __export(IChargeTransparencyLiveLink_exports, {
12059
12059
  ChargeTransparencyLiveLinkContext: () => ChargeTransparencyLiveLinkContext,
12060
- IsAChargeTransparencyLiveLink: () => IsAChargeTransparencyLiveLink
12060
+ IsAChargeTransparencyLiveLink: () => IsAChargeTransparencyLiveLink,
12061
+ isConnector: () => isConnector,
12062
+ isTransport: () => isTransport
12061
12063
  });
12062
12064
  var ChargeTransparencyLiveLinkContext = "https://open.charging.cloud/contexts/chargeTransparency/live/link/1.0";
12063
12065
  function isConnector(data) {
@@ -12083,9 +12085,7 @@ function isTransport(data) {
12083
12085
  return (data["url"] === void 0 || typeof data["url"] === "string") && (data["urls"] === void 0 || Array.isArray(data["urls"]) && data["urls"].every(isTransportURL)) && (data["totp"] === void 0 || isTOTPConfig(data["totp"]));
12084
12086
  }
12085
12087
  function IsAChargeTransparencyLiveLink(data) {
12086
- if (!isMandatoryJSONObject(data))
12087
- return false;
12088
- return data["@context"] === ChargeTransparencyLiveLinkContext && (data["created"] === void 0 || data["created"] === null || typeof data["created"] === "string") && (data["description"] === void 0 || isI18NString(data["description"])) && (data["imageURLs"] === void 0 || Array.isArray(data["imageURLs"]) && data["imageURLs"].every((value) => typeof value === "string")) && (data["geoLocation"] === void 0 || isGeoLocation(data["geoLocation"])) && (data["connector"] === void 0 || isConnector(data["connector"])) && (data["liveTransports"] === void 0 || Array.isArray(data["liveTransports"]) && data["liveTransports"].every(isTransport)) && (data["signatures"] === void 0 || Array.isArray(data["signatures"]));
12088
+ return isMandatoryJSONObject(data) && data["@context"] === ChargeTransparencyLiveLinkContext;
12089
12089
  }
12090
12090
  function isTOTPConfig(data) {
12091
12091
  return isObject(data) && typeof data["initialSharedSecret"] === "string" && typeof data["timeStep"] === "number";
@@ -12329,6 +12329,259 @@ var validationRules_default = {
12329
12329
  }
12330
12330
  }
12331
12331
  };
12332
+ var defaultKeyIdGeneration = ["SubjectPublicKeyInfo", "DER", "SHA-256", "hex"];
12333
+ var defaultExcludedProperties = ["signatures"];
12334
+ var canonicalJSONEncodings = ["JSON", "JCS", "UTF-8"];
12335
+ var subjectPublicKeyInfoHeaders = {
12336
+ "EdDSA-Ed25519": "302A300506032B6570032100",
12337
+ "EdDSA-Ed448": "3043300506032B6571033A00",
12338
+ "ECDSA-secp256r1": "3059301306072A8648CE3D020106082A8648CE3D030107034200",
12339
+ "ECDSA-secp384r1": "3076301006072A8648CE3D020106052B81040022036200",
12340
+ "ECDSA-secp521r1": "30819B301006072A8648CE3D020106052B8104002303818600"
12341
+ };
12342
+ function readDERLength2(bytes, offset) {
12343
+ const first = bytes[offset];
12344
+ if (first === void 0)
12345
+ throw new Error("Truncated ASN.1 length!");
12346
+ if (first < 128)
12347
+ return { length: first, size: 1 };
12348
+ const count = first & 127;
12349
+ if (count === 0 || count > 4)
12350
+ throw new Error("Unsupported ASN.1 length!");
12351
+ let length = 0;
12352
+ for (let index = 1; index <= count; index++) {
12353
+ const byte = bytes[offset + index];
12354
+ if (byte === void 0)
12355
+ throw new Error("Truncated ASN.1 length!");
12356
+ length = length * 256 + byte;
12357
+ }
12358
+ return { length, size: count + 1 };
12359
+ }
12360
+ function subjectPublicKeyInfoKeyMaterial(spki) {
12361
+ if (spki[0] !== 48)
12362
+ throw new Error("A SubjectPublicKeyInfo must start with a SEQUENCE!");
12363
+ let index = 1 + readDERLength2(spki, 1).size;
12364
+ if (spki[index] !== 48)
12365
+ throw new Error("The SubjectPublicKeyInfo does not start with an AlgorithmIdentifier!");
12366
+ const algorithmLength = readDERLength2(spki, index + 1);
12367
+ index += 1 + algorithmLength.size + algorithmLength.length;
12368
+ if (spki[index] !== 3)
12369
+ throw new Error("The SubjectPublicKeyInfo does not contain a BIT STRING!");
12370
+ const keyLength = readDERLength2(spki, index + 1);
12371
+ const start = index + 1 + keyLength.size;
12372
+ if (spki[start] !== 0)
12373
+ throw new Error("The SubjectPublicKeyInfo BIT STRING has unused bits!");
12374
+ const keyMaterial = spki.subarray(start + 1, start + keyLength.length);
12375
+ if (keyMaterial.length === 0)
12376
+ throw new Error("The SubjectPublicKeyInfo contains no key material!");
12377
+ return keyMaterial;
12378
+ }
12379
+ function wrapInSubjectPublicKeyInfo(keyMaterial, algorithm) {
12380
+ const header = subjectPublicKeyInfoHeaders[algorithm];
12381
+ if (header === void 0)
12382
+ throw new Error("Cannot wrap a raw " + algorithm + " key into a SubjectPublicKeyInfo!");
12383
+ const headerBytes = hexToBytes(header);
12384
+ const spki = new Uint8Array(headerBytes.length + keyMaterial.length);
12385
+ spki.set(headerBytes, 0);
12386
+ spki.set(keyMaterial, headerBytes.length);
12387
+ return spki;
12388
+ }
12389
+ function decodeText(value, encoding) {
12390
+ switch (encoding) {
12391
+ case "hex":
12392
+ return hexToBytes(value);
12393
+ case "base64":
12394
+ return base64ToBytes(value);
12395
+ default:
12396
+ throw new Error("Unsupported text encoding: " + String(encoding));
12397
+ }
12398
+ }
12399
+ function encodeText(bytes, encoding) {
12400
+ switch (encoding) {
12401
+ case "hex":
12402
+ return bytesToHex(bytes).toUpperCase();
12403
+ case "base64":
12404
+ return bytesToBase64(bytes);
12405
+ default:
12406
+ return null;
12407
+ }
12408
+ }
12409
+ function publicKeyBytes(publicKey, structure) {
12410
+ const storedStructure = publicKey.encodings[0];
12411
+ const storedBytes = decodeText(publicKey.value, publicKey.encodings[publicKey.encodings.length - 1]);
12412
+ if (storedStructure === structure)
12413
+ return storedBytes;
12414
+ if (storedStructure === "SubjectPublicKeyInfo" && structure === "raw")
12415
+ return subjectPublicKeyInfoKeyMaterial(storedBytes);
12416
+ if (storedStructure === "raw" && structure === "SubjectPublicKeyInfo")
12417
+ return wrapInSubjectPublicKeyInfo(storedBytes, publicKey.algorithm);
12418
+ throw new Error("Cannot represent a " + String(storedStructure) + " key as " + structure + "!");
12419
+ }
12420
+ function computeKeyId(publicKey, keyIdGeneration) {
12421
+ let index = 0;
12422
+ const structure = keyIdGeneration[index++];
12423
+ if (structure === void 0)
12424
+ return null;
12425
+ if (structure === "SubjectPublicKeyInfo" && keyIdGeneration[index] === "DER")
12426
+ index++;
12427
+ let bytes;
12428
+ try {
12429
+ bytes = publicKeyBytes(publicKey, structure);
12430
+ } catch {
12431
+ return null;
12432
+ }
12433
+ for (; index < keyIdGeneration.length; index++) {
12434
+ const step = keyIdGeneration[index];
12435
+ if (step === void 0)
12436
+ return null;
12437
+ switch (step) {
12438
+ case "SHA-256":
12439
+ bytes = sha256$1(bytes);
12440
+ break;
12441
+ case "SHA-384":
12442
+ bytes = sha384$1(bytes);
12443
+ break;
12444
+ case "SHA-512":
12445
+ bytes = sha512$1(bytes);
12446
+ break;
12447
+ // A pipeline ends with the text encoding of the id.
12448
+ default:
12449
+ return encodeText(bytes, step);
12450
+ }
12451
+ }
12452
+ return null;
12453
+ }
12454
+ function nobleSignatureAlgorithm(algorithm) {
12455
+ switch (algorithm) {
12456
+ case "ECDSA-secp256r1-SHA256":
12457
+ return "ECDSA-P256";
12458
+ case "ECDSA-secp384r1-SHA384":
12459
+ return "ECDSA-P384";
12460
+ case "ECDSA-secp521r1-SHA512":
12461
+ return "ECDSA-P521";
12462
+ case "EdDSA-Ed25519":
12463
+ return "Ed25519";
12464
+ case "EdDSA-Ed448":
12465
+ return "Ed448";
12466
+ case "ML-DSA-44":
12467
+ return "ML-DSA-44";
12468
+ case "ML-DSA-65":
12469
+ return "ML-DSA-65";
12470
+ case "ML-DSA-87":
12471
+ return "ML-DSA-87";
12472
+ default:
12473
+ return null;
12474
+ }
12475
+ }
12476
+ function asStringArray(value) {
12477
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
12478
+ }
12479
+ function collectDocumentPublicKeys(Document) {
12480
+ const publicKeys = new Array();
12481
+ const collectFrom = (candidates) => {
12482
+ if (!Array.isArray(candidates))
12483
+ return;
12484
+ for (const candidate of candidates) {
12485
+ const entry = asJSONObject(candidate);
12486
+ if (entry === void 0)
12487
+ continue;
12488
+ const algorithm = asString(entry["algorithm"]);
12489
+ const value = asString(entry["value"]);
12490
+ const encodings = asStringArray(entry["encodings"]);
12491
+ const keyUsage = asStringArray(entry["keyUsage"]) ?? [];
12492
+ if (algorithm === void 0 || algorithm === "" || value === void 0 || value === "" || encodings === void 0 || encodings.length === 0) {
12493
+ continue;
12494
+ }
12495
+ publicKeys.push({ algorithm, encodings, value, keyUsage });
12496
+ }
12497
+ };
12498
+ const chargingStation = asJSONObject(Document["chargingStation"]);
12499
+ const evse = asJSONObject(chargingStation?.["EVSE"]);
12500
+ collectFrom(asJSONObject(Document["chargingStationOperator"])?.["publicKeys"]);
12501
+ collectFrom(asJSONObject(evse?.["energyMeter"])?.["publicKeys"]);
12502
+ return publicKeys;
12503
+ }
12504
+ function verifyDocumentSignature(Document, Signature, Index, PublicKeys, KeyIdGeneration) {
12505
+ const entry = asJSONObject(Signature);
12506
+ if (entry === void 0)
12507
+ return { index: Index, status: "malformed", details: "The signature is not a JSON object." };
12508
+ const keyId = asString(entry["keyId"]);
12509
+ const algorithm = asString(entry["algorithm"]);
12510
+ const value = asString(entry["value"]);
12511
+ const encodings = asStringArray(entry["encodings"]);
12512
+ const signedData = asJSONObject(entry["signedData"]);
12513
+ const described = (status, details) => ({
12514
+ index: Index,
12515
+ ...keyId !== void 0 ? { keyId } : {},
12516
+ ...algorithm !== void 0 ? { algorithm } : {},
12517
+ status,
12518
+ ...details !== void 0 ? { details } : {}
12519
+ });
12520
+ if (keyId === void 0 || keyId === "" || algorithm === void 0 || algorithm === "" || value === void 0 || value === "" || encodings === void 0 || encodings.length === 0) {
12521
+ return described("malformed", "A signature needs a keyId, an algorithm, encodings and a value.");
12522
+ }
12523
+ const signedEncodings = asStringArray(signedData?.["encodings"]) ?? canonicalJSONEncodings;
12524
+ if (signedEncodings.length !== canonicalJSONEncodings.length || !signedEncodings.every((step, position) => step === canonicalJSONEncodings[position])) {
12525
+ return described(
12526
+ "unsupportedAlgorithm",
12527
+ "Only [ " + canonicalJSONEncodings.join(", ") + " ] signed data is understood here."
12528
+ );
12529
+ }
12530
+ const excludedProperties = asStringArray(signedData?.["excludedProperties"]) ?? defaultExcludedProperties;
12531
+ if (!excludedProperties.includes("signatures"))
12532
+ excludedProperties.push("signatures");
12533
+ let signedBytes;
12534
+ try {
12535
+ signedBytes = canonicalJSONBytes(
12536
+ Object.fromEntries(
12537
+ Object.entries(Document).filter(([property]) => !excludedProperties.includes(property))
12538
+ )
12539
+ );
12540
+ } catch (exception) {
12541
+ return described(
12542
+ "malformed",
12543
+ "The signed properties could not be canonicalized: " + (exception instanceof Error ? exception.message : String(exception))
12544
+ );
12545
+ }
12546
+ const suiteAlgorithm = nobleSignatureAlgorithm(algorithm);
12547
+ if (suiteAlgorithm === null)
12548
+ return described("unsupportedAlgorithm", "Unsupported signature algorithm: " + algorithm);
12549
+ const wantedKeyId = keyId.toUpperCase();
12550
+ const publicKey = PublicKeys.find((candidate) => computeKeyId(candidate, KeyIdGeneration)?.toUpperCase() === wantedKeyId);
12551
+ if (publicKey === void 0)
12552
+ return described("unknownPublicKey", "No public key of this document has the key id " + keyId + ".");
12553
+ try {
12554
+ const suite = getSignatureSuite(suiteAlgorithm);
12555
+ const publicKeyBytes_ = publicKeyBytes(publicKey, "raw");
12556
+ const signatureBytes = decodeText(value, encodings[encodings.length - 1]);
12557
+ if (!suite.isValidPublicKey(publicKeyBytes_))
12558
+ return described("malformed", "The public key is not valid for " + algorithm + ".");
12559
+ return described(suite.verify(signedBytes, signatureBytes, publicKeyBytes_) ? "validSignature" : "invalidSignature");
12560
+ } catch (exception) {
12561
+ return described(
12562
+ "malformed",
12563
+ exception instanceof Error ? exception.message : String(exception)
12564
+ );
12565
+ }
12566
+ }
12567
+ function verifyDocumentSignatures(Document) {
12568
+ const signatures = Document["signatures"];
12569
+ if (!Array.isArray(signatures) || signatures.length === 0)
12570
+ return { status: "unsigned", signatures: [], validCount: 0 };
12571
+ const keyIdGeneration = asStringArray(Document["keyIdGeneration"]) ?? defaultKeyIdGeneration;
12572
+ const publicKeys = collectDocumentPublicKeys(Document);
12573
+ const results = signatures.map(
12574
+ (signature, index) => verifyDocumentSignature(Document, signature, index, publicKeys, keyIdGeneration)
12575
+ );
12576
+ const validCount = results.filter((result) => result.status === "validSignature").length;
12577
+ return {
12578
+ status: validCount === 0 ? "noneValid" : validCount === results.length ? "allValid" : "someValid",
12579
+ signatures: results,
12580
+ validCount
12581
+ };
12582
+ }
12583
+
12584
+ // src/chargy.ts
12332
12585
  function isPdfAttachment(value) {
12333
12586
  if (!isMandatoryJSONObject(value))
12334
12587
  return false;
@@ -13265,6 +13518,7 @@ var Chargy = class {
13265
13518
  throw new Error("Parsed JSON content is not a JSON object!");
13266
13519
  const JSONContext = JSONContent["@context"];
13267
13520
  if (IsAChargeTransparencyLiveLink(JSONContent)) {
13521
+ this.verifyLiveLinkSignatures(JSONContent);
13268
13522
  JSONContent.created ??= (/* @__PURE__ */ new Date()).toISOString();
13269
13523
  processedFile.result = JSONContent;
13270
13524
  } else if (JSONContent["format"] === "ptb")
@@ -13434,6 +13688,56 @@ var Chargy = class {
13434
13688
  return IsAChargeTransparencyRecord(verifiedCTR) ? verifiedCTR : void 0;
13435
13689
  }
13436
13690
  //#endregion
13691
+ //#region (private) verifyLiveLinkSignatures(LiveLink)
13692
+ // A live link may be signed as a whole by the operator, which is what ties
13693
+ // the transport URLs and the listed public keys to whoever signed them.
13694
+ // Those signatures are verified whenever the document carries any.
13695
+ //
13696
+ // Nothing here rejects a document. An unsigned live link, an unknown key or
13697
+ // even a signature that does not match is reported as a warning and the
13698
+ // document stays usable: its transports still work, and its signed meter
13699
+ // values carry their own signatures, which are verified separately. What a
13700
+ // reader makes of the warning is the reader's decision.
13701
+ //
13702
+ // Must run before anything is added to the document - see the note at the
13703
+ // call site.
13704
+ verifyLiveLinkSignatures(LiveLink) {
13705
+ const result = verifyDocumentSignatures(LiveLink);
13706
+ const warnings = new Array();
13707
+ const warn = (messageKey, level) => {
13708
+ const message = this.GetMultilanguageText(messageKey);
13709
+ if (!warnings.some((warning) => warning.message === message))
13710
+ warnings.push({ level, message });
13711
+ };
13712
+ if (result.status === "unsigned")
13713
+ warn("DocumentSignature_Missing", "low" /* low */);
13714
+ for (const signature of result.signatures) {
13715
+ switch (signature.status) {
13716
+ case "validSignature":
13717
+ break;
13718
+ // A signature that demonstrably does not match its document is
13719
+ // the one case that says something is actually wrong.
13720
+ case "invalidSignature":
13721
+ warn("DocumentSignature_Mismatch", "high" /* high */);
13722
+ break;
13723
+ // The rest mean "cannot be judged here", which is a weaker
13724
+ // statement than "is wrong".
13725
+ case "unknownPublicKey":
13726
+ warn("DocumentSignature_UnknownPublicKey", "medium" /* medium */);
13727
+ break;
13728
+ case "unsupportedAlgorithm":
13729
+ warn("DocumentSignature_UnsupportedAlgorithm", "medium" /* medium */);
13730
+ break;
13731
+ case "malformed":
13732
+ warn("DocumentSignature_Malformed", "medium" /* medium */);
13733
+ break;
13734
+ }
13735
+ }
13736
+ LiveLink.signatureVerification = result;
13737
+ if (warnings.length > 0)
13738
+ LiveLink.warnings = [...LiveLink.warnings ?? [], ...warnings];
13739
+ }
13740
+ //#endregion
13437
13741
  //#region (private) collectLiveLinkMeterValueKeys(LiveLink)
13438
13742
  // Every public key of the document that is allowed to sign meter values.
13439
13743
  // A charging session is regularly signed by more than one of them: the
@@ -13813,6 +14117,6 @@ buffer/index.js:
13813
14117
  *)
13814
14118
  */
13815
14119
 
13816
- export { ACrypt, Alfen, AlfenCrypt01, BSMCrypt01, CanonicalJSONError, ChargeIT, ChargePoint, ChargePointCrypt01, IChargeTransparencyLiveLink_exports as ChargeTransparencyLiveLink, ChargeTransparencyLiveLinkContext, IChargeTransparencyRecord_exports as ChargeTransparencyRecord, Chargy, chargyInterfaces_exports as ChargyInterfaces, Clone, CloneCTR, ConcatenateBuffers, CreateDiv, CreateDiv2, CreateError, CreateWarning, CryptoAlgorithms, CryptoHashAlgorithms, DayOfWeek, DisplayPrefixes, EDL40, EDL40Crypt01, EDL40ValidationError, EDL40_OBIS, EDL40_SESSION_CONTEXT, EDL40_SIGNATURE_CONTEXT, EMHCrypt01, ErrorLevel, GDFCrypt01, IECCurves, IEncoding, InformationRelevance, InformationRelevanceToString, IsAChargeTransparencyLiveLink, IsAChargeTransparencyRecord, IsAPublicKey, IsAPublicKeyLookup, IsAPublicKeySignature, IsAPublicKeyXY, IsASessionCryptoResult, IsAURL, IsNullOrEmpty, IsValidURL, JSONSignatureVerificationStatus, MENNEKES_EDL40_OBIS, MENNEKES_EDL40_XMLNS, Mennekes, MennekesCrypt01, OBIS2Hex, OBIS2MeasurementName, OBIS_RegExpr, OCMF, OCMFBonnTariffParseError, OCMFTransactionTypes, OCMF_SIGNATURE_ALGORITHMS, OCMFv1_x, OCPI, OIDInfo, PCDF, PCDFCrypt01, PCDFParseError, PCDFValidationError, PCDF_FIELD_ORDER, PCDF_PREFIX, PTB, ParseJSON_LD, PublicKeyFormats, IPublicKeyInfo_exports as PublicKeyInfo, SAFEXML, SessionVerificationResult, SetHex, SetInt8, SetText, SetText_withLength, SetTimestamp, SetTimestamp32, SetUInt32, SetUInt32_withCode, SetUInt64, SetUInt64D, SignMessage, SignatureFormats, IURL_exports as SimpleURL, TimeStatusTypes, URLContext, UTC2human, VerificationResult, VerifyJSONMessageSignatures, WarningLevel, WhenNullOrEmpty, XMLContainer, asJSONArray, asJSONObject, asNumber, asString, base64ToBytes, buf2hex, buildEDL40Signature, buildIsaSignature, buildMennekesSignatureData, bytesToBase64, bytesToHex, canParseEDL40, canonicalJSONBytes, canonicalJSONStringify, cleanHex, closeFullscreen, createCompatibleCurve, createHexString, createLegacyP192Curve, dateToMennekesLocalEpochSeconds, decodeSmlMessages, extractMennekesChargingProcesses, findEntryByObis, findGetListRes, firstKey, firstValue, generateSignatureKeyPair, getArrayElement, getArrayLikeElement, getDirectChildByLocalName, getDirectChildrenByLocalName, getElementsByLocalName, getFirstArrayElement, getInt16Bytes, getInt32Bytes, getInt64Bytes, getInt8Bytes, getLastArrayElement, getOCMFSignatureDisplay, getSignatureSuite, getTrimmedTextContent, hashFile, hex2bin, hex32, hexToArrayBuffer, hexToBytes, intFromBytes, isEncodedValue, isGeoLocation, isI18NString, isICryptoResult, isIFileInfo, isISessionCryptoResult1, isISessionCryptoResult2, isJSONLDObject, isMandatoryArrayOfStrings, isMandatoryBoolean, isMandatoryDecimal, isMandatoryJSONArray, isMandatoryJSONObject, isMandatoryNumber, isMandatoryString, isMandatoryURL, isOIDInfo, isObject, isOptionalArrayOfStrings, isOptionalDecimal, isOptionalJSONArray, isOptionalJSONArrayError, isOptionalJSONArrayOk, isOptionalJSONObject, isOptionalNumber, isOptionalString, isOptionalStringArray, isOptionalStringOrOIDInfo, isOptionalURL, isPCDFText, isPublicKeySubject, isString, isStringArray, isStringOrOIDInfo, isStringOrStringArray, isaListNameContext, jsonPrettyPrinter, measurementName2human, meterTimeZone, normalizePCDFPublicKeyHex, normalizeXMLText, ocmfBonnTariffToChargingTariff, openFullscreen, pad, parseAndVerifyJSONSignatures, parseDescription, parseEDL40, parseHexString, parseMennekesXMLDocument, parseNumber, parseOBIS, parseOCMFBonnTariffText, parsePCDFDocument, parsePCDFPublicKey, parsePCDFSignature, parseSmlTime, parseUTC, readQRCodeTextFromImage, readQRCodeTextFromImageData, readTLV, secp224k1, setUILocale, sha256, sha256____, sha384, sha384____, sha512, sha512____, signJSONMessage, signMessage, stripPCDFControlCharacters, stripTransport, time2human, timeZoneOffsetMinutes, toArrayBuffer, toSessionVerificationResults, toUint8Array, transformEDL40Status, tryParseOCMFBonnTariffText, unquotePCDFText, validatePCDFFields, verifyEDL40Document, verifyJSONMessageSignatureResults, verifyJSONMessageSignatures, verifyJSONSignature, verifyJSONSignatureResult, verifyPCDFDocument };
14120
+ export { ACrypt, Alfen, AlfenCrypt01, BSMCrypt01, CanonicalJSONError, ChargeIT, ChargePoint, ChargePointCrypt01, IChargeTransparencyLiveLink_exports as ChargeTransparencyLiveLink, ChargeTransparencyLiveLinkContext, IChargeTransparencyRecord_exports as ChargeTransparencyRecord, Chargy, chargyInterfaces_exports as ChargyInterfaces, Clone, CloneCTR, ConcatenateBuffers, CreateDiv, CreateDiv2, CreateError, CreateWarning, CryptoAlgorithms, CryptoHashAlgorithms, DayOfWeek, DisplayPrefixes, EDL40, EDL40Crypt01, EDL40ValidationError, EDL40_OBIS, EDL40_SESSION_CONTEXT, EDL40_SIGNATURE_CONTEXT, EMHCrypt01, ErrorLevel, GDFCrypt01, IECCurves, IEncoding, InformationRelevance, InformationRelevanceToString, IsAChargeTransparencyLiveLink, IsAChargeTransparencyRecord, IsAPublicKey, IsAPublicKeyLookup, IsAPublicKeySignature, IsAPublicKeyXY, IsASessionCryptoResult, IsAURL, IsNullOrEmpty, IsValidURL, JSONSignatureVerificationStatus, MENNEKES_EDL40_OBIS, MENNEKES_EDL40_XMLNS, Mennekes, MennekesCrypt01, OBIS2Hex, OBIS2MeasurementName, OBIS_RegExpr, OCMF, OCMFBonnTariffParseError, OCMFTransactionTypes, OCMF_SIGNATURE_ALGORITHMS, OCMFv1_x, OCPI, OIDInfo, PCDF, PCDFCrypt01, PCDFParseError, PCDFValidationError, PCDF_FIELD_ORDER, PCDF_PREFIX, PTB, ParseJSON_LD, PublicKeyFormats, IPublicKeyInfo_exports as PublicKeyInfo, SAFEXML, SessionVerificationResult, SetHex, SetInt8, SetText, SetText_withLength, SetTimestamp, SetTimestamp32, SetUInt32, SetUInt32_withCode, SetUInt64, SetUInt64D, SignMessage, SignatureFormats, IURL_exports as SimpleURL, TimeStatusTypes, URLContext, UTC2human, VerificationResult, VerifyJSONMessageSignatures, WarningLevel, WhenNullOrEmpty, XMLContainer, asJSONArray, asJSONObject, asNumber, asString, base64ToBytes, buf2hex, buildEDL40Signature, buildIsaSignature, buildMennekesSignatureData, bytesToBase64, bytesToHex, canParseEDL40, canonicalJSONBytes, canonicalJSONStringify, cleanHex, closeFullscreen, collectDocumentPublicKeys, createCompatibleCurve, createHexString, createLegacyP192Curve, dateToMennekesLocalEpochSeconds, decodeSmlMessages, extractMennekesChargingProcesses, findEntryByObis, findGetListRes, firstKey, firstValue, generateSignatureKeyPair, getArrayElement, getArrayLikeElement, getDirectChildByLocalName, getDirectChildrenByLocalName, getElementsByLocalName, getFirstArrayElement, getInt16Bytes, getInt32Bytes, getInt64Bytes, getInt8Bytes, getLastArrayElement, getOCMFSignatureDisplay, getSignatureSuite, getTrimmedTextContent, hashFile, hex2bin, hex32, hexToArrayBuffer, hexToBytes, intFromBytes, isEncodedValue, isGeoLocation, isI18NString, isICryptoResult, isIFileInfo, isISessionCryptoResult1, isISessionCryptoResult2, isJSONLDObject, isMandatoryArrayOfStrings, isMandatoryBoolean, isMandatoryDecimal, isMandatoryJSONArray, isMandatoryJSONObject, isMandatoryNumber, isMandatoryString, isMandatoryURL, isOIDInfo, isObject, isOptionalArrayOfStrings, isOptionalDecimal, isOptionalJSONArray, isOptionalJSONArrayError, isOptionalJSONArrayOk, isOptionalJSONObject, isOptionalNumber, isOptionalString, isOptionalStringArray, isOptionalStringOrOIDInfo, isOptionalURL, isPCDFText, isPublicKeySubject, isString, isStringArray, isStringOrOIDInfo, isStringOrStringArray, isaListNameContext, jsonPrettyPrinter, measurementName2human, meterTimeZone, normalizePCDFPublicKeyHex, normalizeXMLText, ocmfBonnTariffToChargingTariff, openFullscreen, pad, parseAndVerifyJSONSignatures, parseDescription, parseEDL40, parseHexString, parseMennekesXMLDocument, parseNumber, parseOBIS, parseOCMFBonnTariffText, parsePCDFDocument, parsePCDFPublicKey, parsePCDFSignature, parseSmlTime, parseUTC, readQRCodeTextFromImage, readQRCodeTextFromImageData, readTLV, secp224k1, setUILocale, sha256, sha256____, sha384, sha384____, sha512, sha512____, signJSONMessage, signMessage, stripPCDFControlCharacters, stripTransport, time2human, timeZoneOffsetMinutes, toArrayBuffer, toSessionVerificationResults, toUint8Array, transformEDL40Status, tryParseOCMFBonnTariffText, unquotePCDFText, validatePCDFFields, verifyDocumentSignatures, verifyEDL40Document, verifyJSONMessageSignatureResults, verifyJSONMessageSignatures, verifyJSONSignature, verifyJSONSignatureResult, verifyPCDFDocument };
13817
14121
  //# sourceMappingURL=index.js.map
13818
14122
  //# sourceMappingURL=index.js.map