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

Secure Cell passphrase API: RbThemis #603

Merged
merged 8 commits into from
Mar 18, 2020
Merged

Conversation

ilammy
Copy link
Collaborator

@ilammy ilammy commented Mar 11, 2020

Add support of Secure Cell passphrase API to RbThemis. The API is described in RFC 3.4 (with corrections).

User notes

Passphrase-based interface of Secure Cell allows you to use short and memorable passphrases to secure your data. While symmetric keys are more secure, they are also longer and much harder for humans to remember.

Here is how you can use passphrases with Secure Cell:

require 'rbthemis'

cell = Themis::ScellSealPassphrase.new('my little password: secrets are magic')

encrypted = cell.encrypt('message data')
decrypted = cell.decrypt(encrypted)

assert_equals(decrypted, message)

Passphrase API accepts passphrases as relatively short strings, suitable for human memory. Master key API uses randomly generated, long binary keys, which are more suitable for machines to remember. However, they are also more efficient and generally more secure due to considerable length. You should prefer to use keys over passphrases if there are no humans involved. The interface is almost the same:

# Get a new key if you don't have one already:
master_key = Themis::gen_sym_key
# Or use an existing value that you store somewhere:
master_key = Base64.decode64 'b0gyNlM4LTFKRDI5anFIRGJ4SmQyLGE7MXN5YWUzR2U='

cell = Themis::ScellSeal.new(master_key)

encrypted = cell.encrypt('message data')
decrypted = cell.decrypt(encrypted)

assert_equals(decrypted, message)

API compatibility

Themis::Scell is deprecated

Instead of Themis::Scell with run-time mode-setting you should instantiate an appropriate subclass:

New API Old API
Themis::ScellSeal.new(key) Themis::Scell.new(key, Themis::Scell::SEAL_MODE)
Themis::ScellSealPassphrase.new(passphrase) not available
Themis::ScellTokenProtect.new(key) Themis::Scell.new(key, Themis::Scell::TOKEN_PROTECT_MODE)
Themis::ScellContextImprint.new(key Themis::Scell.new(key, Themis::Scell::CONTEXT_IMPRINT_MODE)

New objects have compatible API and no further changes in the code base should be required. However, you may wish to revisit decrypt calls for Token Protect.

Simpler Token Protect API

Token Protect mode now accepts encrypted data and token as separate arguments instead of requiring an array.

decrypted = cell.decrypt([encrypted, token], context) # old, requires a list

decrypted = cell.decrypt(encrypted, token, context)   # new, separate args

Unicode considerations

Master keys are always binary data. RbThemis ignores their encoding and always uses raw key bytes as is.

Passphrases are usually provided as text. In this case passphrase will be encoded in UTF-8 for compatibility with other Themis platforms. If you need a different encoding, please use encoding: optional argument, for example:

cell = Themis::ScellSealPassphrase.new(passphrase, encoding: 'UTF-16BE')

Binary passphrases are also accepted. If the passphrase encoding is BINARY then it is used as is, without any conversion.

Technical notes

Initially a slightly different syntax was planned:

cell = Themis::ScellSeal.with_key binary_master_key

cell = Themis::SCellSeal.with_passphrase 'a secret'

It had a noble idea of maintaining similarity with other languages which do not have named arguments. However, after updating tests and examples this syntax came off as unnatural. It also does not provide any value (it's even longer!) and we are not constrained by compatibility, like in Python.

Furthermore, in Ruby you import the entire module so Themis::SCellSealPassphrase class is visible. Other languages may require an awkward import to use SCellSealPassphrase name, but in Ruby you can use it right away.

Checklist

  • Change is covered by automated tests
  • The coding guidelines are followed
  • Public API has proper documentation
  • Example projects and code samples are up-to-date
  • Changelog is updated

Add "error_code" attribute that keeps the original error code returned
by Themis Core. This should make debugging issues a bit easier. Use
"invalid argument" as default value since that's the most common issue.
Add new classes implementing updated Secure Cell API and adding
passphrase support for Seal mode:

  - ScellSeal
  - ScellSealPassphrase
  - ScellTokenProtect
  - ScellContextImprint

This supercedes existing "Scell" class which is more or less deprecated.

The new implementation checks key validity before encryption/decryption
is attempted so new() methods may raise exceptions now.

Token Protect API is now more reasonable and accepts encrypted message
and authentication token as separate arguments for decryption. However,
for compatibility with old API we accept them as a list as well.

New passphrase API can be accessed via ScellSealPassphrase class.
First of all, update the tests to use new API and correctly distinguish
between passphrases and master keys. Add some new tests to verify
message integrity. Additionally, add compatibility tests which verify
that old API is still supported.
Update existing tools to use new API and add a new tool to verify
passphrase API across other languages.
Update code example to use new API. It's kinda dated and terse, bring it
to the same informational level as other languages. Also, fix shebang to
make the example executable directly.

P.S. Those historical names are amusing: full, auto split, user split...
Since new Secure Cell API is compatible with the old one (except for
initialization) we can remove all those swathes of code and replace
them with new implementation.

However, there are caveats due to how inheritance works in Ruby.

Here we proxy encrypt/decrypt calls to the actual object. Alternative
implementation could have looked like

    class Scell
      # Override "new" instead of "initialize" since that allows to
      # return a different instance, possibly even unrelated one.
      def self.new(key, mode)
        case mode
        when SEAL_MODE
          return ScellSeal.new(key)
        # ...
        end
      end
    end

This approach is a bit more efficient because there is no indirection in
encrypt/decrypt calls. However, this approach also makes it impossible
for ScellSeal to inherit from Scell. We would like to keep the
inheritance chain, just in case some code relies on the fact that all
Secure Cells are "kind_of? Themis::Scell". Hence... this.

Compatibility notwithstanding, Themis::Scell is considered deprecated
from now on. Since we have compatibility tests, make sure these warnings
are suppressed so that we are not annoyed by them in test output.
@ilammy ilammy added the W-RbThemis ♦️ Wrapper: RbThemis, Ruby API, Ruby gems label Mar 11, 2020
@ilammy ilammy changed the title Secure Cell passphrase API: PyThemis Secure Cell passphrase API: RbThemis Mar 11, 2020
@ilammy ilammy requested a review from shadinua March 11, 2020 14:14
@Lagovas
Copy link
Collaborator

Lagovas commented Mar 12, 2020

Token Protect mode now accepts encrypted data and token as separate arguments instead of requiring an array.

awesome

Copy link
Collaborator

@Lagovas Lagovas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

This function is used to convert a string into an FFI::MemoryPointer
with string's bytes that we can pass to Themis Core. It's current
implementation is... not ideal because it uses "force_encoding".

This is problematic because it does not return a copy of a string with
different encoding. It modifies the encoding of the string in-place.
This is unexpected for client code *and* does not work for fronzen
strings (e.g., literal constants).

Update the implementation to use FFI::MemoryPointer.from_string which
has the same behavior (returns a pointer into string's buffer) but
does not change the encoding of the string.

This function is used throughout RbThemis code, not only by Secure Cell.
It should be enough to add encoding tests for Secure Cell only, for now.
@ilammy
Copy link
Collaborator Author

ilammy commented Mar 13, 2020

Strange, but CircleCI build has failed while locally unit-test tests were fine. I did not run integration tests though, but they fail with the following error:

/var/lib/gems/2.3.0/gems/rbthemis-0.12.0/lib/rbthemis.rb:22:in `force_encoding': can't modify frozen String (RuntimeError)
	from /var/lib/gems/2.3.0/gems/rbthemis-0.12.0/lib/rbthemis.rb:22:in `string_to_pointer_size'
	from /var/lib/gems/2.3.0/gems/rbthemis-0.12.0/lib/rbthemis.rb:672:in `encrypt'
	from ./tools/ruby/scell_seal_string_echo_pw.rb:34:in `<main>'
  fail 

Debugging has uncovered a flaw in common string_to_pointer_size function. It turns out RbThemis has been accidentally modifying encodings of input strings all along. Well, no longer. I'll just quote the commit message:

Make string_to_pointer_size pure

This function is used to convert a string into an FFI::MemoryPointer with string's bytes that we can pass to Themis Core. It's current implementation is... not ideal because it uses force_encoding.

This is problematic because it does not return a copy of a string with different encoding. It modifies the encoding of the string in-place. This is unexpected for client code and does not work for frozen strings (e.g., literal constants).

Update the implementation to use FFI::MemoryPointer.from_string which has the same behavior (returns a pointer into string's buffer) but does not change the encoding of the string.

This function is used throughout RbThemis code, not only by Secure Cell. It should be enough to add encoding tests for Secure Cell only, for now.

@ilammy ilammy merged commit 83f2535 into cossacklabs:master Mar 18, 2020
@ilammy ilammy deleted the kdf/ruby branch March 18, 2020 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
W-RbThemis ♦️ Wrapper: RbThemis, Ruby API, Ruby gems
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants