Store / access private data linked to specific user

The attacker would just have to know that a user was using a certain app, right?

2 Likes

Does it depend also on the fake app persuading the user to authorise it - and therefore also to run it?

This and the above points are going to be really important aren’t they! We need to put together a guide on recommended procedures and what they do /don’t guarantee.

3 Likes

Yes, just send me a PM.

This would indeed be interesting. If it’s not the case I’m really starting to think that we need something like a getUserEncKey method. Which brings me back to my previous question:

1 Like

As I understand, reading the MD RFC, the READ of and MD, even private, is not blocked so anybody can ask for this data if they know the direction and TypeTag. The protection for this data, if private, is the symmetric or asymmetric encryption. So I you share with somebody the relevant information (direction, TypeTag and decrypt Key) I see no impediment to read this data.

2 Likes

@Mindphreaker While digging around in core libraries I saw that ClientKeys struct has a symmetric encryption key. Will follow this path to see where a client’s symmetric encryption key is used. I wonder if this is a candidate, and if it would be sensible, for exposure in our API’s.
Check it out: https://github.com/maidsafe/safe_client_libs/blob/master/safe_core/src/client/account.rs#L133

3 Likes

I continued to experiment with your example from above @hunterlester and it seems, that I’m able to fetch the stored value but only until I reauthenticate the app (e.g. reload the tab and reauthenticate). After reauthentication I noticed two things:

  1. The generated nonce is different, however the returned mdHandle from newPrivate still seems to be valid because the item count matches. I don’t know if the nonce passed to newPrivate has an impact on de-/encryption.
  2. When calling encryptKey() I get a different return value compared to the time before reauthentication. This of course explains why I can’t fetch the stored value. As long as I don’t reload the page and don’t reauthenticate the encryptKey() returns the same value.

Can you confirm this or do you know why this is happening?

1 Like

I updated my code to use getAppPubEncKey instead of getAppPubSignKey.
This is working nicely.
I ran this code, then reloaded page and reauthenticated my application, retrieved the same private MD, and was able to successfully decrypt using the app’s public encryption key.

let appInfo = {
  id: 'net.maidsafe.api_playground.webclient.10',
  name: 'SAFE web API playground',
  vendor: 'MaidSafe Ltd.',
  scope: null
};

let encryptedEntries = {};

function decryptQueue(mdHandle, i) {
  let entries = Object.keys(encryptedEntries);

  if ( i === entries.length ) {
    console.log('Decrypted entry: ', encryptedEntries);
    return;
  }

  window.safeMutableData.decrypt(mdHandle, encryptedEntries[entries[i]].buf).then(decryptedValue => {
    encryptedEntries[entries[i]] = String.fromCharCode.apply(null, new Uint8Array(decryptedValue));
    decryptQueue(mdHandle, i + 1);
  })
}

window.safeApp.initialise(appInfo).then(appHandle => window.safeApp.authorise(appHandle, {
        _public: ['Read', 'Insert', 'Update', 'Delete'],
        _publicNames: ['Read', 'Insert', 'Update', 'Delete']
      }, { own_container: true })
      .then(authUri => window.safeApp.connectAuthorised(appHandle, authUri)))
      .then(appHandle => window.safeCrypto.sha3Hash(appHandle, 'explicitly_named_md')
        .then(hashedString => ({hashedString, appHandle})))
      .then(({hashedString, appHandle}) => window.safeCrypto.getAppPubEncKey(appHandle)
        .then(encKeyHandle => window.safeCryptoPubEncKey.getRaw(encKeyHandle).then(rawPubEncKey => ({rawPubEncKey, hashedString, appHandle}))))
      .then(({rawPubEncKey, hashedString, appHandle}) => window.safeCrypto.generateNonce(appHandle).then(nonce => ({rawPubEncKey, hashedString, appHandle, nonce})))
      .then(({rawPubEncKey, hashedString, appHandle, nonce}) => window.safeMutableData.newPrivate(appHandle, hashedString, 15001, rawPubEncKey.buffer, nonce.buffer)
        .then(mdHandle => window.safeMutableData.encryptKey(mdHandle, 'key1').then(encryptedKey => ({encryptedKey}))
          .then(({encryptedKey}) => window.safeMutableData.encryptValue(mdHandle, 'encrypted value').then(encryptedValue => ({encryptedKey, encryptedValue}))
            .then(({encryptedKey, encryptedValue}) => window.safeMutableData.quickSetup(mdHandle)
              .then(() => window.safeMutableData.newMutation(appHandle)
                .then(mutationHandle => window.safeMutableDataMutation.insert(mutationHandle, encryptedKey, encryptedValue)
                  .then(() => window.safeMutableData.applyEntriesMutation(mdHandle, mutationHandle).then(() => ({mdHandle, appHandle, hashedString})))
                )
              )
            )
          )
        )
      )
      .then(({mdHandle, appHandle, hashedString}) => window.safeMutableData.getEntries(mdHandle)
        .then(entriesHandle => window.safeMutableDataEntries.forEach(entriesHandle, (k, v) => {
        let key = String.fromCharCode.apply(null, k);
        let value = String.fromCharCode.apply(null, new Uint8Array(v.buf));

        console.log('Key: ', key,',', 'Value: ', value);
      }).then(() => ({appHandle, hashedString}))
    ))
    .then(({appHandle, hashedString}) => window.safeCrypto.getAppPubEncKey(appHandle)
      .then(encKeyHandle => window.safeCryptoPubEncKey.getRaw(encKeyHandle).then(rawPubEncKey => ({rawPubEncKey, hashedString, appHandle})))
      .then(({rawPubEncKey, hashedString, appHandle}) => window.safeCrypto.generateNonce(appHandle).then(nonce => ({rawPubEncKey, hashedString, appHandle, nonce})))
      .then(({rawPubEncKey, hashedString, appHandle, nonce}) => window.safeMutableData.newPrivate(appHandle, hashedString, 15001, rawPubEncKey.buffer, nonce.buffer))
      .then(mdHandle => window.safeMutableData.getEntries(mdHandle)
        .then(entriesHandle => window.safeMutableDataEntries.forEach(entriesHandle, (k, v) => {
          Object.defineProperty(encryptedEntries, k, {value: v, writable: true, enumerable: true, configurable: true});
        })).then(() => {
          console.log(mdHandle);
          decryptQueue(mdHandle,0);
        })
      )
    )

Nonce should always be unique for security purposes. I’m just learning about how this works. Thanks to @dirvine for passing along this link for education: https://www.lvh.io/posts/nonce-misuse-resistance-101.html

The critical security property of a nonce is that it’s never repeated under the same key. You can remember this by the mnemonic that a nonce is a “number used once”. If you were to repeat the nonce, the keystream would also repeat. That means that an attacker can take the two ciphertexts and XOR them to compute the XOR of the plaintexts.

5 Likes

Doesn’t SAFE use EdDSA, which uses a deterministic nonce?

Also,

  • Can someone explain why window.safeMutableData.newRandomPrivate does not have the same cryptographic parameters as window.safeMutableData.newPrivate? Or put differently: what cryptographic secret encryption key and nonce does newRandomPrivate use so I can pass them to newPrivate another time?
    I expected the difference between those functions just to be about the address being picked or not.

  • What exactly does ‘private access’ mean in the context of newRandomPrivate/newPrivate? I’m not able to find a description of this sort of terminology in the RFCs.

2 Likes

In the example getAppPubEncKey are used in symmetric encryption so, for security reasons, is better never reuse nonces. The EdDSA are for asymmetric encryption.

1 Like

The determinism of Nonce doesn’t matter - they should not be repeated for a key. If you use something deterministic then you don’t need to store it along with cipher text. If random then of you would store it along with cipher text. The nonce is stored plain-texted - so when you fetch data it will likely be combination of plain-texted Nonce and cipher text; then extract the Nonce and use it with the sec-sym-key to decrypt ciphertext.
We use this for our Nonce currently.

For MData: Private means the contents are encrypted, public means otherwise.
MDataInfo is a ptr to MData - containing sufficient info to fetch and read/update MData. It has XorName, type-tag, and enc-keys. _random() function is to be used if you want everything random (you need to provide the type-tag though).
The other function _new() is used if you want to provide your stuff. E.g. if you are designing an app where XorName is deterministic depending on user Input etc. If you want deterministic XorName but random keys, there are crypto functions in safe-app rust which you can use to obtain random keys and nonces and supply that along with your chosen XorName. It’s just a matter of mix and match.

6 Likes

I agree with that, but if a nonce is not provided to fn enc_entry_key then a random one is generated which is not stored:

https://github.com/maidsafe/safe_client_libs/blob/a490682b3cc4e0230dc79006a38bdc74c23670bf/safe_core/src/client/mdata_info.rs#L257

So, I don’t understand how the data can be decrypted in this case.

The answer is explained in one of the posts that you ‘liked’ :stuck_out_tongue: - the 2nd point here

So entry-key encryption uses deterministic but unique nonce for all entries as opposed to entry-value which uses completely random nonce (and hence needs to be stored in the network along with the cipher text). With this clever trick we can get the great benefit of indexing. So if you know the MDataInfo (which you would need to know anyway to address a MData) and if you know your entry-key (which is why MData was invented in the first place - it can be used as a key-value store) - then all you need to do is create the deterministic nonce locally (using sha3(MDataInfo::nonce + entry-key)), use that along with your secret key (MDataInfo::key) to encrypt the entry-key get the cipher-text=Vec and ask for entry-value for that entry-key.

If entry-key nonce was also absolutely random like entry-value’s nonce, then it would have to be stored along with cipher-text’ed entry-key in the network and would be impossible to index it as key-value. You would then need to fetch all entry-key,value pairs and decode every entry-key until you get the expected one and only then see the value.

6 Likes

My concern was only about an implementation detail of the function I highlighted: the third parameter is an optional nonce. If none is provided by the caller then a random one is generated locally and is not stored globally and is not returned to the caller. So I thought it was lost.

My error was that I didn’t pay attention that the nonce was serialized together with the ciphered text. Everything is ok, sorry for the disturbance.

3 Likes

Ah nw - probably worth bringing to notice is this PR, still in review. That removes the ‘optionality’ for Nonce if the Sec-Key is provided. It was optional for flexibility if in some case a user wanted an MDataInfo created with Secret-Key but no Nonce even for entry-keys (so that random one was used which would lead to inability to index) - but so far there has been no such use case and it’s just bloating logic with permutation. So we just decided to remove that (it was an option inside an option as you can see) and add it back only when required - the changes are internal and does not affect the API though.

4 Likes

This topic was automatically closed after 60 days. New replies are no longer allowed.