Skip to content
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
6 changes: 5 additions & 1 deletion src/value-objects/crypto/PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -96,7 +97,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);
Expand Down
12 changes: 10 additions & 2 deletions src/value-objects/crypto/PublicKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -43,7 +44,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,
Expand All @@ -55,7 +59,11 @@ 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());

assert(
messageBuffer.length <= PublicKey.MAX_PAYLOAD_LENGTH,
new InvalidLengthError(
Expand Down
7 changes: 6 additions & 1 deletion src/value-objects/hashes/MD5Hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 6 additions & 1 deletion src/value-objects/hashes/SHA256Hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export class SHA256Hash extends ValueObject<string> {
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) {
Expand Down
7 changes: 6 additions & 1 deletion src/value-objects/hashes/SHA512Hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export class SHA512Hash extends ValueObject<string> {
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) {
Expand Down
20 changes: 19 additions & 1 deletion src/value-objects/media/Media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ import { NullObject } from '../NullObject';
import { ValueObject } from '../ValueObject';

export class Media extends ValueObject<string> {
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;
Comment thread
haskou marked this conversation as resolved.
Resolved
}

public getBuffer(): Buffer {
return Buffer.from(this.value);
return this.buffer === undefined
? Buffer.from(this.value)
: Buffer.from(this.buffer);
}

public getSize(): number {
Expand All @@ -23,4 +29,16 @@ export class Media extends ValueObject<string> {
public getBase64(): string {
return this.getBuffer().toString('base64');
}

public isEqual(other: unknown): boolean {
if (other instanceof Media) {
return this.getBuffer().equals(other.getBuffer());
}

if (Buffer.isBuffer(other)) {
return this.getBuffer().equals(other);
}

return super.isEqual(other);
}
}
15 changes: 15 additions & 0 deletions tests/value-objects/crypto/PrivateKey.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
InvalidFormatError,
InvalidLengthError,
Key,
Media,
PrivateKey,
PublicKey,
Signature,
Expand Down Expand Up @@ -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', () => {
Expand Down
20 changes: 20 additions & 0 deletions tests/value-objects/crypto/PublicKey.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
InvalidFormatError,
InvalidLengthError,
Key,
Media,
PrivateKey,
PublicKey,
Signature,
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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 throw InvalidLengthError for oversized payloads', () => {
const key = new PublicKey(publicPem);
const payload = Buffer.alloc(1024 * 1024 + 1);
Expand Down
8 changes: 8 additions & 0 deletions tests/value-objects/hashes/MD5Hash.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createHash } from 'crypto';

import {
InvalidHashError,
Media,
MD5Hash,
NullObject,
StringValueObject,
Expand Down Expand Up @@ -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());
Expand Down
8 changes: 8 additions & 0 deletions tests/value-objects/hashes/SHA256Hash.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
SHA256Hash,
NullObject,
InvalidHashError,
Media,
StringValueObject,
} from '../../../src';

Expand Down Expand Up @@ -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());
Expand Down
8 changes: 8 additions & 0 deletions tests/value-objects/hashes/SHA512Hash.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
SHA512Hash,
NullObject,
InvalidHashError,
Media,
StringValueObject,
} from '../../../src';

Expand Down Expand Up @@ -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());
Expand Down
46 changes: 46 additions & 0 deletions tests/value-objects/media/Media.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NullObjectError } from '../../../src/errors/NullObjectError';
import { Media } from '../../../src/value-objects/media/Media';
import { NullObject } from '../../../src/value-objects/NullObject';
import { UniqueObjectArray } from '../../../src/value-objects/UniqueObjectArray';

describe('Media', () => {
const testContent = 'hello world';
Expand All @@ -23,6 +24,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', () => {
Expand Down Expand Up @@ -51,6 +63,40 @@ describe('Media', () => {
});
});

describe('isEqual', () => {
it('should compare Media instances using preserved bytes', () => {
const media = new Media(Buffer.from([0xff]));
const other = new Media(Buffer.from([0xfe]));

expect(media.toString()).toBe(other.toString());
expect(media.getBuffer()).not.toEqual(other.getBuffer());
expect(media.isEqual(other)).toBeFalse();
});

it('should compare Media instances with the same bytes as equal', () => {
const media = new Media(Buffer.from([0xff]));
const other = new Media(Buffer.from([0xff]));

expect(media.isEqual(other)).toBeTrue();
});

it('should compare Media instances with buffers using preserved bytes', () => {
const media = new Media(Buffer.from([0xff]));

expect(media.isEqual(Buffer.from([0xff]))).toBeTrue();
expect(media.isEqual(Buffer.from([0xfe]))).toBeFalse();
});

it('should keep distinct binary payloads in unique collections', () => {
const medias = UniqueObjectArray.fromArray([
new Media(Buffer.from([0xff])),
new Media(Buffer.from([0xfe])),
]);

expect(medias.length()).toBe(2);
});
});

describe('NullObject behavior', () => {
it('should throw NullObjectError when calling methods on a null media', () => {
const nullMedia = new Media(undefined as unknown as string);
Expand Down
Loading