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

fix(Pluto): encoding / decoding PrivateKeys #78

Merged
merged 1 commit into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
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
12 changes: 12 additions & 0 deletions src/apollo/utils/Ed25519PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class Ed25519PrivateKey extends PrivateKey implements SignableKey {
public raw: Uint8Array;
public keySpecification: Map<string, string> = new Map();

// TODO - nativeValue wants a Buffer, otherwise getInstance breaks
constructor(nativeValue: Uint8Array) {
super();
this.raw = nativeValue;
Expand Down Expand Up @@ -44,4 +45,15 @@ export class Ed25519PrivateKey extends PrivateKey implements SignableKey {
const signature = this.getInstance().sign(message);
return signature.toBytes();
}

public readonly to = {
Buffer: () => this.getEncoded(),
Hex: () => this.to.Buffer().toString("hex")
};

static from = {
Buffer: (value: Buffer) => new Ed25519PrivateKey(value),
Hex: (value: string) => Ed25519PrivateKey.from.Buffer(Buffer.from(value, "hex")),
String: (value: string) => Ed25519PrivateKey.from.Buffer(Buffer.from(value)),
};
}
21 changes: 16 additions & 5 deletions src/apollo/utils/Secp256k1PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export class Secp256k1PrivateKey extends PrivateKey implements SignableKey {
return new Uint8Array([...padding, ...byteList]);
}

sign(message: Buffer) {
const keyPair = Secp256k1PrivateKey.ec.keyFromPrivate(this.getEncoded());
const sig = keyPair.sign(message);
return Buffer.from(sig.toDER());
}

static secp256k1FromBigInteger(bigInteger: BN): Secp256k1PrivateKey {
return new Secp256k1PrivateKey(Uint8Array.from(bigInteger.toArray()));
}
Expand All @@ -61,9 +67,14 @@ export class Secp256k1PrivateKey extends PrivateKey implements SignableKey {
return new Secp256k1PrivateKey(Uint8Array.from(bnprv.toArray()));
}

sign(message: Buffer) {
const keyPair = Secp256k1PrivateKey.ec.keyFromPrivate(this.getEncoded());
const sig = keyPair.sign(message);
return Buffer.from(sig.toDER());
}
public readonly to = {
Buffer: () => Buffer.from(this.getEncoded()),
Hex: () => this.to.Buffer().toString("hex")
};

static from = {
Buffer: (value: Buffer) => Secp256k1PrivateKey.secp256k1FromBytes(new Uint8Array(value)),
Hex: (value: string) => Secp256k1PrivateKey.from.Buffer(Buffer.from(value, "hex")),
String: (value: string) => Secp256k1PrivateKey.from.Buffer(Buffer.from(value))
};
}
12 changes: 12 additions & 0 deletions src/apollo/utils/X25519PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,16 @@ export class X25519PrivateKey extends PrivateKey {
const pub = base64url.baseEncode(x25519PrivateKey.publicKey);
return new X25519PublicKey(Buffer.from(pub));
}

public readonly to = {
Buffer: () => this.getEncoded(),
Hex: () => this.to.Buffer().toString("hex")
};

static from = {
Buffer: (value: Buffer) => new X25519PrivateKey(new Uint8Array(value)),
Hex: (value: string) => X25519PrivateKey.from.Buffer(Buffer.from(value, "hex")),
String: (value: string) => X25519PrivateKey.from.Buffer(Buffer.from(value))
};

}
42 changes: 22 additions & 20 deletions src/apollo/utils/jwt/JWT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Services,
VerificationMethods,
DID,
PrivateKey,
} from "../../../domain";

export class JWT {
Expand All @@ -32,37 +33,37 @@
(prop): prop is Services => prop instanceof Services
);

return {
didResolutionMetadata: { contentType: "application/did+ld+json" },
didDocumentMetadata: {},
didDocument: {
id: resolved.id.toString(),
alsoKnownAs: alsoKnownAs && alsoKnownAs.values,
controller:
controller && controller.values
? controller.values.map((v) => v.toString())
: [],
verificationMethod:
verificationMethods && verificationMethods.values
? verificationMethods.values.map((vm) => {
if (vm.publicKeyMultibase) {
return {
id: vm.id,
type: "EcdsaSecp256k1VerificationKey2019",
controller: vm.controller,
publicKeyMultibase: vm.publicKeyMultibase,
};
}
if (vm.publicKeyJwk) {
return {
id: vm.id,
type: "JsonWebKey2020",
controller: vm.controller,
publicKeyJwk: vm.publicKeyJwk,
};
}
throw new Error("Invalid KeyType");
})
if (vm.publicKeyMultibase) {
return {
id: vm.id,
type: "EcdsaSecp256k1VerificationKey2019",
controller: vm.controller,
publicKeyMultibase: vm.publicKeyMultibase,
};

Check warning on line 55 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 56 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 56 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
if (vm.publicKeyJwk) {
return {
id: vm.id,
type: "JsonWebKey2020",
controller: vm.controller,
publicKeyJwk: vm.publicKeyJwk,
};

Check warning on line 63 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 64 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 64 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
throw new Error("Invalid KeyType");

Check warning on line 65 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
})

Check warning on line 66 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: [],
service:
service?.values?.reduce<didResolver.Service[]>((acc, service) => {
Expand All @@ -82,11 +83,12 @@

async sign(
issuer: DID,
privateKey: Uint8Array,
privateKey: PrivateKey | Uint8Array,
payload: Partial<didJWT.JWTPayload>
): Promise<string> {
const raw = privateKey instanceof PrivateKey ? privateKey.raw : privateKey;
//TODO: Better check if this method is called with PrismDID and not PeerDID or other
const signer = didJWT.ES256KSigner(privateKey);
const signer = didJWT.ES256KSigner(raw);
const jwt = await didJWT.createJWT(
payload,
{ issuer: issuer.toString(), signer },
Expand Down
5 changes: 5 additions & 0 deletions src/domain/models/keyManagement/PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ export abstract class PrivateKey extends Key {
return this.raw;
}
abstract publicKey(): PublicKey;

abstract to: {
Buffer: () => Buffer;
Hex: () => string;
};
}
101 changes: 53 additions & 48 deletions src/pluto/Pluto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import { KeyProperties } from "../domain/models/KeyProperties";
import { Ed25519PrivateKey } from "../apollo/utils/Ed25519PrivateKey";
import { X25519PrivateKey } from "../apollo/utils/X25519PrivateKey";
import { Secp256k1PrivateKey } from "../apollo/utils/Secp256k1PrivateKey";

type IgnoreProps = "entries" | "entityPrefix" | "metadataTableName";
export type PlutoConnectionProps =
Expand Down Expand Up @@ -81,13 +82,13 @@
constructor(connection: PlutoConnectionProps) {
const presetSqlJSConfig =
connection.type === "sqljs"
? {
location: "pluto",
useLocalForage: typeof window !== "undefined",
sqlJsConfig: {
locateFile: (file: string) => `${this.wasmUrl}/dist/${file}`,
},
}
location: "pluto",
useLocalForage: typeof window !== "undefined",
sqlJsConfig: {
locateFile: (file: string) => `${this.wasmUrl}/dist/${file}`,

Check warning on line 89 in src/pluto/Pluto.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 89 in src/pluto/Pluto.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
},
}

Check warning on line 91 in src/pluto/Pluto.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: {};
this.dataSource = new DataSource({
...presetSqlJSConfig,
Expand Down Expand Up @@ -160,7 +161,9 @@
didEntity.methodId = did.methodId;
didEntity.schema = did.schema;
didEntity.alias = alias ?? "";

await this.dataSource.manager.save(didEntity);

await this.storePrivateKeys(
privateKey,
did,
Expand Down Expand Up @@ -192,7 +195,7 @@
did,
privateKey.getProperty(KeyProperties.index)
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
parseInt(privateKey.getProperty(KeyProperties.index)!)
parseInt(privateKey.getProperty(KeyProperties.index)!)

Check warning on line 198 in src/pluto/Pluto.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: 0,
null
)
Expand Down Expand Up @@ -266,11 +269,17 @@
metaId: string | null
) {
const privateKeysEntity = new entities.PrivateKey();
metaId && (privateKeysEntity.id = metaId); // question: Where should I store metaId

if (typeof metaId === "string") {
// question: Where should I store metaId
privateKeysEntity.id = metaId;
}

privateKeysEntity.curve = privateKey.curve;
privateKeysEntity.privateKey = Buffer.from(privateKey.value).toString();
privateKeysEntity.privateKey = privateKey.to.Hex();
privateKeysEntity.keyPathIndex = keyPathIndex ?? 0;
privateKeysEntity.didId = did.toString();

await this.dataSource.manager.save(privateKeysEntity);
}

Expand Down Expand Up @@ -383,11 +392,11 @@
}
return didResponse.map(
(item) =>
({
did: DID.fromString(item.did),
alias: item.alias,
keyPathIndex: item.private_key_keyPathIndex,
} as PrismDIDInfo)
({
did: DID.fromString(item.did),
alias: item.alias,
keyPathIndex: item.private_key_keyPathIndex,
} as PrismDIDInfo)
);
} catch (error) {
throw new Error((error as Error).message);
Expand Down Expand Up @@ -454,11 +463,11 @@
* @async
* @returns {unknown}
*/
async getAllPeerDIDs() {
async getAllPeerDIDs(): Promise<PeerDID[]> {
const didRepository: Repository<entities.DID> =
this.dataSource.manager.getRepository("did");
const privateKeysRepository =
this.dataSource.manager.getRepository("private_key");
this.dataSource.manager.getRepository<entities.PrivateKey>("private_key");
/*
* This method is overcomplicated, dids should have relations.
* */
Expand All @@ -482,13 +491,16 @@
})
);

return didsWithKeys.map((item) => ({
did: DID.fromString(item.did),
privateKeys: item.privateKeys.map((key) => ({
const peerDIDs = didsWithKeys.map(item => {
const privateKeys = item.privateKeys.map((key) => ({
keyCurve: getKeyCurveByNameAndIndex(key.curve, key.keyPathIndex),
value: Buffer.from(key.privateKey),
})),
})) as PeerDID[];
value: Buffer.from(key.privateKey, "hex"),
}));

return new PeerDID(DID.fromString(item.did), privateKeys);
});

return peerDIDs;
} catch (error) {
throw new Error((error as Error).message);
}
Expand All @@ -499,27 +511,26 @@
*
* @async
* @param {DID} did
* @returns {unknown}
* @returns {PrivateKey[]}
*/
async getDIDPrivateKeysByDID(did: DID): Promise<PrivateKey[]> {
const repository = this.dataSource.manager.getRepository("private_key");
try {
const didString = did.toString();
const data = await repository.findBy({
didId: Like(`${didString}%`),
});
return data.map((item) => {
const keyCurve = getKeyCurveByNameAndIndex(item.curve);
const repository = this.dataSource.manager.getRepository<entities.PrivateKey>("private_key");
const data = await repository.findBy({ didId: Like(`${did.toString()}%`) });

if (keyCurve.curve === Curve.ED25519) {
return new Ed25519PrivateKey(Buffer.from(item.privateKey));
}
return data.map(item => this.mapToPrivateKey(item));
}

return new X25519PrivateKey(Buffer.from(item.privateKey));
}) as PrivateKey[];
} catch (error) {
throw new Error((error as Error).message);
private mapToPrivateKey(entity: entities.PrivateKey): PrivateKey {
switch (entity.curve) {
case Curve.SECP256K1:
return Secp256k1PrivateKey.from.Hex(entity.privateKey);
case Curve.ED25519:
return Ed25519PrivateKey.from.Hex(entity.privateKey);
case Curve.X25519:
return X25519PrivateKey.from.Hex(entity.privateKey);
}

throw new Error("Key Curve not recognised");

Check warning on line 533 in src/pluto/Pluto.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

/**
Expand All @@ -530,24 +541,18 @@
* @returns {unknown}
*/
async getDIDPrivateKeyByID(id: string) {
const repository = this.dataSource.manager.getRepository("private_key");
const repository = this.dataSource.manager.getRepository<entities.PrivateKey>("private_key");

try {
const data = await repository.findOne({
where: {
id,
},
where: { id },
});

if (!data) {
return null;
}
const keyCurve = getKeyCurveByNameAndIndex(data.curve);

if (keyCurve.curve === Curve.ED25519) {
return new Ed25519PrivateKey(Buffer.from(data.privateKey));
}

return new X25519PrivateKey(Buffer.from(data.privateKey));
return this.mapToPrivateKey(data);
} catch (error) {
throw new Error((error as Error).message);
}
Expand Down
2 changes: 1 addition & 1 deletion src/prism-agent/Agent.Credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,20 +231,20 @@
throw new Error("DID not found");
}

const signedJWT = await jwt.sign(
didInfo.did,
base64url.baseDecode(Buffer.from(prismPrivateKey.value).toString()),
prismPrivateKey,
{
iss: didInfo.did.toString(),
aud: domain,
nonce: challenge,
vp: {
"@context": ["https://www.w3.org/2018/presentations/v1"],
type: ["VerifiablePresentation"],
verifiableCredential: [originalJWTString],
},
}
);

Check warning on line 247 in src/prism-agent/Agent.Credentials.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

const base64JWT = base64.baseEncode(Buffer.from(signedJWT));
const presentationBody = createPresentationBody(
Expand Down
37 changes: 37 additions & 0 deletions tests/apollo/JWT.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { expect } from "chai";
import { Ed25519PrivateKey } from "../../src/apollo/utils/Ed25519PrivateKey";
import { Secp256k1PrivateKey } from "../../src/apollo/utils/Secp256k1PrivateKey";
import { X25519PrivateKey } from "../../src/apollo/utils/X25519PrivateKey";
import { DID } from "../../src/domain";
import { JWT } from "../../src/apollo/utils/jwt/JWT";


describe("Apollo - JWT", () => {
describe("sign", () => {
[
Ed25519PrivateKey,
X25519PrivateKey,
Secp256k1PrivateKey
].forEach(keyClass => {
test(`${keyClass.name} - can sign with raw key (Uint8Array)`, async function () {
const prismDid = DID.fromString("did:prism:dadsa:1231321dhsauda23847");
const privateKey = keyClass.from.Hex("5ee38f66d07fc5a7f3968b33564bbd3b00eaddd481365838a9d6d3ad2fb82f41");

const sut = new JWT({} as any);

const result = await sut.sign(prismDid, privateKey.raw, { testing: 123 });
expect(result).to.be.a.string;
});

test(`${keyClass.name} - can sign with PrivateKey`, async function () {
const prismDid = DID.fromString("did:prism:dadsa:1231321dhsauda23847");
const privateKey = keyClass.from.Hex("5ee38f66d07fc5a7f3968b33564bbd3b00eaddd481365838a9d6d3ad2fb82f41");

const sut = new JWT({} as any);

const result = await sut.sign(prismDid, privateKey, { testing: 123 });
expect(result).to.be.a.string;
});
});
});
});
Loading
Loading