Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Required JSON field name was missing at depth 0 on encryptJSON() in strict mode #87

Open
rabol opened this issue May 3, 2023 · 5 comments

Comments

@rabol
Copy link

rabol commented May 3, 2023

Hi

in a Laravel project, I'm trying to store an array of data.
here is the data

$data = [
        [
            'name' => 'test',
            'prompt' => 'Ask for name',
            'who' => 'sender',
            'type' => 'text',
        ],
        [
            'name' => 'test',
            'prompt' => 'Ask for name',
            'who' => 'sender',
            'type' => 'text',
        ],
        [
            'name' => 'test',
            'prompt' => 'Ask for name',
            'who' => 'sender',
            'type' => 'text',
        ],
    ];

the table has 2 columns, name and data

code

        $map = (new JsonFieldMap())
            ->addTextField('name')
            ->addTextField('prompt')
            ->addTextField('who')
            ->addTextField('type');

        $encryptedRow
            ->addField('name')
            ->addBlindIndex('name', new BlindIndex('name_index'))
            ->addJsonField('data', $map);
@paragonie-security
Copy link
Contributor

paragonie-security commented May 4, 2023

There are a couple of limitations with the current JSON field design:

  1. The ability to add blind indexes on arbitrary JSON fields (or even compound indexes on the same data) is absent.
  2. Lists are not currently supported, only object hashes.
  3. Arbitrary-depth is not supported; only a flat key-value structure.

This works great if you're storing, e.g. Stripe events as JSONB in a PostgreSQL database but want to encrypt them first. It doesn't work for your use case.

You could iterate over the list with an instance of EncryptedRow and encrypt each blob individually. For example:

        $encryptedRow
            ->addTextField('name')
            ->addTextField('prompt')
            ->addTextField('who')
            ->addTextField('type')
            ->addBlindIndex('name', new BlindIndex('name_index'));

// Encrypt path:
$encryptedData = [];
foreach ($data as $index => $row) {
    $encryptedData[$index] = $encryptedRow->prepareRowForStorage($row);
}
$storeThis = json_encode($encryptedData);

// Decrypt path:
$fromDatabase = json_decode($_whatever_, true);
$data = [];
foreach ($fromDatabase as $index => $row) {
    $data[$index] = $encryptedRow->decryptRow($row[1]);
}
var_dump($data);

However, this has one serious drawback and limitation: The items within the list can be freely arranged because its position in the list is not authenticated. This may be desirable in some cases, however!

If we decide to add arbitrary depth and list support to JsonFieldMap, it would not be as flexible: The indices of the list items would be authenticated, always.

@rabol
Copy link
Author

rabol commented May 5, 2023

Ok, thanks for the reply.

I my case I don't need to search in the json field, so I think I will convert the db field to a text and then json encode/decode
when storing / retrieving the data

@rabol
Copy link
Author

rabol commented May 5, 2023

I have noticed that if I do something like this UserDoc::select('from_title')
(select from_title from user_docs where user_id = 1)

I get an EmptyFieldException

basically any select that does not include all the encrypted columns will throw this exception.

I must have missed someting in my setup ?

@paragonie-security
Copy link
Contributor

paragonie-security commented May 7, 2023

Yes, empty fields are not permitted by default.

/**
* @return bool
*/
public function getPermitEmpty(): bool
{
return $this->permitEmpty;
}
/**
* @param bool $permitted
* @return $this
*/
public function setPermitEmpty(bool $permitted): static
{
$this->permitEmpty = $permitted;
return $this;
}

You can call setPermitEmpty(true) on your EncryptedRow object to avoid this exception.

@paragonie-security
Copy link
Contributor

The changes in #88 will also make this easier to automate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants