diff --git a/src/value-objects/crypto/PrivateKey.ts b/src/value-objects/crypto/PrivateKey.ts index f993cb1..ef78566 100644 --- a/src/value-objects/crypto/PrivateKey.ts +++ b/src/value-objects/crypto/PrivateKey.ts @@ -3,6 +3,7 @@ import { Buffer } from 'buffer'; import { InvalidFormatError } from '../../errors/InvalidFormatError'; import { InvalidLengthError } from '../../errors/InvalidLengthError'; import { assert } from '../../patterns'; +import { Media } from '../media'; import { NullObject } from '../NullObject'; import { StringValueObject } from '../StringValueObject'; import { CryptoAdapter } from './CryptoAdapter'; @@ -48,7 +49,10 @@ export class PrivateKey extends Key { } public sign(payload: CryptoPayload): Signature { - const messageBuffer = Buffer.from(payload.valueOf()); + const messageBuffer = + payload instanceof Media + ? payload.getBuffer() + : Buffer.from(payload.valueOf()); const signatureBuffer = CryptoAdapter.sign(messageBuffer, this.valueOf()); return Signature.fromBuffer(signatureBuffer); diff --git a/src/value-objects/crypto/PublicKey.ts b/src/value-objects/crypto/PublicKey.ts index fadd35a..45ebca1 100644 --- a/src/value-objects/crypto/PublicKey.ts +++ b/src/value-objects/crypto/PublicKey.ts @@ -3,6 +3,7 @@ import { Buffer } from 'buffer'; import { InvalidFormatError } from '../../errors/InvalidFormatError'; import { InvalidLengthError } from '../../errors/InvalidLengthError'; import { assert } from '../../patterns'; +import { Media } from '../media'; import { NullObject } from '../NullObject'; import { StringValueObject } from '../StringValueObject'; import { CryptoAdapter } from './CryptoAdapter'; @@ -42,7 +43,10 @@ export class PublicKey extends Key { payload: CryptoPayload, signature: Signature, ): boolean { - const messageBuffer = Buffer.from(payload.valueOf()); + const messageBuffer = + payload instanceof Media + ? payload.getBuffer() + : Buffer.from(payload.valueOf()); const signatureBuffer = Buffer.from(signature.valueOf(), 'base64'); const valid = CryptoAdapter.verify( signatureBuffer, @@ -54,7 +58,10 @@ export class PublicKey extends Key { } public encrypt(payload: CryptoPayload): EncryptedPayload { - const messageBuffer = Buffer.from(payload.valueOf()); + const messageBuffer = + payload instanceof Media + ? payload.getBuffer() + : Buffer.from(payload.valueOf()); const x25519Pub = CryptoAdapter.publicKeyToX25519(this.valueOf()); diff --git a/src/value-objects/hashes/MD5Hash.ts b/src/value-objects/hashes/MD5Hash.ts index 703e78f..405b360 100644 --- a/src/value-objects/hashes/MD5Hash.ts +++ b/src/value-objects/hashes/MD5Hash.ts @@ -16,7 +16,12 @@ export class MD5Hash extends Hash { public static from( buffer: string | StringValueObject | Media | Buffer, ): MD5Hash { - return new MD5Hash(CryptoAdapter.hash('md5', buffer.valueOf())); + return new MD5Hash( + CryptoAdapter.hash( + 'md5', + buffer instanceof Media ? buffer.getBuffer() : buffer.valueOf(), + ), + ); } constructor(source: string | StringValueObject) { diff --git a/src/value-objects/hashes/SHA256Hash.ts b/src/value-objects/hashes/SHA256Hash.ts index 9fc206d..a26a625 100644 --- a/src/value-objects/hashes/SHA256Hash.ts +++ b/src/value-objects/hashes/SHA256Hash.ts @@ -16,7 +16,12 @@ export class SHA256Hash extends ValueObject { public static from( buffer: string | StringValueObject | Media | Buffer, ): SHA256Hash { - return new SHA256Hash(CryptoAdapter.hash('sha256', buffer.valueOf())); + return new SHA256Hash( + CryptoAdapter.hash( + 'sha256', + buffer instanceof Media ? buffer.getBuffer() : buffer.valueOf(), + ), + ); } constructor(source: string | StringValueObject) { diff --git a/src/value-objects/hashes/SHA512Hash.ts b/src/value-objects/hashes/SHA512Hash.ts index 8b04f8e..3069a78 100644 --- a/src/value-objects/hashes/SHA512Hash.ts +++ b/src/value-objects/hashes/SHA512Hash.ts @@ -16,7 +16,12 @@ export class SHA512Hash extends ValueObject { public static from( buffer: string | StringValueObject | Media | Buffer, ): SHA512Hash { - return new SHA512Hash(CryptoAdapter.hash('sha512', buffer.valueOf())); + return new SHA512Hash( + CryptoAdapter.hash( + 'sha512', + buffer instanceof Media ? buffer.getBuffer() : buffer.valueOf(), + ), + ); } constructor(source: string | StringValueObject) { diff --git a/src/value-objects/media/Media.ts b/src/value-objects/media/Media.ts index 4dff749..e3d65de 100644 --- a/src/value-objects/media/Media.ts +++ b/src/value-objects/media/Media.ts @@ -4,16 +4,22 @@ import { NullObject } from '../NullObject'; import { ValueObject } from '../ValueObject'; export class Media extends ValueObject { + private readonly buffer?: Buffer; + constructor(value: string | Buffer) { super(value?.toString()); if (NullObject.isNullObject(this)) { return this; } + + this.buffer = Buffer.isBuffer(value) ? Buffer.from(value) : undefined; } public getBuffer(): Buffer { - return Buffer.from(this.value); + return this.buffer === undefined + ? Buffer.from(this.value) + : Buffer.from(this.buffer); } public getSize(): number { diff --git a/tests/value-objects/crypto/PrivateKey.spec.ts b/tests/value-objects/crypto/PrivateKey.spec.ts index 5d04e49..9e0ad24 100644 --- a/tests/value-objects/crypto/PrivateKey.spec.ts +++ b/tests/value-objects/crypto/PrivateKey.spec.ts @@ -5,6 +5,7 @@ import { InvalidFormatError, InvalidLengthError, Key, + Media, PrivateKey, PublicKey, Signature, @@ -142,6 +143,20 @@ describe('PrivateKey', () => { ); expect(valid).toBeTrue(); }); + + it('should sign raw Media bytes', () => { + const key = new PrivateKey(privatePem); + const payload = Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0x80]); + const sig = key.sign(new Media(payload)); + + const valid = crypto.verify( + null, + payload, + publicPem, + Buffer.from(sig.valueOf(), 'base64'), + ); + expect(valid).toBeTrue(); + }); }); describe('decrypt', () => { diff --git a/tests/value-objects/crypto/PublicKey.spec.ts b/tests/value-objects/crypto/PublicKey.spec.ts index c8ff12f..32727f1 100644 --- a/tests/value-objects/crypto/PublicKey.spec.ts +++ b/tests/value-objects/crypto/PublicKey.spec.ts @@ -5,6 +5,7 @@ import { InvalidFormatError, InvalidLengthError, Key, + Media, PrivateKey, PublicKey, Signature, @@ -112,6 +113,15 @@ describe('PublicKey', () => { expect(key.isValidSignature(payloadVO, signature)).toBeTrue(); }); + + it('should verify signatures against raw Media bytes', () => { + const key = new PublicKey(publicPem); + const payload = Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0x80]); + const sigBuf = crypto.sign(null, payload, privatePem); + const signature = Signature.fromBuffer(sigBuf); + + expect(key.isValidSignature(new Media(payload), signature)).toBeTrue(); + }); }); describe('encrypt', () => { @@ -146,6 +156,16 @@ describe('PublicKey', () => { expect(encrypted).toBeInstanceOf(EncryptedPayload); }); + it('should encrypt raw Media bytes', () => { + const pubKey = new PublicKey(publicPem); + const privKey = new PrivateKey(privatePem); + const payload = Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0x80]); + const encrypted = pubKey.encrypt(new Media(payload)); + const decrypted = privKey.decrypt(encrypted); + + expect(decrypted).toEqual(payload); + }); + it('should produce output that can be decrypted by the corresponding PrivateKey', () => { const pubKey = new PublicKey(publicPem); const privKey = new PrivateKey(privatePem); diff --git a/tests/value-objects/hashes/MD5Hash.spec.ts b/tests/value-objects/hashes/MD5Hash.spec.ts index ed46088..01e5af2 100644 --- a/tests/value-objects/hashes/MD5Hash.spec.ts +++ b/tests/value-objects/hashes/MD5Hash.spec.ts @@ -2,6 +2,7 @@ import { createHash } from 'crypto'; import { InvalidHashError, + Media, MD5Hash, NullObject, StringValueObject, @@ -98,6 +99,13 @@ describe('MD5Hash', () => { expect(hash.valueOf()).toBe(expected); }); + it('should compute MD5 hash from raw Media bytes', () => { + const input = Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0x80]); + const expected = computeMD5(input); + const hash = MD5Hash.from(new Media(input)); + expect(hash.valueOf()).toBe(expected); + }); + it('should compute MD5 hash from StringValueObject', () => { const input = new StringValueObject('hello'); const expected = computeMD5(input.valueOf()); diff --git a/tests/value-objects/hashes/SHA256Hash.spec.ts b/tests/value-objects/hashes/SHA256Hash.spec.ts index c33b3c9..054a2b1 100644 --- a/tests/value-objects/hashes/SHA256Hash.spec.ts +++ b/tests/value-objects/hashes/SHA256Hash.spec.ts @@ -3,6 +3,7 @@ import { SHA256Hash, NullObject, InvalidHashError, + Media, StringValueObject, } from '../../../src'; @@ -102,6 +103,13 @@ describe('SHA256Hash', () => { expect(hash.valueOf()).toBe(expected); }); + it('should compute SHA256 hash from raw Media bytes', () => { + const input = Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0x80]); + const expected = computeSHA256(input); + const hash = SHA256Hash.from(new Media(input)); + expect(hash.valueOf()).toBe(expected); + }); + it('should compute SHA256 hash from StringValueObject', () => { const input = new StringValueObject('hello'); const expected = computeSHA256(input.valueOf()); diff --git a/tests/value-objects/hashes/SHA512Hash.spec.ts b/tests/value-objects/hashes/SHA512Hash.spec.ts index 458f00f..2139623 100644 --- a/tests/value-objects/hashes/SHA512Hash.spec.ts +++ b/tests/value-objects/hashes/SHA512Hash.spec.ts @@ -3,6 +3,7 @@ import { SHA512Hash, NullObject, InvalidHashError, + Media, StringValueObject, } from '../../../src'; @@ -102,6 +103,13 @@ describe('SHA512Hash', () => { expect(hash.valueOf()).toBe(expected); }); + it('should compute SHA512 hash from raw Media bytes', () => { + const input = Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0x80]); + const expected = computeSHA512(input); + const hash = SHA512Hash.from(new Media(input)); + expect(hash.valueOf()).toBe(expected); + }); + it('should compute SHA512 hash from StringValueObject', () => { const input = new StringValueObject('hello'); const expected = computeSHA512(input.valueOf()); diff --git a/tests/value-objects/media/Media.spec.ts b/tests/value-objects/media/Media.spec.ts index 9338fc3..be518a8 100644 --- a/tests/value-objects/media/Media.spec.ts +++ b/tests/value-objects/media/Media.spec.ts @@ -23,6 +23,17 @@ describe('Media', () => { expect(media).toBeInstanceOf(Media); expect(media.valueOf()).toBe(testContent); }); + + it('should preserve non-text bytes from a Buffer', () => { + const source = Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0x80]); + const media = new Media(source); + + source[0] = 0x00; + + expect(media.getBuffer()).toEqual( + Buffer.from([0xff, 0xfe, 0xfd, 0x00, 0x80]), + ); + }); }); describe('getBuffer', () => {