Skip to content
This repository has been archived by the owner on Sep 22, 2020. It is now read-only.

Logangirvin/json serialization #20

Merged
merged 43 commits into from
Mar 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4f675a1
starting work on trying to get JSON Serialization supported.
logangirvin Mar 9, 2019
1cd823a
added a sign function for Flattened JSON JWS
logangirvin Mar 9, 2019
f31cdca
JWE and decrypt support JSON Serialization?
logangirvin Mar 11, 2019
bca4b51
fixed decrypt for AAD tags, added encrypt flat.
logangirvin Mar 11, 2019
17faeaa
a comment of explanation and type safety. Added JWS constructor tests.
logangirvin Mar 12, 2019
e652e55
getHeaders JWS tests
logangirvin Mar 12, 2019
5b124ff
Just left a single line that shouldn't happen on getSignature untested.
logangirvin Mar 12, 2019
e17dd7c
fixed final JWS test and started on JWE constructor tests.
logangirvin Mar 12, 2019
05b0d93
finished constructor tests.
logangirvin Mar 13, 2019
09c7967
why was I not using getHeader?
logangirvin Mar 13, 2019
b9a4b4b
fixed aad to what I believe is RFC spec?
logangirvin Mar 13, 2019
652377e
added getHeader tests.
logangirvin Mar 13, 2019
061aed7
fixed lint.
logangirvin Mar 14, 2019
e8e9978
Big changes coming for cryptosuite to support arbitrary symmetric key…
logangirvin Mar 14, 2019
8743555
added untested CBC HMAC for AES
logangirvin Mar 15, 2019
9cda2e8
Added GCM support.
logangirvin Mar 15, 2019
92c694a
added a header comment.
logangirvin Mar 15, 2019
785dee5
adding symmetric keys to the other crypto suites.
logangirvin Mar 15, 2019
1b1dbce
added to the crypto factory.
logangirvin Mar 15, 2019
074019d
fixed tests. TIme to see how much of the JWE RFC I got wrong.
logangirvin Mar 15, 2019
ad4367e
These should be exported...
logangirvin Mar 15, 2019
bb1afaf
so HMAC might be wrong.
logangirvin Mar 15, 2019
dec2e19
this is going surprisngly well.
logangirvin Mar 15, 2019
92c5b4a
I believe the refactor was a somewhat success.
logangirvin Mar 15, 2019
61d6e2f
I'm not sure what this test was trying to accomplish. Its not in the …
logangirvin Mar 15, 2019
4676dc6
changing functionality woo.
logangirvin Mar 15, 2019
5dffda7
fixing the constructor check back for JWS.
logangirvin Mar 15, 2019
f11f207
fixed up compact JWE..
logangirvin Mar 15, 2019
f02c751
forgot to check the length on JWE.
logangirvin Mar 15, 2019
5502153
I just realized what these constructor tests were for.
logangirvin Mar 15, 2019
68079fa
the progressive amount of check edge cases on JWE is daunting.
logangirvin Mar 15, 2019
0aca906
the most frustrating and questionable behaviors I've seen from typesc…
logangirvin Mar 16, 2019
0cf997c
fixed primary JWS/JWE tests to cover most branches.
logangirvin Mar 18, 2019
a009437
Fixed the AL which fixed the HMAC.
logangirvin Mar 18, 2019
4746e9e
unit test validated hmac.
logangirvin Mar 18, 2019
0827ba2
AES fully covered.
logangirvin Mar 18, 2019
3c7f0e4
HMAC fully validated.
logangirvin Mar 18, 2019
4c58a81
lint and added data for RSA-OAEP AES GCM validation
logangirvin Mar 18, 2019
3989373
most of the validation works. I'm having difficulties getting the CEK…
logangirvin Mar 19, 2019
6e2c339
RSA JWS validated
logangirvin Mar 19, 2019
ac2c783
fixed lint.
logangirvin Mar 19, 2019
a63ce96
addressed code comments.
logangirvin Mar 19, 2019
65fdabd
adding RFC specs to validation values
logangirvin Mar 20, 2019
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
3 changes: 2 additions & 1 deletion lib/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import uuid from 'uuid/v4';
import VerifiedRequest from './interfaces/VerifiedRequest';
import AuthenticationRequest from './interfaces/AuthenticationRequest';
import AuthenticationResponse from './interfaces/AuthenticationResponse';
import AesCryptoSuite from './crypto/aes/AesCryptoSuite';

/**
* Named arguments to construct an Authentication object
Expand Down Expand Up @@ -49,7 +50,7 @@ export default class Authentication {
this.resolver = options.resolver;
this.tokenValidDurationInMinutes = options.tokenValidDurationInMinutes || Constants.defaultTokenDurationInMinutes;
this.keys = options.keys;
this.factory = new CryptoFactory(options.cryptoSuites || [new RsaCryptoSuite(), new Secp256k1CryptoSuite()]);
this.factory = new CryptoFactory(options.cryptoSuites || [new AesCryptoSuite(), new RsaCryptoSuite(), new Secp256k1CryptoSuite()]);
}

/**
Expand Down
40 changes: 38 additions & 2 deletions lib/CryptoFactory.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import CryptoSuite, { Encrypter, Signer, PublicKeyConstructors } from './interfaces/CryptoSuite';
import CryptoSuite, { Encrypter, Signer, PublicKeyConstructors, SymmetricEncrypter } from './interfaces/CryptoSuite';
import { IDidDocumentPublicKey } from '@decentralized-identity/did-common-typescript';
import JweToken from './security/JweToken';
import JwsToken from './security/JwsToken';

/** A dictionary of JWA encryption algorithm names to Encrypter objects */
type EncrypterMap = {[name: string]: Encrypter};
/** A dictionary of JWA encryption algorithm names to Encyprter objects */
type SymmetricEncrypterMap = {[name: string]: SymmetricEncrypter};
/** A dictionary of JWA signing algorithm names to Signer objects */
type SignerMap = { [name: string]: Signer };

Expand All @@ -14,7 +16,9 @@ type SignerMap = { [name: string]: Signer };
export default class CryptoFactory {

private encrypters: EncrypterMap;
private symmetricEncrypters: SymmetricEncrypterMap;
private signers: SignerMap;
private defaultSymmetricAlgorithm: string;

// the constructors should be factored out as they don't really relate to the pure crypto
private keyConstructors: PublicKeyConstructors;
Expand All @@ -23,10 +27,12 @@ export default class CryptoFactory {
* Constructs a new CryptoRegistry
* @param suites The suites to use for dependency injeciton
*/
constructor (suites: CryptoSuite[]) {
constructor (suites: CryptoSuite[], defaultSymmetricAlgorithm?: string) {
this.encrypters = {};
this.symmetricEncrypters = {};
this.signers = {};
this.keyConstructors = {};
this.defaultSymmetricAlgorithm = 'none';

// takes each suite (CryptoSuite objects) and maps to name of the algorithm.
suites.forEach((suite) => {
Expand All @@ -35,6 +41,11 @@ export default class CryptoFactory {
this.encrypters[encrypterKey] = encAlgorithms[encrypterKey];
}

const symEncAlgorithms = suite.getSymmetricEncrypters();
for (const encrypterKey in symEncAlgorithms) {
this.symmetricEncrypters[encrypterKey] = symEncAlgorithms[encrypterKey];
}

const signerAlgorithms = suite.getSigners();
for (const signerKey in signerAlgorithms) {
this.signers[signerKey] = signerAlgorithms[signerKey];
Expand All @@ -45,6 +56,15 @@ export default class CryptoFactory {
this.keyConstructors[keyType] = pluginKeyConstructors[keyType];
}
});

if (defaultSymmetricAlgorithm) {
this.defaultSymmetricAlgorithm = defaultSymmetricAlgorithm;
} else {
for (const algorithm in this.symmetricEncrypters) {
this.defaultSymmetricAlgorithm = algorithm;
break;
}
}
}

/**
Expand Down Expand Up @@ -91,4 +111,20 @@ export default class CryptoFactory {
getSigner (name: string): Signer {
return this.signers[name];
}

/**
* Gets the SymmetricEncrypter object given the symmetric encryption algorithm's name
* @param name The name of the algorithm
* @returns The corresponding SymmetricEncrypter, if any
*/
getSymmetricEncrypter (name: string): SymmetricEncrypter {
return this.symmetricEncrypters[name];
}

/**
* Gets the default symmetric encryption algorithm to use
*/
getDefaultSymmetricEncryptionAlgorithm (): string {
return this.defaultSymmetricAlgorithm;
}
}
220 changes: 220 additions & 0 deletions lib/crypto/aes/AesCryptoSuite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import CryptoSuite, { Encrypter, Signer, SymmetricEncrypter, PublicKeyConstructors } from '../../interfaces/CryptoSuite';
import crypto from 'crypto';

/**
* Encrypter plugin for Advanced Encryption Standard symmetric keys
*/
export default class AesCryptoSuite implements CryptoSuite {

getEncrypters (): { [algorithm: string]: Encrypter } {
return {};
}

getSigners (): { [algorithm: string]: Signer; } {
return {};
}

getKeyConstructors (): PublicKeyConstructors {
return {};
}

getSymmetricEncrypters (): { [algorithm: string]: SymmetricEncrypter } {
return {
'A128GCM': {
encrypt: this.encryptAesGcm(128),
decrypt: this.decryptAesGcm(128)
},
'A192GCM': {
encrypt: this.encryptAesGcm(192),
decrypt: this.decryptAesGcm(192)
},
'A256GCM': {
encrypt: this.encryptAesGcm(256),
decrypt: this.decryptAesGcm(256)
},
'A128CBC-HS256': {
encrypt: this.encryptAesCbcHmacSha2(128, 256),
decrypt: this.decryptAesCbcHmacSha2(128, 256)
},
'A192CBC-HS384': {
encrypt: this.encryptAesCbcHmacSha2(192, 384),
decrypt: this.decryptAesCbcHmacSha2(192, 384)
},
'A256CBC-HS512': {
encrypt: this.encryptAesCbcHmacSha2(256, 512),
decrypt: this.decryptAesCbcHmacSha2(256, 512)
}
};
}

/**
* Given the encryption parameters, returns the AES CBC HMAC SHA2 encryption function
* @param keySize Size of the keys
* @param hashSize Size of the SHA2 hash
* @returns a SymmetricEncrypter encrypt function
*/
private encryptAesCbcHmacSha2 (keySize: number, hashSize: number): (plaintext: Buffer, additionalAuthenticatedData: Buffer) =>
Promise<{ciphertext: Buffer, initializationVector: Buffer, key: Buffer, tag: Buffer}> {
return async (plaintext: Buffer, additionalAuthenticatedData: Buffer) => {
const mackey = this.generateSymmetricKey(keySize);
const enckey = this.generateSymmetricKey(keySize);
const initializationVector = this.generateInitializationVector(128);
const algorithm = `aes-${keySize}-cbc`;
const cipher = crypto.createCipheriv(algorithm, enckey, initializationVector);
const ciphertext = Buffer.concat([
cipher.update(plaintext),
cipher.final()
]);
const tag = this.generateHmacTag(hashSize, keySize, mackey, additionalAuthenticatedData, initializationVector, ciphertext);
return {
ciphertext,
initializationVector,
key: Buffer.concat([mackey, enckey]),
tag
};
};
}

/**
* Given the decryption parameters, returns an AES CBC HMAC SHA2 decryption function
* @param keySize Size of the keys
* @param hashSize Size of the SHA2 hash
* @returns a SymmetricEncrypter decrypt function
*/
private decryptAesCbcHmacSha2 (keySize: number, hashSize: number):
(ciphertext: Buffer, additionalAuthenticatedData: Buffer, initializationVector: Buffer, key: Buffer, tag: Buffer) =>
Promise<Buffer> {
return async (ciphertext: Buffer, additionalAuthenticatedData: Buffer, initializationVector: Buffer, key: Buffer, tag: Buffer) => {
const splitLength = key.length / 2;
const mackey = key.slice(0, splitLength);
const enckey = key.slice(splitLength, key.length);
const computedTag = this.generateHmacTag(hashSize, keySize, mackey, additionalAuthenticatedData, initializationVector, ciphertext);
if (computedTag.compare(tag) !== 0) {
throw new Error('Invalid tag');
}
const algorithm = `aes-${keySize}-cbc`;
const decipher = crypto.createDecipheriv(algorithm, enckey, initializationVector);
const plaintext = Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
return plaintext;
};
}

/**
* Given the encryption parameters, returns the AES GCM encryption function
* @param keySize Size of the keys
* @returns a SymmetricEncrypter encrypt function
*/
private encryptAesGcm (keySize: number): (plaintext: Buffer, additionalAuthenticatedData: Buffer) =>
Promise<{ciphertext: Buffer, initializationVector: Buffer, key: Buffer, tag: Buffer}> {
return async (plaintext: Buffer, additionalAuthenticatedData: Buffer) => {
const key = this.generateSymmetricKey(keySize);
const initializationVector = this.generateInitializationVector(96);
const algorithm = `aes-${keySize}-gcm`;
const cipher = crypto.createCipheriv(algorithm, key, initializationVector) as crypto.CipherGCM;
cipher.setAAD(additionalAuthenticatedData);
const ciphertext = Buffer.concat([
cipher.update(plaintext),
cipher.final()
]);
return {
ciphertext,
initializationVector,
key,
tag: cipher.getAuthTag()
};
};
}

/**
* Given the decryption parameters, returns an AES GCM decryption function
* @param keySize Size of the keys
* @returns a SymmetricEncrypter decrypt function
*/
private decryptAesGcm (keySize: number):
(ciphertext: Buffer, additionalAuthenticatedData: Buffer, initializationVector: Buffer, key: Buffer, tag: Buffer) =>
Promise<Buffer> {
return async (ciphertext: Buffer, additionalAuthenticatedData: Buffer, initializationVector: Buffer, key: Buffer, tag: Buffer) => {
const algorithm = `aes-${keySize}-gcm`;
const decipher = crypto.createDecipheriv(algorithm, key, initializationVector) as crypto.DecipherGCM;
decipher.setAAD(additionalAuthenticatedData);
decipher.setAuthTag(tag);
return Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
};
}

/**
* Generates the HMAC Tag
* @param hashSize HMAC hash size
* @param keySize HMAC tag size
* @param mackey MAC key
* @param additionalAuthenticatedData Additional authenticated data
* @param initializationVector initialization vector
* @param ciphertext encrypted data
* @returns HMAC Tag
*/
private generateHmacTag (hashSize: number, keySize: number, mackey: Buffer,
additionalAuthenticatedData: Buffer, initializationVector: Buffer, ciphertext: Buffer): Buffer {
const mac = this.generateHmac(hashSize, mackey, additionalAuthenticatedData, initializationVector, ciphertext);
return mac.slice(0, Math.ceil(keySize / 8));
}

/**
* Generates the full HMac
* @param hashSize HMAC hash size
* @param mackey MAC key
* @param additionalAuthenticatedData Additional authenticated data
* @param initializationVector initialization vector
* @param ciphertext encrypted data
* @returns HMAC in full
*/
private generateHmac (hashSize: number, mackey: Buffer,
additionalAuthenticatedData: Buffer, initializationVector: Buffer, ciphertext: Buffer): Buffer {
const al = this.getAdditionalAuthenticatedDataLength(additionalAuthenticatedData);
const hmac = crypto.createHmac(`sha${hashSize}`, mackey);
hmac.update(additionalAuthenticatedData);
hmac.update(initializationVector);
hmac.update(ciphertext);
hmac.update(al);
const mac = hmac.digest();
return mac;
}

/**
* Gets the Additional Authenticated Data length in Big Endian notation
* @param additionalAuthenticatedData Additional authenticated data
* @return Additional Authenticated Data returned as a base64 big endian unsigned integer
*/
private getAdditionalAuthenticatedDataLength (additionalAuthenticatedData: Buffer): Buffer {
const aadLength = additionalAuthenticatedData.length * 8;
const alMsb = aadLength & 0xFFFFFFFF00000000;
const alLsb = (aadLength >> 32) & 0x00000000FFFFFFFF;
const al = Buffer.alloc(8);
al.writeUInt32BE(alMsb, 0);
al.writeUInt32BE(alLsb, 4);
return al;
}

// these are two different functions to allow validation against RFC specs

/**
* Generates a symmetric key
* @param bits Size in bits of the key
*/
private generateSymmetricKey (bits: number): Buffer {
return crypto.randomBytes(Math.ceil(bits / 8));
}

/**
* Generates an initialization vector
* @param bits Size in bits of the initialization vector
*/
private generateInitializationVector (bits: number): Buffer {
return crypto.randomBytes(Math.ceil(bits / 8));
}
}
7 changes: 6 additions & 1 deletion lib/crypto/ec/Secp256k1CryptoSuite.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import EcPublicKey from './EcPublicKey';
import CryptoSuite from '../../interfaces/CryptoSuite';
import CryptoSuite, { SymmetricEncrypter } from '../../interfaces/CryptoSuite';
import PrivateKey from '../../security/PrivateKey';
import PublicKey from '../../security/PublicKey';
import { IDidDocumentPublicKey } from '@decentralized-identity/did-common-typescript';
Expand All @@ -10,6 +10,11 @@ const ecKey = require('ec-key');
* Encrypter plugin for Elliptic Curve P-256K1
*/
export class Secp256k1CryptoSuite implements CryptoSuite {

getSymmetricEncrypters (): { [algorithm: string]: SymmetricEncrypter } {
return {};
}

/** Encryption with Secp256k1 keys not supported */
getEncrypters () {
return {};
Expand Down
7 changes: 6 additions & 1 deletion lib/crypto/rsa/RsaCryptoSuite.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import RsaPublicKey from './RsaPublicKey';
import CryptoSuite from '../../interfaces/CryptoSuite';
import CryptoSuite, { SymmetricEncrypter } from '../../interfaces/CryptoSuite';
import { IDidDocumentPublicKey } from '@decentralized-identity/did-common-typescript';
// TODO: Create and reference TypeScript definition file for 'jwk-to-pem'
const jwkToPem = require('jwk-to-pem');
Expand All @@ -12,6 +12,11 @@ import PublicKey from '../../security/PublicKey';
* Encrypter plugin for RsaSignature2018
*/
export class RsaCryptoSuite implements CryptoSuite {

getSymmetricEncrypters (): { [algorithm: string]: SymmetricEncrypter } {
return {};
}

getEncrypters () {
return {
'RSA-OAEP': {
Expand Down
6 changes: 6 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ import { RsaCryptoSuite } from './crypto/rsa/RsaCryptoSuite';
import PrivateKeyRsa from './crypto/rsa/RsaPrivateKey';
import { Secp256k1CryptoSuite } from './crypto/ec/Secp256k1CryptoSuite';
import EcPrivateKey from './crypto/ec/EcPrivateKey';
import AesCryptoSuite from './crypto/aes/AesCryptoSuite';
import JweToken from './security/JweToken';
import JwsToken from './security/JwsToken';
import CryptoFactory from './CryptoFactory';
import Authentication, { AuthenticationOptions } from './Authentication';
import VerifiedRequest from './interfaces/VerifiedRequest';
import TestPrivateKey from '../tests/mocks/TestPrivateKey';
import { TestPublicKey } from '../tests/mocks/TestPublicKey';
import TestCryptoSuite from '../tests/mocks/TestCryptoProvider';

export { Authentication, AuthenticationOptions, VerifiedRequest };
export { CryptoSuite, Encrypter, Signer };
export { PublicKey, PrivateKey };
export { RsaCryptoSuite, PrivateKeyRsa };
export { Secp256k1CryptoSuite, EcPrivateKey };
export { AesCryptoSuite };
export { CryptoFactory, JwsToken, JweToken };
export { TestCryptoSuite, TestPrivateKey, TestPublicKey };
Loading