Switch to EC encryption
This commit is contained in:
Родитель
eafac6ad37
Коммит
61c91032e0
|
@ -3425,6 +3425,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz",
|
||||
"integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g=="
|
||||
},
|
||||
"@types/node-jose": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.5.tgz",
|
||||
"integrity": "sha512-fScnd7GdaHC31PYouWR0xYSOXQLrmxPhLM92CYlVy4UetSwis2u5e6khMazj1Xyidt8zPeRU0PHLmI+mpamhGQ==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
|
||||
|
@ -14932,6 +14940,11 @@
|
|||
"chalk": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"long": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
|
@ -15930,6 +15943,39 @@
|
|||
"integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=",
|
||||
"dev": true
|
||||
},
|
||||
"node-jose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.0.0.tgz",
|
||||
"integrity": "sha512-j8zoFze1gijl8+DK/dSXXqX7+o2lMYv1XS+ptnXgGV/eloQaqq1YjNtieepbKs9jBS4WTnMOqyKSaQuunJzx0A==",
|
||||
"requires": {
|
||||
"base64url": "^3.0.1",
|
||||
"buffer": "^5.5.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"lodash": "^4.17.15",
|
||||
"long": "^4.0.0",
|
||||
"node-forge": "^0.10.0",
|
||||
"pako": "^1.0.11",
|
||||
"process": "^0.11.10",
|
||||
"uuid": "^3.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-forge": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
|
||||
},
|
||||
"pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-libs-browser": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
|
||||
|
@ -17906,8 +17952,7 @@
|
|||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
|
||||
"dev": true
|
||||
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@decentralized-identity/sidetree": "^0.9.1",
|
||||
"@types/node-jose": "^1.1.5",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"axios": "^0.19.2",
|
||||
|
@ -42,6 +43,7 @@
|
|||
"json-canonicalize": "^1.0.3",
|
||||
"jsonschema": "^1.2.10",
|
||||
"multihashes": "^0.4.19",
|
||||
"node-jose": "^2.0.0",
|
||||
"qr-scanner": "^1.1.1",
|
||||
"qrcode": "^1.4.4",
|
||||
"querystring": "^0.2.0",
|
||||
|
|
17
src/dids.ts
17
src/dids.ts
|
@ -18,17 +18,20 @@ export async function verifyJws(jws: string, {
|
|||
}
|
||||
|
||||
// TODO remove 'JwsVerificationKey2020' when prototypes have updated
|
||||
const ENCRYPTION_KEY_TYPES = ['RSAEncryptionPublicKey', 'JwsVerificationKey2020'];
|
||||
const ENCRYPTION_KEY_TYPES = ['JsonWebKey2020', 'JwsVerificationKey2020'];
|
||||
|
||||
export async function encryptFor(jws: string, did: string, { generateEncryptionKey }: KeyGenerators, keyId?: string) {
|
||||
export async function encryptFor(jws: string, did: string, { generateEncryptionKey }: KeyGenerators, keyIdIn?: string) {
|
||||
const didDoc = (await axios.get(resolveUrl + encodeURIComponent(did))).data;
|
||||
const encryptionKeys = didDoc.publicKey.filter(k => ENCRYPTION_KEY_TYPES.includes(k.type));
|
||||
|
||||
const encryptionKey = keyId ? encryptionKeys.filter(k => k.id === keyId)[0] :
|
||||
encryptionKeys[0];
|
||||
const keyId = keyIdIn ? keyIdIn : encryptionKeys[0].id;
|
||||
const encryptionKey = encryptionKeys.filter(k => k.id === keyId)[0]
|
||||
|
||||
const ek = await generateEncryptionKey(encryptionKey.publicKeyJwk);
|
||||
return ek.encrypt({ kid: encryptionKey.kid }, jws);
|
||||
const ek = await generateEncryptionKey({
|
||||
...encryptionKey.publicKeyJwk,
|
||||
kid: keyId
|
||||
});
|
||||
return ek.encrypt({ kid: keyId }, jws);
|
||||
}
|
||||
const resolveKeyId = async (kid: string): Promise<JsonWebKey> => {
|
||||
const fragment = '#' + kid.split('#')[1];
|
||||
|
@ -57,7 +60,7 @@ export async function generateDid({ signingPublicJwk, encryptionPublicJwk, recov
|
|||
}, {
|
||||
id: 'encryption-key-1',
|
||||
purpose: ['general', 'auth'],
|
||||
type: 'RSAEncryptionPublicKey',
|
||||
type: 'JsonWebKey2020',
|
||||
jwk: encryptionPublicJwk
|
||||
}]
|
||||
}]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import base64url from 'base64url';
|
||||
import { randomBytes, PublicKeyInput } from 'crypto';
|
||||
import jose, { JWT } from 'jose';
|
||||
import jjose, { JWT } from 'jose';
|
||||
import jose from 'node-jose';
|
||||
import secp256k1 from 'secp256k1';
|
||||
import { EncryptionKey, SigningKey, KeyGenerators } from './KeyTypes';
|
||||
|
||||
|
@ -18,36 +19,58 @@ export const keyGenerators: KeyGenerators = {
|
|||
export function encodeSection (data: any): string {
|
||||
return base64url.encode(JSON.stringify(data));
|
||||
}
|
||||
export async function generateEncryptionKey (inputPublic?: JsonWebKey, inputPrivate?: JsonWebKey): Promise<EncryptionKey> {
|
||||
let publicKey;
|
||||
let privateKey = null;
|
||||
let privateJwk;
|
||||
const keystore = jose.JWK.createKeyStore();
|
||||
const encryptionKeyPros = {
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"alg": 'ECDH-ES',
|
||||
}
|
||||
export async function generateEncryptionKey(inputPublic?: JsonWebKey, inputPrivate?: JsonWebKey): Promise<EncryptionKey> {
|
||||
let publicKey: jose.JWK.Key | null = null;
|
||||
let privateKey: jose.JWK.Key | null = null;
|
||||
|
||||
if (inputPublic) {
|
||||
publicKey = jose.JWK.asKey(inputPublic as PublicKeyInput);
|
||||
publicKey = await jose.JWK.asKey(inputPublic);
|
||||
|
||||
if (inputPrivate) {
|
||||
privateKey = await jose.JWK.asKey(inputPrivate)
|
||||
}
|
||||
} else {
|
||||
privateKey = jose.JWK.generateSync('RSA');
|
||||
publicKey = privateKey;
|
||||
privateJwk = privateKey.toJWK(true);
|
||||
publicKey = privateKey = await keystore.generate("EC", "P-256", encryptionKeyPros);
|
||||
}
|
||||
|
||||
const publicJwkFull = publicKey.toJWK(false);
|
||||
|
||||
const publicJwk = {
|
||||
alg: publicJwkFull.alg,
|
||||
e: publicJwkFull.e,
|
||||
kty: publicJwkFull.kty,
|
||||
n: publicJwkFull.n
|
||||
};
|
||||
const publicJwk = publicKey.toJSON(false);
|
||||
const privateJwk = privateKey ? privateKey.toJSON(true) : null;
|
||||
|
||||
return {
|
||||
decrypt: async (payload) => {
|
||||
return (await jose.JWE.decrypt(payload, privateKey)).toString();
|
||||
// TODO assertions about header
|
||||
if (!privateKey) {
|
||||
throw new Error('Cannot decrypt with a public key');
|
||||
}
|
||||
|
||||
try {
|
||||
const ret = await jose.JWE.createDecrypt(privateKey).decrypt(payload);
|
||||
return ret.plaintext.toString();
|
||||
} catch (e) {
|
||||
console.log("Error decrypting", e)
|
||||
}
|
||||
},
|
||||
encrypt: async (header, payload) => {
|
||||
return jose.JWE.encrypt(payload, publicJwk, {
|
||||
...header,
|
||||
enc: "A256GCM"
|
||||
});
|
||||
const input = payload;
|
||||
const jwe = await jose.JWE
|
||||
.createEncrypt({
|
||||
format: 'compact',
|
||||
fields: {
|
||||
...header,
|
||||
enc: 'A256GCM',
|
||||
}
|
||||
}, publicKey)
|
||||
.update(jose.util.asBuffer(input)).final()
|
||||
console.log("Encrypt", header, (publicKey as any).kid,
|
||||
jwe
|
||||
)
|
||||
return jwe;
|
||||
},
|
||||
publicJwk,
|
||||
privateJwk
|
||||
|
@ -59,9 +82,9 @@ export async function generateSigningKey (inputPublic?: JsonWebKey, inputPrivate
|
|||
let privateKey = null;
|
||||
let privateJwk;
|
||||
if (inputPublic) {
|
||||
publicKey = jose.JWK.asKey(inputPublic as PublicKeyInput);
|
||||
publicKey = jjose.JWK.asKey(inputPublic as PublicKeyInput);
|
||||
} else {
|
||||
privateKey = jose.JWK.generateSync('EC', 'secp256k1');
|
||||
privateKey = jjose.JWK.generateSync('EC', 'secp256k1');
|
||||
publicKey = privateKey;
|
||||
privateJwk = privateKey.toJWK(true);
|
||||
}
|
||||
|
@ -77,12 +100,12 @@ export async function generateSigningKey (inputPublic?: JsonWebKey, inputPrivate
|
|||
|
||||
return {
|
||||
sign: async (header, payload) => {
|
||||
return jose.JWS.sign(payload, privateKey, header);
|
||||
return jjose.JWS.sign(payload, privateKey, header);
|
||||
},
|
||||
verify: async (jwt) => {
|
||||
let result;
|
||||
try {
|
||||
result = jose.JWS.verify(jwt, publicJwk);
|
||||
result = jjose.JWS.verify(jwt, publicJwk);
|
||||
return {
|
||||
payload: result,
|
||||
valid: true
|
||||
|
|
60
src/keys.ts
60
src/keys.ts
|
@ -1,5 +1,7 @@
|
|||
import base64url from 'base64url';
|
||||
import { Jose } from 'jose-jwe-jws';
|
||||
import jose from 'node-jose';
|
||||
|
||||
import { EncryptionKey, SigningKey, KeyGenerators } from './KeyTypes';
|
||||
|
||||
import elliptic from 'elliptic'
|
||||
|
@ -7,6 +9,7 @@ import sha256 from './sha256';
|
|||
|
||||
const EC = elliptic.ec;
|
||||
const ec = new EC('secp256k1');
|
||||
const keystore = jose.JWK.createKeyStore();
|
||||
|
||||
export function encodeSection(data: any): string {
|
||||
return base64url.encode(JSON.stringify(data));
|
||||
|
@ -17,57 +20,54 @@ export const keyGenerators: KeyGenerators = {
|
|||
generateSigningKey
|
||||
};
|
||||
|
||||
const encryptionKeyPros = {
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"alg": 'ECDH-ES',
|
||||
}
|
||||
export async function generateEncryptionKey(inputPublic?: JsonWebKey, inputPrivate?: JsonWebKey): Promise<EncryptionKey> {
|
||||
let publicKey: CryptoKey | null = null;
|
||||
let privateKey: CryptoKey | null = null;
|
||||
let publicKey: jose.JWK.Key | null = null;
|
||||
let privateKey: jose.JWK.Key | null = null;
|
||||
|
||||
if (inputPublic) {
|
||||
publicKey = await window.crypto.subtle.importKey('jwk', inputPublic, {
|
||||
name: 'RSA-OAEP',
|
||||
hash: { name: 'SHA-1' }
|
||||
}, true, ['encrypt', 'wrapKey']);
|
||||
publicKey = await jose.JWK.asKey(inputPublic);
|
||||
|
||||
if (inputPrivate) {
|
||||
privateKey = await window.crypto.subtle.importKey('jwk', inputPrivate, {
|
||||
name: 'RSA-OAEP',
|
||||
hash: { name: 'SHA-1' }
|
||||
}, true, ['decrypt', 'unwrapKey']);
|
||||
privateKey = await jose.JWK.asKey(inputPrivate)
|
||||
}
|
||||
} else {
|
||||
const k = await window.crypto.subtle.generateKey({
|
||||
name: 'RSA-OAEP',
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
||||
hash: { name: 'SHA-1' }
|
||||
}, true, // extractable
|
||||
['encrypt', 'wrapKey', 'decrypt', 'unwrapKey']);
|
||||
publicKey = k.publicKey;
|
||||
privateKey = k.privateKey;
|
||||
publicKey = privateKey = await keystore.generate("EC", "P-256", encryptionKeyPros);
|
||||
}
|
||||
|
||||
const publicJwk = await window.crypto.subtle.exportKey('jwk', publicKey);
|
||||
const privateJwk = privateKey ? await window.crypto.subtle.exportKey('jwk', privateKey) : null;
|
||||
const publicJwk = publicKey.toJSON(false);
|
||||
const privateJwk = privateKey ? privateKey.toJSON(true) : null;
|
||||
|
||||
const cryptographer = new Jose.WebCryptographer();
|
||||
cryptographer.setKeyEncryptionAlgorithm('RSA-OAEP');
|
||||
cryptographer.setContentEncryptionAlgorithm('A256GCM');
|
||||
return {
|
||||
decrypt: async (payload) => {
|
||||
// TODO assertions about header
|
||||
if (!privateKey) {
|
||||
throw new Error('Cannot decrypt with a public key');
|
||||
}
|
||||
const decrypter = new Jose.JoseJWE.Decrypter(cryptographer, Promise.resolve(privateKey));
|
||||
|
||||
try {
|
||||
const ret = await decrypter.decrypt(payload);
|
||||
return ret;
|
||||
const ret = await jose.JWE.createDecrypt(privateKey).decrypt(payload);
|
||||
return ret.plaintext.toString();
|
||||
} catch (e) {
|
||||
console.log("Error decrypting", e)
|
||||
}
|
||||
},
|
||||
encrypt: async (header, payload) => {
|
||||
const input = payload;
|
||||
const encrypter = new Jose.JoseJWE.Encrypter(cryptographer, publicKey);
|
||||
Object.entries(header).forEach(([k, v]) => encrypter.addHeader(k, v));
|
||||
return encrypter.encrypt(input);
|
||||
const jwe = await jose.JWE
|
||||
.createEncrypt({
|
||||
format: 'compact',
|
||||
fields: {
|
||||
...header,
|
||||
enc: 'A256GCM',
|
||||
}
|
||||
}, publicKey)
|
||||
.update(jose.util.asBuffer(input)).final()
|
||||
return jwe;
|
||||
},
|
||||
publicJwk,
|
||||
privateJwk
|
||||
|
|
Загрузка…
Ссылка в новой задаче