Cryptographic ABI

已查看 58 次
跳至第一个未读帖子

Mickaël Salaün

未读,
2021年1月5日 12:54:042021/1/5
收件人 zirco...@fuchsia.dev、stora...@fuchsia.dev、securi...@fuchsia.dev、component-f...@fuchsia.dev
Hi,

Fuchsia is really interesting, especially from a security point of view,
but some pieces are missing for now. I would like to start a discussion
with some draft (and probably some bogus) ideas about confidentiality
and authenticity features mapping to the Fuchsia model. This document
doesn't dive too much into cryptographic implementation details but it
focuses on the system call API and ABI.

Encrypting component instances' data (kernel or component instance side)
is a requirement to enforce confidentiality without trusting (local or
remote) storage services (or intermediate services acting as proxies).
Component authentication can be used to only grant access to data to
their owner, to enforce quota per component instances, and to protect
against DoS.

The goal of the following proposition is to enable all component
instances to easily use their own set of cryptographic keys without
storing nor accessing them, thanks to the capability model and key
derivations. These keys can be used to encrypt and authenticate data (at
rest), and to authenticate (local or remote) peers in a secure and
privacy preserving way.

New kernel objects
------------------

One of the main challenges is to not directly tie keys to a component
instance (e.g. its job) nor its topology (i.e. moniker), which may
evolve with system updates. Another challenge is to enable multiple
cooperative component instances to share the same encrypted data without
accessing the key payload.

Using new dedicated kernel objects may enable any processes to encrypt,
sign, decrypt and authenticate specific data. This way, key management
remains independent from component hierarchies (jobs), even if in
practice they should be close. For instance, a component could still be
updated to create multiple internal jobs and transfer key handles to its
child jobs.

To achieve that, we add a set of system calls to manage new kernel
objects: keyring, msgkey, peerkey, passkey and passid. The kernel may be
in charge of some cryptographic processing (e.g. private symmetric keys)
while some other (e.g. private asymmetric keys) can be delegated to a
secure element (i.e. hardware): the private key payloads don't reach
user space and are assigned to a specific usage in a specific context.
This helps protect against key leaks, key misuses and persistent
compromised key. The kernel can also improve keys protection thanks to
dedicated memory mappings (cf. Linux's memfd_create/MFD_SECRET).

Keyrings
--------

This model relies on key derivations and handles. A (privileged) syscall
is dedicated to get one or multiple root keyrings derived from the
current secure boot state (e.g. TPM PCRs). This keyring can then be used
to derive usable keys.

zx_keyring_get_root(uint32_t slot, uint32_t options, zx_handle_t* out);

The zx_keyring_get_root() syscall enables to get some initial keyrings
tied to the system integrity. Only one call to get the keyring of a
specific @slot (e.g. a key derived from a PCR) should be allowed, and
only from a process with the ZX_RIGHT_ROOT privilege. The set of
cryptographic algorithms tie to the @out keyring (and its derived keys)
can be chosen at paving-time according to the hardware cryptography
acceleration features (e.g. AES-NI or ChaCha20). This set of algorithms
may not be part of Fuchsia ABI. The new @out keyring is derived from the
integrity of the running system (and hardware-bound), which make it
useful for local-only data encryption and local-only component instance
authentication.

Every component instance's cache should be automatically encrypted by
locally derived keys to get similar guarantees as with a Linux swap
encryption, but at the component instance level and with authentication.
This provides continuous authenticated and encrypted component instance
state for its whole lifetime. This encrypted data cannot be decrypted on
another running system.

zx_keyring_import(uint32_t cipher_set, const void* private_key, size_t
private_key_size, uint32_t options, zx_handle_t* out);

The zx_keyring_import() syscall may be used to create a keyring
independent from the integrity of the running system, which enables to
share keys between multiple running systems (e.g. to encrypt user
session data shared between multiple devices). Because of compatibility
reasons, the set of cryptographic algorithms specified by @cipher_set
(e.g. ZX_KEYRING_CIPHER_XCHACHA20_POLY1305_ARGON2) must be part of the
Fuchsia ABI. @private_key should be a high-entropy private key from
which multiple keys can be derived.

zx_keyring_derive(zx_handle_t parent, const void* context, size_t
context_size, uint32_t options, zx_handle_t* out);

The zx_keyring_derive() syscall enables to give their own set of keys to
other component instances. Indeed, deriving the main key of @parent
enables to create new derived keys thanks to a user-supplied
deterministic @context and a Key Derivation Function (KDF). @context can
be a stored (randomly generated) key tied to a component URL and its
absolute moniker, or a key derived from a user-supplied secret (e.g.
passwords for user sessions), or (if there is no storage available) a
hardcoded value. As a safeguard, @context must be unique (at creation
time) for all objects derived from the same parent.

zx_keyring_mask_rights(zx_handle_t keyring, uint32_t key_types,
zx_rights_t masked_rights);

The zx_keyring_mask_rights() syscall enables to (temporarily) remove (or
restore) a set of access rights to all the @keyring derived objects with
specific @key_types (bitmask). For instance, this may be interesting to
forbid decryption of data when the system is in a specific state (e.g.
locked user session). Underneath, cryptographic (public or private) keys
could be removed from (the kernel) memory to prevent hardware attacks,
or derived back to regain some abilities.

Data confidentiality and authenticity
-------------------------------------

Enforced Authenticated Encryption with Associated Data (AEAD) enables to
securely store data in a secure way, which also enables all component
instances to have a virtually infinite private storage space.

zx_msgkey_derive(zx_handle_t parent, const void* context, size_t
context_size, uint32_t options, zx_handle_t* out);

An msgkey object enables to encrypt, sign, decrypt and authenticate
data. The zx_msgkey_derive() syscall creates a new msgkey by deriving
the main key of a @parent handle referring to either a keyring or an
msgkey object. @context must not already be used by another child of
@parent and it must refer to a specific usage (e.g.
"storage-cache-data", "storage-cache-filename", etc.).

zx_msgkey_encrypt(zx_handle_t msgkey, uint64_t offset, const void*
buffer, size_t buffer_size, uint32_t options, void* out, size_t
out_size, size_t* actual);

The zx_msgkey_encrypt() syscall enables to directly encrypt and sign a
buffer with a @msgkey key. @offset set the message index used by the
encryption algorithm.

zx_msgkey_decrypt(zx_handle_t msgkey, uint64_t offset, const void*
buffer, size_t buffer_size, uint32_t options, void* out, size_t
out_size, size_t* actual);

The zx_msgkey_decrypt() syscall enables to directly decrypt and
authenticate a buffer with a @msgkey key.

zx_msgkey_encrypt and zx_msgkey_decrypt may be useful to quickly encrypt
and decrypt data (e.g. file name) and to further transform them (e.g.
base64) which may be required by the recipient (e.g. file system
service). Long-term encryption and decryption can be performed thanks to
two following syscalls.

zx_msgkey_splice_socket(zx_handle_t msgkey, uint64_t offset, zx_handle_t
socket, uint32_t options, zx_handle_t* out);

The zx_msgkey_splice_socke() syscall consumes @msgkey and @socket to
create a new @out socket. This socket can be used to transparently
encrypt/sign data with zx_socket_write() and decrypt/authenticate data
with zx_socket_read() to/from the other side of @socket. @offset sets
the initial message index which will be automatically increased with
socket reads and writes. Such
auto-encrypting/signing/decrypting/authenticating socket can be useful
to transmit confidential data between (remote or local) peers.

zx_msgkey_splice_stream(zx_handle_t msgkey, uint64_t offset, zx_handle_t
stream, uint32_t options, zx_handle_t* out);

The zx_msgkey_splice_socke() syscall consumes @msgkey and @stream to
create a new @out stream. This enables to transparently encrypt/decrypt
data to/from a VMO (e.g. read-only file). @offset sets the initial
message index which is automatically adjusted according to the
corresponding VMO offset.

Component instance authenticity
-------------------------------

Being able to authenticate a component instance may be useful for (local
or remote) storage services, secure delegated configurations (i.e.
storage quota tied to the client but data only accessible to the server)
and trusted human-computer interfaces (e.g. inform users of the
component instances drawing on screen).

A peerkey object refers to the (unforgeable) identity of a component
instance. A unique peerkey should be created by the component manager
and dedicated to each component instance it creates. Such component
receive their peerkey as an initial handle and can use it as a component
instance ID and to authenticate another component instance ID.

Using dedicated key handle types (keymsg, peerkey, passkey and passid)
avoids misuse of transferred keys (e.g. confused deputy attack).
Nonetheless, each key type should be derived from a different context.

zx_peerkey_derive(zx_handle_t keyring, const void* context, size_t
context_size, uint32_t options, zx_handle_t* out);

The zx_peerkey_derive() syscall creates a new @out peerkey object
derived from the main key of @keyring thanks to @context.

zx_passkey_create(zx_handle_t peerkey, const char* usage, size_t
usage_size, uint32_t options, zx_handle_t* out);

The zx_passkey_create() syscall creates a passkey object which enables
to authenticate and use a received passid. @usage sets a specific usage
name (e.g. "StorageProtocol:cache",
"UserInterfaceProtocol:notification", etc.) for @out which must match
the one used by the verified party (i.e. a passid). Unlike contexts used
for derived-type syscalls, a @peerkey can create passkeys with the same
@usage.

zx_passid_create(zx_handle_t peerkey, const char* usage, size_t
usage_size, zx_handle_t wrapped_handle, uint32_t options, zx_handle_t* out);

The zx_passid_create() syscall creates a pass identity @out from a
@peerkey. A passid is meant to be transferred to another component
instance. @usage sets a specific usage name for @out which must match
the one used by the verifier party (i.e. a passkey). @wrapped_handle is
an arbitrary optional handle (e.g. ZX_HANDLE_INVALID) to pass with the
passid and only accessible if authenticated.

The created @out passid doesn't have a ZX_RIGHT_DUPLICATE right. A
passid shouldn't have a (readable) related_koid, which could be used to
identify an (ephemeral) identity (i.e. peerkey KOID). Zircon always
requires a peerkey of the same keyring to use/unwrap a passid handle.
For instance, this enables to check that a service is in the same user
session as a client component instance, which may give guarantees that
shared data stay in a comparable trusted level.

zx_passid_unwrap(zx_handle_t passid, zx_handle_t passkey, uint32_t
options, zx_handle_t* out);

The zx_passid_unwrap() consumes @passid and return the wrapped handle,
usually a channel, or ZX_HANDLE_INVALID. @passid and @passkey must have
the same usage and they must come from the same keyring (but from
different peerkeys).

zx_passid_compute_hmac(zx_handle_t passid, zx_handle_t passkey, uint32_t
checksum_type, const void* buffer, size_t buffer_size, uint32_t options,
void* hmac, size_t hmac_size);

The zx_passid_compute_hmac() syscall generates and write the HMAC of the
input @buffer with the combination of @passid and @passkey. This enables
to authenticate a component instance while preserving its privacy (i.e.
not infering its URL nor its moniker), and avoid confused deputy attacks
by forwarding a received passid. The HMAC can be used to avoid flooding
services with an arbitrary number of IDs. @passid and @passkey must have
the same usage and they must come from the same keyring (but from
different peerkeys). @checksum_type identifies the used checksum (e.g.
ZX_KEY_CHECKSUM_SHA256). The @hmac_size must match the size of a
@checksum_type value.

Component instance encryption
-----------------------------

The following syscalls enable to encrypt data for a component instance
even if it is not started yet. For instance, this can be useful for
encrypting received user messages (e.g. SMS) for the user session
whereas the component instances related to this session are not
available because the session is not open nor unlocked.

zx_passid_get_public_key(zx_handle_t passid, zx_handle_t passkey,
uint32_t options, void* out, size_t out_size, size_t* actual);

The zx_passid_get_signature() syscall write the public key of the
passkey sibling of @passid to @out. @passkey must come from the same
peerkey as @passid. This signature enables to encrypt messages even when
the creator of @passid isn't available (e.g. user session not yet
started), thanks to the following syscall.

zx_msgkey_derive_encrypt(zx_handle_t passkey, const void*
passid_public_key, uint32_t options, zx_handle_t* out);

The zx_msgkey_derive_encrypt() syscall creates a new msgkey @out which
is only able to encrypt (i.e. write-only right) messages for the passkey
sibling of the passid which created @passid_public_key (i.e. with the
same usage and the same parent peerkey).

zx_msgkey_derive_decrypt(zx_handle_t passkey, zx_handle_t passid,
uint32_t options, zx_handle_t* out);

The zx_msgkey_derive_decrypt() syscall creates a new msgkey @out which
is only able to decrypt (i.e. read-only right) messages encrypted by the
passkey sibling of @passid for the signature of passid sibling of
@passkey. This enable component instances to decrypt messages signed by
a specific peer.

Component manager
-----------------

The component manager initially gets a root keyring derived from the
secure boot state (i.e. system authenticity). The component manager is
then in charge of deterministically (thanks to component URLs and
monikers) deriving new keyrings dedicated per component instance, and to
assign them a peerkey for component authentication and asymmetric
encryption.

Encrypted and shareable file hierarchies
----------------------------------------

Using msgkey objects, a similar approach to EncFS or ext4 encryption
could be implemented. Some inspiration can also be taken from iOS keys
(class) management.

At the files level, a process can create an msgkey object for a specific
file hierarchy. An encrypted (randomly generated) private key can be
stored in each file and directory (e.g. in the encrypted filename) and
used to encrypt and sign files' and directories' content. For each child
node, a new msgkey is derived from the parent msgkey and the node's key,
which creates a dedicated msgkey for the child hierarchy. When linking
or moving a file to another hierarchy, re-encrypt (or encrypt) the node
key with the new hierarchy key (i.e. similar to LUKS).

Thanks to this derivation mechanism, a file hierarchy can be shared with
a handle to this hierarchy (storage service side) and a handle to the
related msgkey (data owner side). When sharing a hierarchy read-only,
the msgkey can be shared without the write right. This approach is
theoretically independent from the file system but may have limitations
according to the filesystem limitations (e.g. file name length).
However, a native Fuchsia filesystem could leverage the fact that a
directory is also a file (which can then store an encrypted key for its
hierarchy).

For compatibility reasons, each node content should start with a magic
value tied to the specific set of cryptographic algorithms used to
encrypt and authenticate the rest of the content. As for ABI revisions,
next versions of this magic should not be predictable.

The filesystem FIDL protocols and the corresponding library should take
into account the bigger (encrypted) file name size (e.g. by grouping
them) and the per-node keys. The filesystem library (fdio) could use a
"_cleartext" suffix for all cleartext IO functions, which should rarely
be used (e.g. write file in cleartext on an USB drive).

Encrypted storage devices
-------------------------

At the storage device level, zxcrypt drivers could use a dedicated
keyring to encrypt a high-entropy key for their own use. It may be
preferable to use a quick symmetric encryption (i.e. without
authentication) to encrypt all local storage devices but let component
instances be in charge of their own data authentication (and decryption).

To avoid double encryption but still ensure that data at rest are
correctly encrypted (and signed), an improvement could be to ensure that
data written by zxcrypt clients to the zxcrypt device is encrypted by a
system-derived key. This could be enforced by a locked stream handle
which could only be unlocked by splicing it with an msgkey derived from
the root of a specific keyring (e.g. local root keyring form a specific
slot).

Network encryption
------------------

Similarly to the zxcrypt optimization, network encryption could be
enforced with locked socket handles to ensure that data is properly
encrypted thanks to an msgkey derived from the root of a specific
keyring (e.g. imported keyring shared between multiple Fuchsia devices).
This may be interesting for overnet.

Performance impact
------------------

The performance overhead should not be negligible, especially because of
the authentication mechanism, but new hardware features could improve it.

I think there is a performance improvement opportunity thanks to the
vDSO and CPU security features such as BTI, XOM and PAC (with its
limitations), but the thread capabilities would need to be restricted
and side channel attacks would need to be addressed. Another opportunity
would be to leverage the asynchronous syscall mechanism with ring
buffers, a bit like Linux's io_uring(2).

Regards,
Mickaël

Adam Barth

未读,
2021年1月5日 13:51:352021/1/5
收件人 Mickaël Salaün、zircon-dev、stora...@fuchsia.dev、securi...@fuchsia.dev、component-f...@fuchsia.dev
Hi Mickaël,

Thanks for your email.  Two process points and one substantive point:

## Process points

(1) You've cross-posted to many mailing lists.  I can understand why you might do that if you're unsure which mailing list makes the most sense or if your message touches on many different areas.  However, we would prefer less cross-posting, if possible.  There's a general "discuss" mailing list which can be used for cross-cutting concerns if there isn't a more specific mailing list that is appropriate.

(2) The way we normally handle proposals like yours through the RFC process rather than through mailing lists.  You can find out more about Fuchsia RFCs at https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/current_rfc_process .  The first step in the process is to socialize your idea with the project, which can be done over email.  However, your email has very specific technical designs, which is something that usually comes about in step 2 or 3.

## Substantive points

(3) It's likely that we'll want something like what you describe at some point.  However, there's a large pile of useful functionality we could add to the system.  We need to figure out in what order to add that functionality.  Generally, we'll add functionality to the project's roadmap based on two constraints: (a) if there is a "ready and willing customer" that needs the functionality, or (b) if not adding the functionality now makes it significantly (e.g., 10x) more difficult to add the functionality in the future.

Having a ready-and-willing customer is important to ground the work in concrete requirements.  That helps avoid the risk of building functionality that can't actually be used by a customer because we didn't understand their requirements well enough.

If you look at the project's roadmap...


... you'll see that many of the things we're working on are complete migrations and reducing technical debt.  That's a result of this YAGNI [1] approach to the project.

Based on what you've written, you seem like a very capable contributor, and I do hope that you'll end up contributing to Fuchsia.  I'd be happy to talk with you directly and help you find something to work on in the project that aligns with your interests.

Adam



--
All posts must follow the Fuchsia Code of Conduct https://fuchsia.dev/fuchsia-src/CODE_OF_CONDUCT or may be removed.
---
To unsubscribe from this group and stop receiving emails from it, send an email to component-framewo...@fuchsia.dev.

Mickaël Salaün

未读,
2021年1月6日 13:36:422021/1/6
收件人 Adam Barth、zircon-dev、stora...@fuchsia.dev、securi...@fuchsia.dev、component-f...@fuchsia.dev

On 05/01/2021 19:48, Adam Barth wrote:
> Hi Mickaël,
>
> Thanks for your email.  Two process points and one substantive point:
>
> ## Process points
>
> (1) You've cross-posted to many mailing lists.  I can understand why you
> might do that if you're unsure which mailing list makes the most sense
> or if your message touches on many different areas.  However, we would
> prefer less cross-posting, if possible.  There's a general "discuss"
> mailing list which can be used for cross-cutting concerns if there isn't
> a more specific mailing list that is appropriate.

OK, I'm continuing this thread on the "discuss" mailing list:
https://groups.google.com/a/fuchsia.dev/g/discuss/c/fhxQCth62WA
> <mailto:component-framework-dev%2Bunsu...@fuchsia.dev>.
>
回复全部
回复作者
转发
0 个新帖子