1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3    return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.Metadata = void 0;
7const canonical_json_1 = require("@tufjs/canonical-json");
8const util_1 = __importDefault(require("util"));
9const base_1 = require("./base");
10const error_1 = require("./error");
11const root_1 = require("./root");
12const signature_1 = require("./signature");
13const snapshot_1 = require("./snapshot");
14const targets_1 = require("./targets");
15const timestamp_1 = require("./timestamp");
16const utils_1 = require("./utils");
17/***
18 * A container for signed TUF metadata.
19 *
20 * Provides methods to convert to and from json, read and write to and
21 * from JSON and to create and verify metadata signatures.
22 *
23 * ``Metadata[T]`` is a generic container type where T can be any one type of
24 * [``Root``, ``Timestamp``, ``Snapshot``, ``Targets``]. The purpose of this
25 * is to allow static type checking of the signed attribute in code using
26 * Metadata::
27 *
28 * root_md = Metadata[Root].fromJSON("root.json")
29 * # root_md type is now Metadata[Root]. This means signed and its
30 * # attributes like consistent_snapshot are now statically typed and the
31 * # types can be verified by static type checkers and shown by IDEs
32 *
33 * Using a type constraint is not required but not doing so means T is not a
34 * specific type so static typing cannot happen. Note that the type constraint
35 * ``[Root]`` is not validated at runtime (as pure annotations are not available
36 * then).
37 *
38 * Apart from ``expires`` all of the arguments to the inner constructors have
39 * reasonable default values for new metadata.
40 */
41class Metadata {
42    constructor(signed, signatures, unrecognizedFields) {
43        this.signed = signed;
44        this.signatures = signatures || {};
45        this.unrecognizedFields = unrecognizedFields || {};
46    }
47    sign(signer, append = true) {
48        const bytes = Buffer.from((0, canonical_json_1.canonicalize)(this.signed.toJSON()));
49        const signature = signer(bytes);
50        if (!append) {
51            this.signatures = {};
52        }
53        this.signatures[signature.keyID] = signature;
54    }
55    verifyDelegate(delegatedRole, delegatedMetadata) {
56        let role;
57        let keys = {};
58        switch (this.signed.type) {
59            case base_1.MetadataKind.Root:
60                keys = this.signed.keys;
61                role = this.signed.roles[delegatedRole];
62                break;
63            case base_1.MetadataKind.Targets:
64                if (!this.signed.delegations) {
65                    throw new error_1.ValueError(`No delegations found for ${delegatedRole}`);
66                }
67                keys = this.signed.delegations.keys;
68                if (this.signed.delegations.roles) {
69                    role = this.signed.delegations.roles[delegatedRole];
70                }
71                else if (this.signed.delegations.succinctRoles) {
72                    if (this.signed.delegations.succinctRoles.isDelegatedRole(delegatedRole)) {
73                        role = this.signed.delegations.succinctRoles;
74                    }
75                }
76                break;
77            default:
78                throw new TypeError('invalid metadata type');
79        }
80        if (!role) {
81            throw new error_1.ValueError(`no delegation found for ${delegatedRole}`);
82        }
83        const signingKeys = new Set();
84        role.keyIDs.forEach((keyID) => {
85            const key = keys[keyID];
86            // If we dont' have the key, continue checking other keys
87            if (!key) {
88                return;
89            }
90            try {
91                key.verifySignature(delegatedMetadata);
92                signingKeys.add(key.keyID);
93            }
94            catch (error) {
95                // continue
96            }
97        });
98        if (signingKeys.size < role.threshold) {
99            throw new error_1.UnsignedMetadataError(`${delegatedRole} was signed by ${signingKeys.size}/${role.threshold} keys`);
100        }
101    }
102    equals(other) {
103        if (!(other instanceof Metadata)) {
104            return false;
105        }
106        return (this.signed.equals(other.signed) &&
107            util_1.default.isDeepStrictEqual(this.signatures, other.signatures) &&
108            util_1.default.isDeepStrictEqual(this.unrecognizedFields, other.unrecognizedFields));
109    }
110    toJSON() {
111        const signatures = Object.values(this.signatures).map((signature) => {
112            return signature.toJSON();
113        });
114        return {
115            signatures,
116            signed: this.signed.toJSON(),
117            ...this.unrecognizedFields,
118        };
119    }
120    static fromJSON(type, data) {
121        const { signed, signatures, ...rest } = data;
122        if (!utils_1.guard.isDefined(signed) || !utils_1.guard.isObject(signed)) {
123            throw new TypeError('signed is not defined');
124        }
125        if (type !== signed._type) {
126            throw new error_1.ValueError(`expected '${type}', got ${signed['_type']}`);
127        }
128        let signedObj;
129        switch (type) {
130            case base_1.MetadataKind.Root:
131                signedObj = root_1.Root.fromJSON(signed);
132                break;
133            case base_1.MetadataKind.Timestamp:
134                signedObj = timestamp_1.Timestamp.fromJSON(signed);
135                break;
136            case base_1.MetadataKind.Snapshot:
137                signedObj = snapshot_1.Snapshot.fromJSON(signed);
138                break;
139            case base_1.MetadataKind.Targets:
140                signedObj = targets_1.Targets.fromJSON(signed);
141                break;
142            default:
143                throw new TypeError('invalid metadata type');
144        }
145        const sigMap = signaturesFromJSON(signatures);
146        return new Metadata(signedObj, sigMap, rest);
147    }
148}
149exports.Metadata = Metadata;
150function signaturesFromJSON(data) {
151    if (!utils_1.guard.isObjectArray(data)) {
152        throw new TypeError('signatures is not an array');
153    }
154    return data.reduce((acc, sigData) => {
155        const signature = signature_1.Signature.fromJSON(sigData);
156        return { ...acc, [signature.keyID]: signature };
157    }, {});
158}
159