Message ID | 20240330065506.3146-1-zhangyiqun@phytium.com.cn (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | KEYS: Add ECDH support | expand |
[+Cc linux-crypto] On Sat, Mar 30, 2024 at 02:55:06PM +0800, Zhang Yiqun wrote: > This patch is to introduce ECDH into keyctl syscall for > userspace usage, containing public key generation and > shared secret computation. > > It is mainly based on dh code, so it has the same condition > to the input which only user keys is supported. The output > result is storing into the buffer with the provided length. > > Signed-off-by: Zhang Yiqun <zhangyiqun@phytium.com.cn> > --- > Documentation/security/keys/core.rst | 62 ++++++ > include/linux/compat.h | 4 + > include/uapi/linux/keyctl.h | 11 + > security/keys/Kconfig | 12 + > security/keys/Makefile | 2 + > security/keys/compat_ecdh.c | 50 +++++ > security/keys/ecdh.c | 318 +++++++++++++++++++++++++++ > security/keys/internal.h | 44 ++++ > security/keys/keyctl.c | 10 + > 9 files changed, 513 insertions(+) > create mode 100644 security/keys/compat_ecdh.c > create mode 100644 security/keys/ecdh.c Nacked-by: Eric Biggers <ebiggers@google.com> The existing KEYCTL_PKEY_*, KEYCTL_DH_COMPUTE, and AF_ALG are causing enough problems. We do not need any more UAPIs like this. They are hard to maintain, break often, not properly documented, increase the kernel's attack surface, and what they do is better done in userspace. Please refer to the recent thread https://lore.kernel.org/linux-crypto/CZSHRUIJ4RKL.34T4EASV5DNJM@matfyz.cz/T/#u where these issues were discussed in detail. - Eric
On Sat Mar 30, 2024 at 8:55 AM EET, Zhang Yiqun wrote: > This patch is to introduce ECDH into keyctl syscall for "Introduce Elliptic Curve Diffie-Hellman (ECDH)" What does it mean to "introduce ECDH into keyctl syscall"? > userspace usage, containing public key generation and > shared secret computation. Who else uses syscalls other than user space? You are implying that there some other client. > > It is mainly based on dh code, so it has the same condition > to the input which only user keys is supported. The output > result is storing into the buffer with the provided length. What is "dh code"? > > Signed-off-by: Zhang Yiqun <zhangyiqun@phytium.com.cn> > --- > Documentation/security/keys/core.rst | 62 ++++++ > include/linux/compat.h | 4 + > include/uapi/linux/keyctl.h | 11 + > security/keys/Kconfig | 12 + > security/keys/Makefile | 2 + > security/keys/compat_ecdh.c | 50 +++++ > security/keys/ecdh.c | 318 +++++++++++++++++++++++++++ > security/keys/internal.h | 44 ++++ > security/keys/keyctl.c | 10 + > 9 files changed, 513 insertions(+) > create mode 100644 security/keys/compat_ecdh.c > create mode 100644 security/keys/ecdh.c > > diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst > index 326b8a973828..9749466a3c95 100644 > --- a/Documentation/security/keys/core.rst > +++ b/Documentation/security/keys/core.rst > @@ -884,6 +884,68 @@ The keyctl syscall functions are: > and either the buffer length or the OtherInfo length exceeds the > allowed length. > > + * Compute a elliptic curve Diffie-Hellman shared secret:: Compute an Elliptic Curve Diffie-Hellman (ECDH) shared secret: > + > + long keyctl(KEYCTL_ECDH_COMPUTE, struct keyctl_ecdh_params *params, > + char *buffer, size_t buflen, > + struct keyctl_curve_params *curve); > + > + The params struct contains serial numbers for two keys:: > + > + - The local private key > + - The shared public key > + > + The value computed is:: > + > + result = private EC-MUTIPLY public > + > + The buffer length must be at least the length of the prime, or zero. > + > + If the buffer length is nonzero, the length of the result is > + returned when it is successfully calculated and copied in to the > + buffer. When the buffer length is zero, the minimum required > + buffer length is returned. > + > + The curve parameter struct keyctl_curve_params is as follows: > + > + - ``char *curvename`` specifies the curve parameter used in > + the following computation. > + > + This function will return error EOPNOTSUPP if the key type is not > + supported, error ENOKEY if the key could not be found, or error > + EACCES if the key is not readable by the caller. > + > + * Generate a elliptic curve Diffie-Hellman shared public key:: s/::/:/ various locations > + > + long keyctl(KEYCTL_ECDH_GENPUBKEY, > + struct keyctl_ecdh_params *params, > + char *buffer, size_t buflen, > + struct keyctl_curve_params *curve); > + > + The params struct contains serial numbers for one keys:: > + > + - The local private key > + > + The value computed is:: > + > + result = private EC-MUTIPLY G > + > + The buffer length must be at least the length of the prime, or zero. > + > + If the buffer length is nonzero, the length of the result is > + returned when it is successfully calculated and copied in to the > + buffer. When the buffer length is zero, the minimum required > + buffer length is returned. > + > + The curve parameter struct keyctl_curve_params is as follows: > + > + - ``char *curvename`` specifies the curve parameter used in s/``char *curvename``/char *curvename/ > + the following computation. > + > + This function will return error EOPNOTSUPP if the key type is not > + supported, error ENOKEY if the key could not be found, or error > + EACCES if the key is not readable by the caller. > + > > * Restrict keyring linkage:: > > diff --git a/include/linux/compat.h b/include/linux/compat.h > index 233f61ec8afc..1f989ef5c9e1 100644 > --- a/include/linux/compat.h > +++ b/include/linux/compat.h > @@ -411,6 +411,10 @@ struct compat_keyctl_kdf_params { > __u32 __spare[8]; > }; > > +struct compat_keyctl_curve_params { > + compat_uptr_t curvename; > +}; Please, do not create this at all. > + > struct compat_stat; > struct compat_statfs; > struct compat_statfs64; > diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h > index 4c8884eea808..77b5d9d837a2 100644 > --- a/include/uapi/linux/keyctl.h > +++ b/include/uapi/linux/keyctl.h > @@ -70,6 +70,8 @@ > #define KEYCTL_MOVE 30 /* Move keys between keyrings */ > #define KEYCTL_CAPABILITIES 31 /* Find capabilities of keyrings subsystem */ > #define KEYCTL_WATCH_KEY 32 /* Watch a key or ring of keys for changes */ > +#define KEYCTL_ECDH_COMPUTE 33 /* Compute EC Diffie-Hellman values */ > +#define KEYCTL_ECDH_GENPUBKEY 34 /* Generate EC Diffie-Hellman public keys */ > > /* keyctl structures */ > struct keyctl_dh_params { > @@ -90,6 +92,15 @@ struct keyctl_kdf_params { > __u32 __spare[8]; > }; > > +struct keyctl_ecdh_params { > + __s32 priv; > + __s32 pub; > +}; > + > +struct keyctl_curve_params { > + char __user *curvename; This will cause ABI to be a total trainwreck because the size changes depending on arch bitsize. Please never do anything like this in an ioctl API. I.e. please change to __u64 curve_name > +}; > > + > #define KEYCTL_SUPPORTS_ENCRYPT 0x01 > #define KEYCTL_SUPPORTS_DECRYPT 0x02 > #define KEYCTL_SUPPORTS_SIGN 0x04 > diff --git a/security/keys/Kconfig b/security/keys/Kconfig > index abb03a1b2a5c..b36be8d8d501 100644 > --- a/security/keys/Kconfig > +++ b/security/keys/Kconfig > @@ -125,6 +125,18 @@ config KEY_DH_OPERATIONS > > If you are unsure as to whether this is required, answer N. > > +config KEY_ECDH_OPERATIONS > + bool "Elliptic Curve Diffie-Hellman operations on retained keys" > + depends on KEYS > + select CRYPTO > + select CRYPTO_ECDH > + help > + This option provides support for calculating Elliptic Curve > + Diffie-Hellman public keys and shared secrets using values > + stored as keys in the kernel. > + > + If you are unsure as to whether this is required, answer N. > + > config KEY_NOTIFICATIONS > bool "Provide key/keyring change notifications" > depends on KEYS && WATCH_QUEUE > diff --git a/security/keys/Makefile b/security/keys/Makefile > index 5f40807f05b3..590fc4724f37 100644 > --- a/security/keys/Makefile > +++ b/security/keys/Makefile > @@ -17,11 +17,13 @@ obj-y := \ > request_key_auth.o \ > user_defined.o > compat-obj-$(CONFIG_KEY_DH_OPERATIONS) += compat_dh.o > +compat-obj-$(CONFIG_KEY_ECDH_OPERATIONS) += compat_ecdh.o > obj-$(CONFIG_COMPAT) += compat.o $(compat-obj-y) > obj-$(CONFIG_PROC_FS) += proc.o > obj-$(CONFIG_SYSCTL) += sysctl.o > obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o > obj-$(CONFIG_KEY_DH_OPERATIONS) += dh.o > +obj-$(CONFIG_KEY_ECDH_OPERATIONS) += ecdh.o > obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += keyctl_pkey.o > > # > diff --git a/security/keys/compat_ecdh.c b/security/keys/compat_ecdh.c > new file mode 100644 > index 000000000000..040d2a1c5548 > --- /dev/null > +++ b/security/keys/compat_ecdh.c > @@ -0,0 +1,50 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* 32-bit compatibility syscall for 64-bit systems for ECDH operations > + * > + * Copyright (C) 2024 Zhang Yiqun <zhangyiqun@phytium.com.cn> > + */ > + > +#include <linux/uaccess.h> > + Not sure what is the purpose of this empty line? > +#include "internal.h" > + > +/* > + * Perform the ECDH computation or ECDH based key derivation. > + * > + * If successful, 0 will be returned. > + */ > +long compat_keyctl_ecdh_compute(struct keyctl_ecdh_params __user *params, > + char __user *buffer, size_t buflen, > + struct compat_keyctl_curve_params __user *curve) > +{ > + struct keyctl_curve_params curvecopy; > + struct compat_keyctl_curve_params compat_curvecopy; reverse xmas tree declaration order (various locations) > + > + if (!curve) > + return -EINVAL; > + > + if (copy_from_user(&compat_curvecopy, curve, sizeof(compat_curvecopy)) != 0) > + return -EFAULT; > + > + curvecopy.curvename = compat_ptr(compat_curvecopy.curvename); > + > + return keyctl_ecdh_compute(params, buffer, buflen, &curvecopy); > +} > + > +long compat_keyctl_ecdh_genpubkey(struct keyctl_ecdh_params __user *params, > + char __user *buffer, size_t buflen, > + struct compat_keyctl_curve_params __user *curve) > +{ > + struct keyctl_curve_params curvecopy; > + struct compat_keyctl_curve_params compat_curvecopy; > + > + if (!curve) > + return -EINVAL; > + > + if (copy_from_user(&compat_curvecopy, curve, sizeof(compat_curvecopy)) != 0) > + return -EFAULT; > + > + curvecopy.curvename = compat_ptr(compat_curvecopy.curvename); > + > + return keyctl_ecdh_genpubkey(params, buffer, buflen, &curvecopy); > +} > diff --git a/security/keys/ecdh.c b/security/keys/ecdh.c > new file mode 100644 > index 000000000000..5e5be22b920c > --- /dev/null > +++ b/security/keys/ecdh.c > @@ -0,0 +1,318 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* Crypto operations using stored keys > + * > + * Copyright (c) 2024 Zhang Yiqun <zhangyiqun@phytium.com.cn> > + */ > + > +#include <linux/slab.h> > +#include <linux/uaccess.h> > +#include <linux/scatterlist.h> > +#include <linux/crypto.h> > +#include <crypto/hash.h> > +#include <crypto/kpp.h> > +#include <crypto/ecdh.h> > +#include <keys/user-type.h> > +#include "internal.h" > + > +static ssize_t ecdh_data_from_key(key_serial_t keyid, char **data) > +{ > + struct key *key; > + key_ref_t key_ref; > + long status; > + ssize_t ret; > + > + key_ref = lookup_user_key(keyid, 0, KEY_NEED_READ); > + if (IS_ERR(key_ref)) { > + ret = -ENOKEY; > + goto error; > + } > + > + key = key_ref_to_ptr(key_ref); > + > + ret = -EOPNOTSUPP; > + if (key->type == &key_type_user) { > + down_read(&key->sem); > + status = key_validate(key); > + if (status == 0) { > + const struct user_key_payload *payload; > + uint8_t *duplicate; > + > + payload = user_key_payload_locked(key); > + > + duplicate = kmemdup(payload->data, payload->datalen, > + GFP_KERNEL); > + if (duplicate) { > + *data = duplicate; > + ret = payload->datalen; > + } else { > + ret = -ENOMEM; > + } > + } > + up_read(&key->sem); > + } > + > + key_put(key); > +error: > + return ret; > +} > + > +static void ecdh_free_data(struct ecdh *ecdh) > +{ > + kfree_sensitive(ecdh->key); > +} > + > +long keyctl_ecdh_compute(struct keyctl_ecdh_params __user *params, > + char __user *buffer, size_t buflen, > + struct keyctl_curve_params __user *curve) > +{ > + long ret; > + ssize_t dlen; > + int secretlen; > + int outlen; > + struct keyctl_ecdh_params pcopy; > + struct ecdh ecdh_inputs; > + struct scatterlist insg; in_sg > + struct scatterlist outsg; out_sg > + DECLARE_CRYPTO_WAIT(compl); > + struct crypto_kpp *tfm; > + struct kpp_request *req; > + uint8_t *secret; > + uint8_t *outbuf; out_buf > + char *dhname; > + > + if (!params || (!buffer && buflen) || !curve) { > + ret = -EINVAL; > + goto out1; > + } return -EINVAL; (remove out1 label) > + > + if (copy_from_user(&pcopy, params, sizeof(pcopy)) != 0) { > + ret = -EFAULT; > + goto out1; > + } > + > + memset(&ecdh_inputs, 0, sizeof(ecdh_inputs)); > + > + dlen = ecdh_data_from_key(pcopy.priv, &ecdh_inputs.key); > + if (dlen < 0) { > + ret = dlen; > + goto out1; > + } > + ecdh_inputs.key_size = dlen; > + > + secretlen = crypto_ecdh_key_len(&ecdh_inputs); > + secret = kmalloc(secretlen, GFP_KERNEL); > + if (!secret) { > + ret = -ENOMEM; > + goto out2; goto err_free_data; > + } > + > + ret = crypto_ecdh_encode_key(secret, secretlen, &ecdh_inputs); > + if (ret) > + goto out3; goto err_free_secret; ... you probably get the idea, please do this for all labels. > + > + dhname = strndup_user(curve->curvename, CRYPTO_MAX_ALG_NAME); > + > + tfm = crypto_alloc_kpp(dhname, 0, 0); > + if (IS_ERR(tfm)) { > + ret = PTR_ERR(tfm); > + goto out3; So who free's dhname in this case? BR, Jarkko
On Sat, 2024-03-30 at 00:04 -0700, Eric Biggers wrote: > [+Cc linux-crypto] > > On Sat, Mar 30, 2024 at 02:55:06PM +0800, Zhang Yiqun wrote: > > This patch is to introduce ECDH into keyctl syscall for > > userspace usage, containing public key generation and > > shared secret computation. > > > > It is mainly based on dh code, so it has the same condition > > to the input which only user keys is supported. The output > > result is storing into the buffer with the provided length. > > > > Signed-off-by: Zhang Yiqun <zhangyiqun@phytium.com.cn> > > --- > > Documentation/security/keys/core.rst | 62 ++++++ > > include/linux/compat.h | 4 + > > include/uapi/linux/keyctl.h | 11 + > > security/keys/Kconfig | 12 + > > security/keys/Makefile | 2 + > > security/keys/compat_ecdh.c | 50 +++++ > > security/keys/ecdh.c | 318 > > +++++++++++++++++++++++++++ > > security/keys/internal.h | 44 ++++ > > security/keys/keyctl.c | 10 + > > 9 files changed, 513 insertions(+) > > create mode 100644 security/keys/compat_ecdh.c > > create mode 100644 security/keys/ecdh.c > > Nacked-by: Eric Biggers <ebiggers@google.com> > > The existing KEYCTL_PKEY_*, KEYCTL_DH_COMPUTE, and AF_ALG are causing > enough problems. We do not need any more UAPIs like this. They are > hard to maintain, break often, not properly documented, increase the > kernel's attack surface, and what they do is better done in > userspace. Actually that's not entirely true. There is a use case for keys which is where you'd like to harden unwrapped key handling and don't have the ability to use a device. The kernel provides a harder exfiltration environment than user space, so there is a use case for getting the kernel to handle operations on unwrapped keys for the protection it affords the crytpographic key material. For instance there are people who use the kernel keyring to replace ssh-agent and thus *reduce* the attack surface they have for storing ssh keys: https://blog.cloudflare.com/the-linux-kernel-key-retention-service-and-why-you-should-use-it-in-your-next-application/ The same thing could be done with gpg keys or the gnome keyring. > Please refer to the recent thread > https://lore.kernel.org/linux-crypto/CZSHRUIJ4RKL.34T4EASV5DNJM@matfyz.cz/T/#u > where these issues were discussed in detail. This thread was talking about using the kernel for handling the algorithms themselves (which is probably best done in userspace) and didn't address using the kernel to harden the key protection environment. James
On Sat, Mar 30, 2024 at 09:09:51AM -0400, James Bottomley wrote: > On Sat, 2024-03-30 at 00:04 -0700, Eric Biggers wrote: > > [+Cc linux-crypto] > > > > On Sat, Mar 30, 2024 at 02:55:06PM +0800, Zhang Yiqun wrote: > > > This patch is to introduce ECDH into keyctl syscall for > > > userspace usage, containing public key generation and > > > shared secret computation. > > > > > > It is mainly based on dh code, so it has the same condition > > > to the input which only user keys is supported. The output > > > result is storing into the buffer with the provided length. > > > > > > Signed-off-by: Zhang Yiqun <zhangyiqun@phytium.com.cn> > > > --- > > > Documentation/security/keys/core.rst | 62 ++++++ > > > include/linux/compat.h | 4 + > > > include/uapi/linux/keyctl.h | 11 + > > > security/keys/Kconfig | 12 + > > > security/keys/Makefile | 2 + > > > security/keys/compat_ecdh.c | 50 +++++ > > > security/keys/ecdh.c | 318 > > > +++++++++++++++++++++++++++ > > > security/keys/internal.h | 44 ++++ > > > security/keys/keyctl.c | 10 + > > > 9 files changed, 513 insertions(+) > > > create mode 100644 security/keys/compat_ecdh.c > > > create mode 100644 security/keys/ecdh.c > > > > Nacked-by: Eric Biggers <ebiggers@google.com> > > > > The existing KEYCTL_PKEY_*, KEYCTL_DH_COMPUTE, and AF_ALG are causing > > enough problems. We do not need any more UAPIs like this. They are > > hard to maintain, break often, not properly documented, increase the > > kernel's attack surface, and what they do is better done in > > userspace. > > Actually that's not entirely true. There is a use case for keys which > is where you'd like to harden unwrapped key handling and don't have the > ability to use a device. The kernel provides a harder exfiltration > environment than user space, so there is a use case for getting the > kernel to handle operations on unwrapped keys for the protection it > affords the crytpographic key material. > > For instance there are people who use the kernel keyring to replace > ssh-agent and thus *reduce* the attack surface they have for storing > ssh keys: > > https://blog.cloudflare.com/the-linux-kernel-key-retention-service-and-why-you-should-use-it-in-your-next-application/ > > The same thing could be done with gpg keys or the gnome keyring. First, that blog post never actually said that the "replace ssh-agent with kernel keyrings" idea was deployed. It sounds like a proof of concept idea that someone thought was interesting and decided to blog about. Upstream OpenSSH has no support for Linux keyrings. It seems unlikely it would get added, especially given the OpenSSH developers' healthy skepticism of using broken Linux-isms. You're welcome to bring it up on openssh-unix-dev and get their buy-in first. Second, as mentioned by the blog post, the kernel also does not support private keys in the default OpenSSH format. That sort of thing is an example of the fundamental problem with trying to make the kernel support every cryptographic protocol and format in existence. Userspace simply has much more flexibility to implement whatever it happens to need. Third, ssh-agent is already a separate process, and like any other process the kernel enforces isolation of its address space. The potential loopholes are ptrace and coredumps, which ssh-agent already disables, except for ptrace by root which it can't do alone, but the system administrator can do that by setting the ptrace_scope sysctl to 3 or by using SELinux. Amusingly, the existing KEYCTL_DH_* APIs, and the KEYCTL_ECDH_* APIs proposed by this patch, only operate on user keys that the process has READ access to. This means that the keys can be trivially extracted by a shell script running in your user session. That's *less* secure than using an isolated process... That's not to mention that merging this will likely add vulnerabilities reachable by unprivileged users, just as the original KEYCTL_DH_* did. I had to fix some of them last time around (e.g. commits 12d41a023efb, bbe240454d86, 3619dec5103d, 1d9ddde12e3c, ccd9888f14a8, 199512b1234f). I don't really feel like doing it again. (Wait, was this supposed to *improve* security?) > > Please refer to the recent thread > > https://lore.kernel.org/linux-crypto/CZSHRUIJ4RKL.34T4EASV5DNJM@matfyz.cz/T/#u > > where these issues were discussed in detail. > > This thread was talking about using the kernel for handling the > algorithms themselves (which is probably best done in userspace) and > didn't address using the kernel to harden the key protection > environment. This patch is about using the kernel to handle a class of algorithm, Elliptic-Curve Diffie-Hellman. Which specific algorithm in that class (i.e. which elliptic curve), who knows. Just like the existing APIs like this, it's undocumented which algorithm(s) are actually supported. - Eric
Hi Eric, > > Amusingly, the existing KEYCTL_DH_* APIs, and the KEYCTL_ECDH_* APIs proposed by > this patch, only operate on user keys that the process has READ access to. This > means that the keys can be trivially extracted by a shell script running in your > user session. That's *less* secure than using an isolated process... > I can see this being true for user or session keys, but I don't think this is true of process or thread specific keys. At least I couldn't read any keys out of a test app when I tried. Regards, -Denis
On Sat, 2024-03-30 at 17:48 -0700, Eric Biggers wrote: > On Sat, Mar 30, 2024 at 09:09:51AM -0400, James Bottomley wrote: [...] > > For instance there are people who use the kernel keyring to replace > > ssh-agent and thus *reduce* the attack surface they have for > > storing > > ssh keys: > > > > https://blog.cloudflare.com/the-linux-kernel-key-retention-service-and-why-you-should-use-it-in-your-next-application/ > > > > The same thing could be done with gpg keys or the gnome keyring. > > First, that blog post never actually said that the "replace ssh-agent > with kernel keyrings" idea was deployed. It sounds like a proof of > concept idea that someone thought was interesting and decided to blog > about. Upstream OpenSSH has no support for Linux keyrings. The openssh community is incredibly resistant to out of house innovation. It has no support for engine or provider keys, for TPM keys, or for that systemd start patch xz just exploited ... > It seems unlikely it would get added, especially given the OpenSSH > developers' healthy skepticism of using broken Linux-isms. > You're welcome to bring it up on openssh-unix-dev and get their buy- > in first. I also didn't say just openssh. You picked the one you apparently know hardly ever accepts anyone else's ideas. I don't disagree that finding implementors is reasonable ... I just wouldn't pick openssh as the first upstream target. > Second, as mentioned by the blog post, the kernel also does not > support private keys in the default OpenSSH format. That sort of > thing is an example of the fundamental problem with trying to make > the kernel support every cryptographic protocol and format in > existence. Userspace simply has much more flexibility to implement > whatever it happens to need. That's a complete red herring. You don't need the kernel keyrings to support every format, you just need a user space converter to import to the kernel keyring format. Every device or token that can replace key handling has their own internal format and they all come with importers that do conversion. > Third, ssh-agent is already a separate process, and like any other > process the kernel enforces isolation of its address space. The > potential loopholes are ptrace and coredumps, which ssh-agent already > disables, except for ptrace by root which it can't do alone, but the > system administrator can do that by setting the ptrace_scope sysctl > to 3 or by using SELinux. Well, a) this doesn't survive privilege escalation and b) I don't think many people would buy into the notion that we should remove security functions from the kernel and give them to userspace daemons because it's safer. James
On Sat Mar 30, 2024 at 3:09 PM EET, James Bottomley wrote: > On Sat, 2024-03-30 at 00:04 -0700, Eric Biggers wrote: > > [+Cc linux-crypto] > > > > On Sat, Mar 30, 2024 at 02:55:06PM +0800, Zhang Yiqun wrote: > > > This patch is to introduce ECDH into keyctl syscall for > > > userspace usage, containing public key generation and > > > shared secret computation. > > > > > > It is mainly based on dh code, so it has the same condition > > > to the input which only user keys is supported. The output > > > result is storing into the buffer with the provided length. > > > > > > Signed-off-by: Zhang Yiqun <zhangyiqun@phytium.com.cn> > > > --- > > > Documentation/security/keys/core.rst | 62 ++++++ > > > include/linux/compat.h | 4 + > > > include/uapi/linux/keyctl.h | 11 + > > > security/keys/Kconfig | 12 + > > > security/keys/Makefile | 2 + > > > security/keys/compat_ecdh.c | 50 +++++ > > > security/keys/ecdh.c | 318 > > > +++++++++++++++++++++++++++ > > > security/keys/internal.h | 44 ++++ > > > security/keys/keyctl.c | 10 + > > > 9 files changed, 513 insertions(+) > > > create mode 100644 security/keys/compat_ecdh.c > > > create mode 100644 security/keys/ecdh.c > > > > Nacked-by: Eric Biggers <ebiggers@google.com> > > > > The existing KEYCTL_PKEY_*, KEYCTL_DH_COMPUTE, and AF_ALG are causing > > enough problems. We do not need any more UAPIs like this. They are > > hard to maintain, break often, not properly documented, increase the > > kernel's attack surface, and what they do is better done in > > userspace. > > Actually that's not entirely true. There is a use case for keys which > is where you'd like to harden unwrapped key handling and don't have the > ability to use a device. The kernel provides a harder exfiltration > environment than user space, so there is a use case for getting the > kernel to handle operations on unwrapped keys for the protection it > affords the crytpographic key material. > > For instance there are people who use the kernel keyring to replace > ssh-agent and thus *reduce* the attack surface they have for storing > ssh keys: > > https://blog.cloudflare.com/the-linux-kernel-key-retention-service-and-why-you-should-use-it-in-your-next-application/ > > The same thing could be done with gpg keys or the gnome keyring. Eric has a correct standing given that the commit message does not have motivation part at all. With a description of the problem that this patch is supposed to solve this would be more meaningful to review. BR, Jarkko
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst index 326b8a973828..9749466a3c95 100644 --- a/Documentation/security/keys/core.rst +++ b/Documentation/security/keys/core.rst @@ -884,6 +884,68 @@ The keyctl syscall functions are: and either the buffer length or the OtherInfo length exceeds the allowed length. + * Compute a elliptic curve Diffie-Hellman shared secret:: + + long keyctl(KEYCTL_ECDH_COMPUTE, struct keyctl_ecdh_params *params, + char *buffer, size_t buflen, + struct keyctl_curve_params *curve); + + The params struct contains serial numbers for two keys:: + + - The local private key + - The shared public key + + The value computed is:: + + result = private EC-MUTIPLY public + + The buffer length must be at least the length of the prime, or zero. + + If the buffer length is nonzero, the length of the result is + returned when it is successfully calculated and copied in to the + buffer. When the buffer length is zero, the minimum required + buffer length is returned. + + The curve parameter struct keyctl_curve_params is as follows: + + - ``char *curvename`` specifies the curve parameter used in + the following computation. + + This function will return error EOPNOTSUPP if the key type is not + supported, error ENOKEY if the key could not be found, or error + EACCES if the key is not readable by the caller. + + * Generate a elliptic curve Diffie-Hellman shared public key:: + + long keyctl(KEYCTL_ECDH_GENPUBKEY, + struct keyctl_ecdh_params *params, + char *buffer, size_t buflen, + struct keyctl_curve_params *curve); + + The params struct contains serial numbers for one keys:: + + - The local private key + + The value computed is:: + + result = private EC-MUTIPLY G + + The buffer length must be at least the length of the prime, or zero. + + If the buffer length is nonzero, the length of the result is + returned when it is successfully calculated and copied in to the + buffer. When the buffer length is zero, the minimum required + buffer length is returned. + + The curve parameter struct keyctl_curve_params is as follows: + + - ``char *curvename`` specifies the curve parameter used in + the following computation. + + This function will return error EOPNOTSUPP if the key type is not + supported, error ENOKEY if the key could not be found, or error + EACCES if the key is not readable by the caller. + * Restrict keyring linkage:: diff --git a/include/linux/compat.h b/include/linux/compat.h index 233f61ec8afc..1f989ef5c9e1 100644 --- a/include/linux/compat.h +++ b/include/linux/compat.h @@ -411,6 +411,10 @@ struct compat_keyctl_kdf_params { __u32 __spare[8]; }; +struct compat_keyctl_curve_params { + compat_uptr_t curvename; +}; + struct compat_stat; struct compat_statfs; struct compat_statfs64; diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h index 4c8884eea808..77b5d9d837a2 100644 --- a/include/uapi/linux/keyctl.h +++ b/include/uapi/linux/keyctl.h @@ -70,6 +70,8 @@ #define KEYCTL_MOVE 30 /* Move keys between keyrings */ #define KEYCTL_CAPABILITIES 31 /* Find capabilities of keyrings subsystem */ #define KEYCTL_WATCH_KEY 32 /* Watch a key or ring of keys for changes */ +#define KEYCTL_ECDH_COMPUTE 33 /* Compute EC Diffie-Hellman values */ +#define KEYCTL_ECDH_GENPUBKEY 34 /* Generate EC Diffie-Hellman public keys */ /* keyctl structures */ struct keyctl_dh_params { @@ -90,6 +92,15 @@ struct keyctl_kdf_params { __u32 __spare[8]; }; +struct keyctl_ecdh_params { + __s32 priv; + __s32 pub; +}; + +struct keyctl_curve_params { + char __user *curvename; +}; + #define KEYCTL_SUPPORTS_ENCRYPT 0x01 #define KEYCTL_SUPPORTS_DECRYPT 0x02 #define KEYCTL_SUPPORTS_SIGN 0x04 diff --git a/security/keys/Kconfig b/security/keys/Kconfig index abb03a1b2a5c..b36be8d8d501 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -125,6 +125,18 @@ config KEY_DH_OPERATIONS If you are unsure as to whether this is required, answer N. +config KEY_ECDH_OPERATIONS + bool "Elliptic Curve Diffie-Hellman operations on retained keys" + depends on KEYS + select CRYPTO + select CRYPTO_ECDH + help + This option provides support for calculating Elliptic Curve + Diffie-Hellman public keys and shared secrets using values + stored as keys in the kernel. + + If you are unsure as to whether this is required, answer N. + config KEY_NOTIFICATIONS bool "Provide key/keyring change notifications" depends on KEYS && WATCH_QUEUE diff --git a/security/keys/Makefile b/security/keys/Makefile index 5f40807f05b3..590fc4724f37 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -17,11 +17,13 @@ obj-y := \ request_key_auth.o \ user_defined.o compat-obj-$(CONFIG_KEY_DH_OPERATIONS) += compat_dh.o +compat-obj-$(CONFIG_KEY_ECDH_OPERATIONS) += compat_ecdh.o obj-$(CONFIG_COMPAT) += compat.o $(compat-obj-y) obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_SYSCTL) += sysctl.o obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o obj-$(CONFIG_KEY_DH_OPERATIONS) += dh.o +obj-$(CONFIG_KEY_ECDH_OPERATIONS) += ecdh.o obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += keyctl_pkey.o # diff --git a/security/keys/compat_ecdh.c b/security/keys/compat_ecdh.c new file mode 100644 index 000000000000..040d2a1c5548 --- /dev/null +++ b/security/keys/compat_ecdh.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* 32-bit compatibility syscall for 64-bit systems for ECDH operations + * + * Copyright (C) 2024 Zhang Yiqun <zhangyiqun@phytium.com.cn> + */ + +#include <linux/uaccess.h> + +#include "internal.h" + +/* + * Perform the ECDH computation or ECDH based key derivation. + * + * If successful, 0 will be returned. + */ +long compat_keyctl_ecdh_compute(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct compat_keyctl_curve_params __user *curve) +{ + struct keyctl_curve_params curvecopy; + struct compat_keyctl_curve_params compat_curvecopy; + + if (!curve) + return -EINVAL; + + if (copy_from_user(&compat_curvecopy, curve, sizeof(compat_curvecopy)) != 0) + return -EFAULT; + + curvecopy.curvename = compat_ptr(compat_curvecopy.curvename); + + return keyctl_ecdh_compute(params, buffer, buflen, &curvecopy); +} + +long compat_keyctl_ecdh_genpubkey(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct compat_keyctl_curve_params __user *curve) +{ + struct keyctl_curve_params curvecopy; + struct compat_keyctl_curve_params compat_curvecopy; + + if (!curve) + return -EINVAL; + + if (copy_from_user(&compat_curvecopy, curve, sizeof(compat_curvecopy)) != 0) + return -EFAULT; + + curvecopy.curvename = compat_ptr(compat_curvecopy.curvename); + + return keyctl_ecdh_genpubkey(params, buffer, buflen, &curvecopy); +} diff --git a/security/keys/ecdh.c b/security/keys/ecdh.c new file mode 100644 index 000000000000..5e5be22b920c --- /dev/null +++ b/security/keys/ecdh.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Crypto operations using stored keys + * + * Copyright (c) 2024 Zhang Yiqun <zhangyiqun@phytium.com.cn> + */ + +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/scatterlist.h> +#include <linux/crypto.h> +#include <crypto/hash.h> +#include <crypto/kpp.h> +#include <crypto/ecdh.h> +#include <keys/user-type.h> +#include "internal.h" + +static ssize_t ecdh_data_from_key(key_serial_t keyid, char **data) +{ + struct key *key; + key_ref_t key_ref; + long status; + ssize_t ret; + + key_ref = lookup_user_key(keyid, 0, KEY_NEED_READ); + if (IS_ERR(key_ref)) { + ret = -ENOKEY; + goto error; + } + + key = key_ref_to_ptr(key_ref); + + ret = -EOPNOTSUPP; + if (key->type == &key_type_user) { + down_read(&key->sem); + status = key_validate(key); + if (status == 0) { + const struct user_key_payload *payload; + uint8_t *duplicate; + + payload = user_key_payload_locked(key); + + duplicate = kmemdup(payload->data, payload->datalen, + GFP_KERNEL); + if (duplicate) { + *data = duplicate; + ret = payload->datalen; + } else { + ret = -ENOMEM; + } + } + up_read(&key->sem); + } + + key_put(key); +error: + return ret; +} + +static void ecdh_free_data(struct ecdh *ecdh) +{ + kfree_sensitive(ecdh->key); +} + +long keyctl_ecdh_compute(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct keyctl_curve_params __user *curve) +{ + long ret; + ssize_t dlen; + int secretlen; + int outlen; + struct keyctl_ecdh_params pcopy; + struct ecdh ecdh_inputs; + struct scatterlist insg; + struct scatterlist outsg; + DECLARE_CRYPTO_WAIT(compl); + struct crypto_kpp *tfm; + struct kpp_request *req; + uint8_t *secret; + uint8_t *outbuf; + char *dhname; + + if (!params || (!buffer && buflen) || !curve) { + ret = -EINVAL; + goto out1; + } + + if (copy_from_user(&pcopy, params, sizeof(pcopy)) != 0) { + ret = -EFAULT; + goto out1; + } + + memset(&ecdh_inputs, 0, sizeof(ecdh_inputs)); + + dlen = ecdh_data_from_key(pcopy.priv, &ecdh_inputs.key); + if (dlen < 0) { + ret = dlen; + goto out1; + } + ecdh_inputs.key_size = dlen; + + secretlen = crypto_ecdh_key_len(&ecdh_inputs); + secret = kmalloc(secretlen, GFP_KERNEL); + if (!secret) { + ret = -ENOMEM; + goto out2; + } + + ret = crypto_ecdh_encode_key(secret, secretlen, &ecdh_inputs); + if (ret) + goto out3; + + dhname = strndup_user(curve->curvename, CRYPTO_MAX_ALG_NAME); + + tfm = crypto_alloc_kpp(dhname, 0, 0); + if (IS_ERR(tfm)) { + ret = PTR_ERR(tfm); + goto out3; + } + + kfree(dhname); + + ret = crypto_kpp_set_secret(tfm, secret, secretlen); + if (ret) + goto out4; + + outlen = crypto_kpp_maxsize(tfm); + + if (buflen == 0) { + ret = outlen; + goto out4; + } else if (outlen > buflen) { + ret = -EOVERFLOW; + goto out4; + } + + outbuf = kzalloc(outlen, GFP_KERNEL); + if (!outbuf) { + ret = -ENOMEM; + goto out4; + } + + dlen = ecdh_data_from_key(pcopy.pub, (char **)&outbuf); + if (dlen != outlen) { + ret = dlen; + goto out5; + } + + sg_init_one(&insg, outbuf, outlen); + sg_init_one(&outsg, outbuf, outlen/2); + + req = kpp_request_alloc(tfm, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out5; + } + + kpp_request_set_input(req, &insg, outlen); + kpp_request_set_output(req, &outsg, outlen/2); + init_completion(&compl.completion); + kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | + CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &compl); + + /* + * For DH, generate_public_key and generate_shared_secret are + * the same calculation + */ + ret = crypto_kpp_compute_shared_secret(req); + if (ret == -EINPROGRESS) { + wait_for_completion(&compl.completion); + ret = compl.err; + if (ret) + goto out6; + } + + if (copy_to_user(buffer, outbuf, req->dst_len) == 0) + ret = req->dst_len; + else + ret = -EFAULT; + +out6: + kpp_request_free(req); +out5: + kfree_sensitive(outbuf); +out4: + crypto_free_kpp(tfm); +out3: + kfree_sensitive(secret); +out2: + ecdh_free_data(&ecdh_inputs); +out1: + return ret; +} + +long keyctl_ecdh_genpubkey(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct keyctl_curve_params __user *curve) +{ + long ret; + ssize_t dlen; + int secretlen; + int outlen; + struct keyctl_ecdh_params pcopy; + struct ecdh ecdh_inputs; + struct scatterlist outsg; + DECLARE_CRYPTO_WAIT(compl); + struct crypto_kpp *tfm; + struct kpp_request *req; + uint8_t *secret; + uint8_t *outbuf; + char *dhname; + + if (!params || (!buffer && buflen)) { + ret = -EINVAL; + goto out1; + } + + if (copy_from_user(&pcopy, params, sizeof(pcopy)) != 0) { + ret = -EFAULT; + goto out1; + } + + memset(&ecdh_inputs, 0, sizeof(ecdh_inputs)); + + dlen = ecdh_data_from_key(pcopy.priv, &ecdh_inputs.key); + if (dlen < 0) { + ret = dlen; + goto out1; + } + ecdh_inputs.key_size = dlen; + + secretlen = crypto_ecdh_key_len(&ecdh_inputs); + secret = kmalloc(secretlen, GFP_KERNEL); + if (!secret) { + ret = -ENOMEM; + goto out2; + } + + ret = crypto_ecdh_encode_key(secret, secretlen, &ecdh_inputs); + if (ret) + goto out3; + + dhname = strndup_user(curve->curvename, CRYPTO_MAX_ALG_NAME); + + tfm = crypto_alloc_kpp(dhname, 0, 0); + if (IS_ERR(tfm)) { + ret = PTR_ERR(tfm); + goto out3; + } + + kfree(dhname); + + ret = crypto_kpp_set_secret(tfm, secret, secretlen); + if (ret) + goto out4; + + outlen = crypto_kpp_maxsize(tfm); + + if (buflen == 0) { + ret = outlen; + goto out4; + } else if (outlen > buflen) { + ret = -EOVERFLOW; + goto out4; + } + + outbuf = kzalloc(outlen, GFP_KERNEL); + if (!outbuf) { + ret = -ENOMEM; + goto out4; + } + + sg_init_one(&outsg, outbuf, outlen); + + req = kpp_request_alloc(tfm, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out5; + } + + kpp_request_set_input(req, NULL, 0); + kpp_request_set_output(req, &outsg, outlen); + init_completion(&compl.completion); + kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | + CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &compl); + + /* + * For DH, generate_public_key and generate_shared_secret are + * the same calculation + */ + ret = crypto_kpp_generate_public_key(req); + if (ret == -EINPROGRESS) { + wait_for_completion(&compl.completion); + ret = compl.err; + if (ret) + goto out6; + } + + if (copy_to_user(buffer, outbuf, req->dst_len) == 0) + ret = req->dst_len; + else + ret = -EFAULT; + +out6: + kpp_request_free(req); +out5: + kfree_sensitive(outbuf); +out4: + crypto_free_kpp(tfm); +out3: + kfree_sensitive(secret); +out2: + ecdh_free_data(&ecdh_inputs); +out1: + return ret; +} diff --git a/security/keys/internal.h b/security/keys/internal.h index 2cffa6dc8255..165523e29b52 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -306,6 +306,50 @@ static inline long compat_keyctl_dh_compute( #endif #endif +#ifdef CONFIG_KEY_ECDH_OPERATIONS +extern long keyctl_ecdh_compute(struct keyctl_ecdh_params __user *param, + char __user *buffer, size_t buflen, + struct keyctl_curve_params __user *curve); +extern long keyctl_ecdh_genpubkey(struct keyctl_ecdh_params __user *param, + char __user *buffer, size_t buflen, + struct keyctl_curve_params __user *curve); +#ifdef CONFIG_COMPAT +extern long compat_keyctl_ecdh_compute(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct compat_keyctl_curve_params __user *curve); +extern long compat_keyctl_ecdh_genpubkey(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct compat_keyctl_curve_params __user *curve); +#endif +#else +static inline long keyctl_ecdh_compute(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct keyctl_curve_params __user *kdf) +{ + return -EOPNOTSUPP; +} +static inline long keyctl_ecdh_genpubkey(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct keyctl_curve_params __user *kdf) +{ + return -EOPNOTSUPP; +} +#ifdef CONFIG_COMPAT +long compat_keyctl_ecdh_compute(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct compat_keyctl_curve_params __user *curve) +{ + return -EOPNOTSUPP; +} +long compat_keyctl_ecdh_genpubkey(struct keyctl_ecdh_params __user *params, + char __user *buffer, size_t buflen, + struct compat_keyctl_curve_params __user *curve) +{ + return -EOPNOTSUPP; +} +#endif +#endif + #ifdef CONFIG_ASYMMETRIC_KEY_TYPE extern long keyctl_pkey_query(key_serial_t, const char __user *, diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 10ba439968f7..e690c130386a 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -2019,6 +2019,16 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, case KEYCTL_WATCH_KEY: return keyctl_watch_key((key_serial_t)arg2, (int)arg3, (int)arg4); + case KEYCTL_ECDH_COMPUTE: + return keyctl_ecdh_compute((struct keyctl_ecdh_params __user *) arg2, + (char __user *) arg3, (size_t) arg4, + (struct keyctl_curve_params __user *) arg5); + + case KEYCTL_ECDH_GENPUBKEY: + return keyctl_ecdh_genpubkey((struct keyctl_ecdh_params __user *) arg2, + (char __user *) arg3, (size_t) arg4, + (struct keyctl_curve_params __user *) arg5); + default: return -EOPNOTSUPP; }
This patch is to introduce ECDH into keyctl syscall for userspace usage, containing public key generation and shared secret computation. It is mainly based on dh code, so it has the same condition to the input which only user keys is supported. The output result is storing into the buffer with the provided length. Signed-off-by: Zhang Yiqun <zhangyiqun@phytium.com.cn> --- Documentation/security/keys/core.rst | 62 ++++++ include/linux/compat.h | 4 + include/uapi/linux/keyctl.h | 11 + security/keys/Kconfig | 12 + security/keys/Makefile | 2 + security/keys/compat_ecdh.c | 50 +++++ security/keys/ecdh.c | 318 +++++++++++++++++++++++++++ security/keys/internal.h | 44 ++++ security/keys/keyctl.c | 10 + 9 files changed, 513 insertions(+) create mode 100644 security/keys/compat_ecdh.c create mode 100644 security/keys/ecdh.c