-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Node): Implement unwrapping of message payload (#201)
Needed for relaycorp/awala-gateway-internet#328
- Loading branch information
Showing
5 changed files
with
160 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { arrayBufferFrom, expectBuffersToEqual } from '../_test_utils'; | ||
import { | ||
SessionEnvelopedData, | ||
SessionlessEnvelopedData, | ||
} from '../crypto_wrappers/cms/envelopedData'; | ||
import { | ||
derSerializePublicKey, | ||
generateECDHKeyPair, | ||
generateRSAKeyPair, | ||
} from '../crypto_wrappers/keys'; | ||
import Certificate from '../crypto_wrappers/x509/Certificate'; | ||
import { MockPrivateKeyStore, MockPublicKeyStore } from '../keyStores/testMocks'; | ||
import { issueGatewayCertificate, issueInitialDHKeyCertificate } from '../pki'; | ||
import { StubMessage, StubPayload } from '../ramf/_test_utils'; | ||
import { BaseNode } from './BaseNode'; | ||
|
||
const TOMORROW = new Date(); | ||
TOMORROW.setDate(TOMORROW.getDate() + 1); | ||
TOMORROW.setMilliseconds(0); | ||
|
||
let senderCertificate: Certificate; | ||
beforeAll(async () => { | ||
const senderKeyPair = await generateRSAKeyPair(); | ||
senderCertificate = await issueGatewayCertificate({ | ||
issuerPrivateKey: senderKeyPair.privateKey, | ||
subjectPublicKey: senderKeyPair.publicKey, | ||
validityEndDate: TOMORROW, | ||
}); | ||
}); | ||
|
||
let recipientCertificate: Certificate; | ||
let recipientPrivateKey: CryptoKey; | ||
let privateKeyStore: MockPrivateKeyStore; | ||
beforeAll(async () => { | ||
const recipientKeyPair = await generateRSAKeyPair(); | ||
recipientPrivateKey = recipientKeyPair.privateKey; | ||
|
||
recipientCertificate = await issueGatewayCertificate({ | ||
issuerPrivateKey: recipientKeyPair.privateKey, | ||
subjectPublicKey: recipientKeyPair.publicKey, | ||
validityEndDate: TOMORROW, | ||
}); | ||
|
||
privateKeyStore = new MockPrivateKeyStore(); | ||
await privateKeyStore.registerNodeKey(recipientKeyPair.privateKey, recipientCertificate); | ||
}); | ||
|
||
describe('unwrapMessagePayload', () => { | ||
const payloadPlaintextContent = arrayBufferFrom('content'); | ||
|
||
let publicKeyStore: MockPublicKeyStore; | ||
beforeEach(() => { | ||
publicKeyStore = new MockPublicKeyStore(); | ||
}); | ||
|
||
const RECIPIENT_ADDRESS = 'https://example.com'; | ||
|
||
test('Payload plaintext should be returned when using sessionless encryption', async () => { | ||
const payload = await SessionlessEnvelopedData.encrypt( | ||
payloadPlaintextContent, | ||
recipientCertificate, | ||
); | ||
const message = new StubMessage( | ||
RECIPIENT_ADDRESS, | ||
senderCertificate, | ||
Buffer.from(payload.serialize()), | ||
); | ||
const node = new StubNode(privateKeyStore, publicKeyStore); | ||
|
||
const payloadPlaintext = await node.unwrapMessagePayload(message); | ||
|
||
expectBuffersToEqual(payloadPlaintext.content, payloadPlaintextContent); | ||
expect(publicKeyStore.keys).toBeEmpty(); | ||
}); | ||
|
||
test('Payload plaintext should be returned and session key stored when using a session', async () => { | ||
const sessionKeyPair = await generateECDHKeyPair(); | ||
const initialSessionCertificate = await issueInitialDHKeyCertificate({ | ||
issuerCertificate: recipientCertificate, | ||
issuerPrivateKey: recipientPrivateKey, | ||
subjectPublicKey: sessionKeyPair.publicKey, | ||
validityEndDate: TOMORROW, | ||
}); | ||
await privateKeyStore.registerInitialSessionKey( | ||
sessionKeyPair.privateKey, | ||
initialSessionCertificate, | ||
); | ||
const encryptionResult = await SessionEnvelopedData.encrypt( | ||
payloadPlaintextContent, | ||
initialSessionCertificate, | ||
); | ||
const message = new StubMessage( | ||
RECIPIENT_ADDRESS, | ||
senderCertificate, | ||
Buffer.from(encryptionResult.envelopedData.serialize()), | ||
); | ||
const node = new StubNode(privateKeyStore, publicKeyStore); | ||
|
||
const payloadPlaintext = await node.unwrapMessagePayload(message); | ||
|
||
expectBuffersToEqual(payloadPlaintext.content, payloadPlaintextContent); | ||
|
||
const storedKey = publicKeyStore.keys[await senderCertificate.calculateSubjectPrivateAddress()]; | ||
expect(storedKey.publicKeyCreationTime).toEqual(message.creationDate); | ||
expectBuffersToEqual(Buffer.from(encryptionResult.dhKeyId), storedKey.publicKeyId); | ||
expectBuffersToEqual( | ||
await derSerializePublicKey( | ||
(await encryptionResult.envelopedData.getOriginatorKey()).publicKey, | ||
), | ||
storedKey.publicKeyDer, | ||
); | ||
}); | ||
}); | ||
|
||
class StubNode extends BaseNode<StubPayload> {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { PrivateKeyStore } from '../keyStores/privateKeyStore'; | ||
import { PublicKeyStore } from '../keyStores/publicKeyStore'; | ||
import PayloadPlaintext from '../messages/payloads/PayloadPlaintext'; | ||
import RAMFMessage from '../messages/RAMFMessage'; | ||
import { NodeOptions } from './NodeOptions'; | ||
|
||
export abstract class BaseNode<Payload extends PayloadPlaintext> { | ||
constructor( | ||
protected privateKeyStore: PrivateKeyStore, | ||
protected publicKeyStore: PublicKeyStore, | ||
protected cryptoOptions: Partial<NodeOptions> = {}, | ||
) {} | ||
|
||
/** | ||
* Decrypt and return the payload in the `message`. | ||
* | ||
* Also store the session key, if using the channel session protocol. | ||
* | ||
* @param message | ||
*/ | ||
public async unwrapMessagePayload<P extends Payload>(message: RAMFMessage<P>): Promise<P> { | ||
const unwrapResult = await message.unwrapPayload(this.privateKeyStore); | ||
|
||
// If the sender uses channel session, store its public key for later use. | ||
if (unwrapResult.senderSessionKey) { | ||
await this.publicKeyStore.saveSessionKey( | ||
unwrapResult.senderSessionKey, | ||
message.senderCertificate, | ||
message.creationDate, | ||
); | ||
} | ||
|
||
return unwrapResult.payload; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { EncryptionOptions } from '../crypto_wrappers/cms/envelopedData'; | ||
import { SignatureOptions } from '../crypto_wrappers/cms/SignatureOptions'; | ||
|
||
export interface NodeOptions { | ||
readonly encryption: Partial<EncryptionOptions>; | ||
readonly signature: Partial<SignatureOptions>; | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters