
Research
/Security News
Toptal’s GitHub Organization Hijacked: 10 Malicious Packages Published
Threat actors hijacked Toptal’s GitHub org, publishing npm packages with malicious payloads that steal tokens and attempt to wipe victim systems.
@bicycle-codes/envelope
Advanced tools
Envelopes that have been authorized by the recipient. This hides the sender's identity, while the recipient is still visible. This way we hide the metadata of who is talking to whom via private message. But, because the recipient is legible, we can still index messages by recipient.
This supports multiple devices by default because we are using the Identity module.
Each envelope includes a signature. We want to give out our signed envelopes privately, without revealing them publicly.
If we assume we are doing this with internet infrastructure (ie, a server), then in the initial meeting the server would be able to see who we are giving out certificates to because the recipient must be visible. But in subsequent communication, the server would not know who we are talking to, they would just know that we are communicating with someone that we have given a certificate to.
You could also give out the certificates via some other means, like on your website, or via signal message, in which case the server would not know who it is from. Meaning, the server cannot even assume that a message is from your 'friend circle' in the application. It can only see that you got a message at a particular time; we can't infer anything about who it is from.
This is assuming that all the users of the app are well behaved, and not giving out envelopes willy nilly. :thinking:
If we stick with comparisons to common physical activities, this is very similar to the postal service. The envelope shows the recipient, and it needs a stamp (the signature here), but it hides the sender's ID.
The envelopes and encrypted messages pair with an identity instance instance on your device.
We create a symmetric key and encrypt it to various "exchange" keys. The exchange keys are non-extractable key pairs that can only be used on the device where they were created.
That way the documents created by this library can be freely distributed without leaking any keys.
Just a document signed by the recipient, like this:
// envelope
{
seq: 0,
expiration: 456,
recipient: 'my-username',
signature: '123abc',
author: 'did:key:abc'
}
// the message
{ envelope, content: 'encrypted text' }
// sender ID is in the content, so it is only readable by
// the recipient
import type { SignedMessage } from '@bicycle-codes/message'
type Envelope = SignedMessage<{
seq:number,
expiration?:number, // default to 0, which means no expiration
recipient:string, // the recipient's username
}>
When you encrypt a string, we create a record of keys. The key
object is a map from device name to a symmetric key that has been encrypted to the device. We do it this way because each device has its own keypair. We use the symmetric key to encrypt the content.
interface EncryptedContent {
key:Record<string, string>, // { deviceName: 'encrypted-key' }
content:string // encrypted text
}
Create an envelope.
async function create (
// crypto:Implementation,
signingKeypair:CryptoKeyPair,
{
username,
seq,
expiration = 0 // no expiration by default
}:{ username:string, seq:number, expiration?:number }
):Promise<Envelope>
Create a new AES key, take an envelope and some content. Encrypt the content, then put the content in the envelope.
This will encrypt the AES key to every device in the recipient identity, as well as your own identity.
import { Identity } from '@bicycle-codes/identity'
async function wrapMessage (
me:Identity,
recipient:Identity, // because we need to encrypt the message to the recipient
envelope:Envelope,
content:Content
):Promise<[{
envelope:Envelope,
message:EncryptedContent
}, Keys]>
This returns an array of
[{ envelope, message: encryptedMessage }, { ...senderKeys }]
[!NOTE] We return the sender keys as a seperate object because we do not want the sender's device names to be in the message that gets sent, because that would leak information about who the sender is.
The sender could save a map of the message's hash to the returned key object. That way they can save the map to some storage, and then look up the key by the hash of the message object.
Decrypt a given message. Depends on having the right crypto
object. Return a Content
object:
type Content = SignedRequest<{
from:{ username:string },
text:string,
mentions?:string[],
}>
export async function decryptMessage (
crypto:Crypto.Implementation,
msg:EncryptedContent
):Promise<Content>
import { decryptMessage } from '@bicycle-codes/envelope'
const decrypted = await decryptMessage(alicesCrypto, msgContent)
console.log(decrypted.from.username)
// => bob
console.log(decrypted.text)
// => hello
Pass in the keys as a separate argument if you are the message author. The sender's keys are not in the message evnelope, because we need to keep your device names out of the unencrypted envelope.
import { decryptMessage } from '@bicycle-codes/envelope'
// bobs keys were not in the envelope, because doing so would
// reveal information about the message author, Bob.
const decrypted = await decryptMessage(bob, msgContent, bobsMsgKeys)
console.log(decrypted.from.username)
// => bob
console.log(decrypted.text)
// => hello
Check if a given envelope is valid. currentSeq
is an optional sequence number to use when checking the validity. If currentSeq
is less than or equal to seq
in the envelope
, then this will return false
.
function verify (envelope:Envelope, currentSeq?:number):Promise<boolean>
test('check that the envelope is valid', async t => {
const isValid = await verify(alicesEnvelope)
t.equal(isValid, true, 'should validate a valid envelope')
t.equal(await verify(alicesEnvelope, 0), true,
'should take a sequence number')
t.equal(await verify(alicesEnvelope, 1), false,
'should say a message is invalid if the sequence number is equal')
try {
t.equal(await verify('baloney'))
} catch (err) {
t.ok(err, 'should throw given a malformed message')
}
})
Thanks to @Dominic for sketching this idea originally.
FAQs
Envelopes that have been authorized by the recipient
The npm package @bicycle-codes/envelope receives a total of 19 weekly downloads. As such, @bicycle-codes/envelope popularity was classified as not popular.
We found that @bicycle-codes/envelope demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
/Security News
Threat actors hijacked Toptal’s GitHub org, publishing npm packages with malicious payloads that steal tokens and attempt to wipe victim systems.
Research
/Security News
Socket researchers investigate 4 malicious npm and PyPI packages with 56,000+ downloads that install surveillance malware.
Security News
The ongoing npm phishing campaign escalates as attackers hijack the popular 'is' package, embedding malware in multiple versions.