Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition to backend for WoT, following new roadmap #4

Merged
merged 1 commit into from
Apr 27, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions src/security/inter-trust.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { Decoder, Encoder } from '@ndn/tlv';
import { Data, Interest, Name, Signer, Verifier } from '@ndn/packet';
import { Certificate, createSigner, createVerifier, ECDSA, ValidityPeriod } from '@ndn/keychain';
import * as endpoint from '@ndn/endpoint';
import type { Forwarder } from '@ndn/fw';
import { Storage } from '../storage/mod.ts';
import { base64ToBytes } from '@ucla-irl/ndnts-aux/utils';
import { SecurityAgent } from './types.ts';

/**
* A Signer & Verifier that handles security authentication.
* CertStorage itself is not a storage, actually. Depend on an external storage.
*/
export class InterTrust implements SecurityAgent {
private _signer: Signer | undefined;
readonly readyEvent: Promise<void>;
private trustedNames: string[] = [];

constructor(
readonly trustAnchor: Certificate,
readonly ownCertificate: Certificate,
readonly storage: Storage,
readonly fw: Forwarder,
prvKeyBits: Uint8Array,
) {
this.readyEvent = (async () => {
await this.importCert(trustAnchor);
await this.importCert(ownCertificate);
const keyPair = await ECDSA.cryptoGenerate({
importPkcs8: [prvKeyBits, ownCertificate.publicKeySpki],
}, true);
this._signer = createSigner(
ownCertificate.name.getPrefix(ownCertificate.name.length - 2),
ECDSA,
keyPair,
).withKeyLocator(ownCertificate.name);
})();
}

/** Obtain the signer */
get signer() {
return this._signer!;
}

/** Obtain this node's own certificate */
get certificate() {
return this.ownCertificate;
}

/** Import an external certificate into the storage */
async importCert(cert: Certificate) {
await this.storage.set(cert.name.toString(), Encoder.encode(cert.data));
}

/**
* Fetch a certificate based on its name from local storage and then remote.
* @param keyName The certificate's name.
* @param localOnly If `true`, only look up the local storage without sending an Interest.
* @returns The fetched certificate. `undefined` if not found.
*/
async getCertificate(keyName: Name, localOnly: boolean): Promise<Certificate | undefined> {
const certBytes = await this.storage.get(keyName.toString());
if (certBytes === undefined) {
if (localOnly) {
return undefined;
} else {
try {
const result = await endpoint.consume(
new Interest(
keyName,
Interest.MustBeFresh,
Interest.Lifetime(5000),
),
{
// Fetched key must be signed by a known key
// TODO: Find a better way to handle security
verifier: this.localVerifier,
retx: 5,
fw: this.fw,
},
);

// Cache result certificates
this.storage.set(result.name.toString(), Encoder.encode(result));

return Certificate.fromData(result);
} catch {
return undefined;
}
}
} else {
return Certificate.fromData(Decoder.decode(certBytes, Data));
}
}

/**
* Verify a packet. Throw an error if failed.
* @param pkt The packet to verify.
* @param localOnly If `true`, only look up the local storage for the certificate.
*/
async verify(pkt: Verifier.Verifiable, localOnly: boolean) {
const keyName = pkt.sigInfo?.keyLocator?.name;
if (!keyName) {
throw new Error(`Data not signed: ${pkt.name.toString()}`);
}
const cert = await this.getCertificate(keyName, localOnly);
if (cert === undefined) {
throw new Error(`No certificate: ${pkt.name.toString()} signed by ${keyName.toString()}`);
}
const verifier = await createVerifier(cert, { algoList: [ECDSA] });
try {
await verifier.verify(pkt);
} catch (error) {
throw new Error(`Unable to verify ${pkt.name.toString()} signed by ${keyName.toString()} due to: ${error}`);
}
}

/** Obtain an verifier that fetches certificate */
get verifier(): Verifier {
return {
verify: (pkt) => this.verify(pkt, false),
};
}

/** Obtain an verifier that does not fetch certificate remotely */
get localVerifier(): Verifier {
return {
verify: (pkt) => this.verify(pkt, true),
};
}

public static async create(
trustAnchor: Certificate,
ownCertificate: Certificate,
storage: Storage,
fw: Forwarder,
prvKeyBits: Uint8Array,
) {
const result = new InterTrust(
trustAnchor,
ownCertificate,
storage,
fw,
prvKeyBits,
);
await result.readyEvent;
return result;
}

//function that takes b64 encoded certificate bytes and returns cert
decodeCert = (b64Value: string) => {
const wire = base64ToBytes(b64Value);
const data = Decoder.decode(wire, Data);
const cert = Certificate.fromData(data);
return cert;
};

/** Import an external certificate in base64 format into the storage */
async importCertAsb64(certb64: string) {
const cert = this.decodeCert(certb64); // convert b64 into type Certificate
await this.storage.set(cert.name.toString(), Encoder.encode(cert.data)); // put certificate into cache
}

async generatePoR(
strangerCert: string, // stranger's certficate in base64 format
): Promise<string> {
//function that generates the name of cross signed certificate
//this function follows the naming convention described in Intertrust poster
const generatePoRName = (strangerKeyname: string, zone_A_certname: string = this.trustAnchor.name.toString()) => {
// Construct <Z_a name encoded>
const parts: string[] = zone_A_certname.split('/');
let zone_A_name: string = '';
for (let i = 0; i < parts.length; i++) { //put <Z_a name encoded> together with a loop
if (parts[i].includes('KEY')) {
break;
}
zone_A_name += parts[i] + '/';
}

return new Name(strangerKeyname + '/' + zone_A_name + '/v1');
};

const sCert = this.decodeCert(strangerCert); //stranger's cert

//create validity parameters to be set with .build method later
const now = Date.now();
const oneHourInMilliseconds = 60 * 60 * 1000; // Number of milliseconds in an hour, change this if you want to adjust validity period
const validityPeriodEnd = now + oneHourInMilliseconds;
const validityPeriod = new ValidityPeriod();
validityPeriod.notBefore = now;
validityPeriod.notAfter = validityPeriodEnd;

//this check is needed because Certificate.build function needs non-null signer
if (!this._signer) {
throw new Error('Signer is undefined');
}
// Create a cross-signed certificate, i.e. the PoR
const proofOfZoneRecognition = await Certificate.build({
name: generatePoRName(sCert.name.toString()),
validity: validityPeriod,
publicKeySpki: sCert.publicKeySpki, // Now accessing getPublicKey on the Certificate object
signer: this._signer,
});

// Cache PoR certificate
this.storage.set(proofOfZoneRecognition.name.toString(), Encoder.encode(proofOfZoneRecognition.data));

// Encode the final certificate to base64 using Node.js Buffer
const encodedPoR = Buffer.from(Encoder.encode(proofOfZoneRecognition.data)).toString('base64');

return encodedPoR; // Return the encoded string
}
}