Skip to content

Commit

Permalink
change signer and verifier interface
Browse files Browse the repository at this point in the history
  • Loading branch information
jihyunlab-phil committed Dec 31, 2023
1 parent 57647cb commit 421dee1
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 14 deletions.
14 changes: 10 additions & 4 deletions src/cryptos/asymmetric/crypto-factory.asymmetric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import { PrivateCipher } from './private-cipher.asymmetric';
import { PublicCipher } from './public-cipher.asymmetric';
import * as crypto from 'crypto';

export const createSigner = (key: crypto.KeyObject) => {
return new Signer(key);
export const createSigner = (
key: crypto.KeyLike | crypto.SignKeyObjectInput | crypto.SignPrivateKeyInput,
algorithm: string | null | undefined = null
) => {
return new Signer(key, algorithm);
};

export const createVerifier = (key: crypto.KeyObject) => {
return new Verifier(key);
export const createVerifier = (
key: crypto.KeyLike | crypto.VerifyKeyObjectInput | crypto.VerifyPublicKeyInput | crypto.VerifyJsonWebKeyInput,
algorithm: string | null | undefined = null
) => {
return new Verifier(key, algorithm);
};

export const createPrivateCipher = (key: crypto.RsaPrivateKey | crypto.KeyLike) => {
Expand Down
11 changes: 8 additions & 3 deletions src/cryptos/asymmetric/signer.asymmetric.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import * as crypto from 'crypto';

export class Signer {
private key: crypto.KeyObject;
private algorithm: string | null | undefined;
private key: crypto.KeyLike | crypto.SignKeyObjectInput | crypto.SignPrivateKeyInput;

constructor(key: crypto.KeyObject) {
constructor(
key: crypto.KeyLike | crypto.SignKeyObjectInput | crypto.SignPrivateKeyInput,
algorithm: string | null | undefined = null
) {
this.key = key;
this.algorithm = algorithm;
}

sign(message: Buffer) {
return crypto.sign(null, message, this.key);
return crypto.sign(this.algorithm, message, this.key);
}
}
15 changes: 12 additions & 3 deletions src/cryptos/asymmetric/verifier.asymmetric.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import * as crypto from 'crypto';

export class Verifier {
private key: crypto.KeyObject;
private algorithm: string | null | undefined;
private key:
| crypto.KeyLike
| crypto.VerifyKeyObjectInput
| crypto.VerifyPublicKeyInput
| crypto.VerifyJsonWebKeyInput;

constructor(key: crypto.KeyObject) {
constructor(
key: crypto.KeyLike | crypto.VerifyKeyObjectInput | crypto.VerifyPublicKeyInput | crypto.VerifyJsonWebKeyInput,
algorithm: string | null | undefined = null
) {
this.key = key;
this.algorithm = algorithm;
}

verify(message: Buffer, signature: Buffer) {
return crypto.verify(null, message, this.key, signature);
return crypto.verify(this.algorithm, message, this.key, signature);
}
}
14 changes: 10 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,18 @@ export const Aead = {

export const Asymmetric = {
create: {
signer: (key: crypto.KeyObject) => {
return createSigner(key);
signer: (
key: crypto.KeyLike | crypto.SignKeyObjectInput | crypto.SignPrivateKeyInput,
algorithm: string | null | undefined = null
) => {
return createSigner(key, algorithm);
},

verifier: (key: crypto.KeyObject) => {
return createVerifier(key);
verifier: (
key: crypto.KeyLike | crypto.VerifyKeyObjectInput | crypto.VerifyPublicKeyInput | crypto.VerifyJsonWebKeyInput,
algorithm: string | null | undefined = null
) => {
return createVerifier(key, algorithm);
},

privateCipher: (key: crypto.RsaPrivateKey | crypto.KeyLike) => {
Expand Down
182 changes: 182 additions & 0 deletions test/credentials/jwt.credential.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { Asymmetric, HASH, HMAC, Helper, Hmac } from '../../src/index';

describe('Jwt', () => {
test('HS256(empty secret)', () => {
const jsonHeader = {
alg: 'HS256',
typ: 'JWT',
};

const jsonPayload = {
sub: '1234567890',
name: 'John Doe',
iat: 1516239022,
};

const secret = '';

const testHeader = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';
const testPayload = 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ';
const testSignature = 'he0ErCNloe4J7Id0Ry2SEDg09lKkZkfsRiGsdX_vgEg';

const header = Buffer.from(JSON.stringify(jsonHeader)).toString('base64url');
const payload = Buffer.from(JSON.stringify(jsonPayload)).toString('base64url');

expect(header).toBe(testHeader);
expect(payload).toBe(testPayload);

const input = `${testHeader}.${testPayload}`;

const signature = Hmac.create(HMAC.SHA256, secret).update(input).base64url();
expect(signature).toBe(testSignature);
});

test('HS256(secret)', () => {
const jsonHeader = {
alg: 'HS256',
typ: 'JWT',
};

const jsonPayload = {
sub: '1234567890',
name: 'John Doe',
iat: 1516239022,
};

const secret = '8cb9f3eef4f72946f1fbd1c08a8bd5dfad0d38c187999175e9610f405e666203';

const testHeader = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';
const testPayload = 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ';
const testSignature = 'Fk-95l4iKZvhJBRnJ6pWnbU5YO3d7or0xvlZDgifLFo';

const header = Buffer.from(JSON.stringify(jsonHeader)).toString('base64url');
const payload = Buffer.from(JSON.stringify(jsonPayload)).toString('base64url');

expect(header).toBe(testHeader);
expect(payload).toBe(testPayload);

const input = `${testHeader}.${testPayload}`;

const signature = Hmac.create(HMAC.SHA256, secret).update(input).base64url();
expect(signature).toBe(testSignature);
});

test('ES256', () => {
const jsonHeader = {
alg: 'ES256',
typ: 'JWT',
};

const jsonPayload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: 1516239022,
};

const testHeader = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9';
const testPayload = 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0';
const testSignature = 'nRhr_v0pAjm2vYBqzP-pFdcAcASZf4KdYN0cgzinQlQQDSVSz0FB_JXOGwSR9lwzfX7pKQ0op2Yy1fFAjyqWcQ';

const header = Buffer.from(JSON.stringify(jsonHeader)).toString('base64url');
const payload = Buffer.from(JSON.stringify(jsonPayload)).toString('base64url');

expect(header).toBe(testHeader);
expect(payload).toBe(testPayload);

const privateKey = Helper.keypair.generate.privateKey({
key: {
kty: 'EC',
crv: 'P-256',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
d: 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
},
format: 'jwk',
});

const publicKey = Helper.keypair.generate.publicKey({
key: {
kty: 'EC',
crv: 'P-256',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
},
format: 'jwk',
});

const input = Buffer.from(`${header}.${payload}`, 'ascii');

const signer = Asymmetric.create.signer({ key: privateKey, dsaEncoding: 'ieee-p1363' });
const signature = signer.sign(input).toString('base64url');

const verifier = Asymmetric.create.verifier({ key: publicKey, dsaEncoding: 'ieee-p1363' });

expect(verifier.verify(input, Buffer.from(testSignature, 'base64url'))).toBe(true);
expect(verifier.verify(input, Buffer.from(signature, 'base64url'))).toBe(true);
});

test('ECDSA P-256 SHA-256(JWS - RFC 7515: A.3 Example JWS Using ECDSA P-256 SHA-256)', () => {
const jsonHeader = { alg: 'ES256' };
const textPayload = `{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}`;

const testProtectedHeader = 'eyJhbGciOiJFUzI1NiJ9';
const testPayload =
'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ';
const testSignature = 'DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q';

const protectedHeader = Buffer.from(JSON.stringify(jsonHeader)).toString('base64url');
const payload = Buffer.from(textPayload).toString('base64url');
const payloadBuffer = Buffer.from(`{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}`);

const testPayloadBuffer = Buffer.from([
123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48,
56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46,
99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125,
]);

expect(payloadBuffer).toStrictEqual(testPayloadBuffer);

expect(protectedHeader).toBe(testProtectedHeader);
expect(payload).toBe(testPayload);

const privateKey = Helper.keypair.generate.privateKey({
key: {
kty: 'EC',
crv: 'P-256',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
d: 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI',
},
format: 'jwk',
});

const publicKey = Helper.keypair.generate.publicKey({
key: {
kty: 'EC',
crv: 'P-256',
x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU',
y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0',
},
format: 'jwk',
});

const input = Buffer.from(`${protectedHeader}.${payload}`, 'ascii');

let signer = Asymmetric.create.signer({ key: privateKey, dsaEncoding: 'ieee-p1363' });
let signature = signer.sign(input).toString('base64url');

let verifier = Asymmetric.create.verifier({ key: publicKey, dsaEncoding: 'ieee-p1363' });

expect(verifier.verify(input, Buffer.from(testSignature, 'base64url'))).toBe(true);
expect(verifier.verify(input, Buffer.from(signature, 'base64url'))).toBe(true);

signer = Asymmetric.create.signer({ key: privateKey, dsaEncoding: 'ieee-p1363' }, HASH.SHA256);
signature = signer.sign(input).toString('base64url');

expect(verifier.verify(input, Buffer.from(signature, 'base64url'))).toBe(true);

verifier = Asymmetric.create.verifier({ key: publicKey, dsaEncoding: 'ieee-p1363' }, HASH.SHA256);
expect(verifier.verify(input, Buffer.from(signature, 'base64url'))).toBe(true);
});
});

0 comments on commit 421dee1

Please sign in to comment.