Re: How to get my krb5 crypto lib upstream?

From: Chuck Lever III
Date: Wed May 31 2023 - 15:09:31 EST




> On May 31, 2023, at 1:02 PM, David Howells <dhowells@xxxxxxxxxx> wrote:
>
> Hi Herbert, Chuck,
>
> I'm wondering how to make progress on getting my krb5 crypto lib upstream.
>
> Can I push it as it stands and then we try and build it into the crypto API
> for it later? That would allow me to push my rxgk implementation for AF_RXRPC
> and at least allow userspace to use that.

Sharing our private conversation during LSF here:

My feeling is crypto/krb5 needs at least one consumer, and
it makes sense for the first one to be AFS.

Once crypto/krb5 is in the tree, I'll be glad to look at
starting to replace the internals of the SunRPC GSS Kerberos
mechanism with what is provided in crypto/krb5.

However, if there is going to be significant API churn after
crypto/krb5 lands, I'd like to wait until that stabilizes
before adopting major pieces of crypto/krb5 in SunRPC.


> As far as building a crypto API around it goes, we need four interfaces:
>
> (1) Key generation. We may need to generate a {cipher,hash} key pair {Ke,Ki}
> or just a hash key Kc. We might conceivably want both.
>
> At the moment, I return a prepared cipher or a prepared hash. I don't
> deal with the key pairing here as it makes testing a bit more awkward.
>
> int crypto_krb5_get_Kc(const struct krb5_enctype *krb5,
> const struct krb5_buffer *TK,
> u32 usage,
> struct krb5_buffer *key,
> struct crypto_shash **_shash,
> gfp_t gfp);
> int crypto_krb5_get_Ke(const struct krb5_enctype *krb5,
> const struct krb5_buffer *TK,
> u32 usage,
> struct krb5_buffer *key,
> struct crypto_sync_skcipher **_ci,
> gfp_t gfp);
> int crypto_krb5_get_Ki(const struct krb5_enctype *krb5,
> const struct krb5_buffer *TK,
> u32 usage,
> struct krb5_buffer *key,
> struct crypto_shash **_shash,
> gfp_t gfp);
>
> (2) PRF+ generation. This takes some a key and a metadata blob and generates
> a new blob that then gets used as a key.
>
> int crypto_krb5_calc_PRFplus(const struct krb5_enctype *krb5,
> const struct krb5_buffer *K,
> unsigned int L,
> const struct krb5_buffer *S,
> struct krb5_buffer *result,
> gfp_t gfp);
>
> (3) Encrypt and Decrypt.
>
> Encrypt has to be parameterised to take a specific confounder for testing
> and generate a random one for normal operation. The IV is fixed all
> zeroes in the cases I've implemented. When testing, decrypt should
> perhaps be passed the confounder to check it.
>
> When encrypting, the output buffer will be larger than the input buffer
> (or, at least, room must be set aside) so that a confounder, padding and
> a checksum can be inserted.
>
> When decrypting, we either want to provide a separate output buffer so
> that the confounder and checksum can be stripped off, or we need to be
> able to find out where the decrypted payload plus the padding will be (we
> can't work out how much padding there is - that's left to the caller).
>
> At the moment, I pass a single buffer descriptor, providing encrypt with
> extra space front and back and providing decrypt with somewhere to save
> offset and length:
>
> ssize_t crypto_krb5_encrypt(const struct krb5_enctype *krb5,
> struct krb5_enc_keys *keys,
> struct scatterlist *sg, unsigned int nr_sg,
> size_t sg_len,
> size_t data_offset, size_t data_len,
> bool preconfounded);
> int crypto_krb5_decrypt(const struct krb5_enctype *krb5,
> struct krb5_enc_keys *keys,
> struct scatterlist *sg, unsigned int nr_sg,

So are we going to stick with struct scatterlist here,
or should it be rather an iterator of some kind?


> size_t *_offset, size_t *_len,
> int *_error_code);
>
> I also allow a krb5/gssapi error code to be returned so that it can be
> used in the generation of abort messages. This needs sorting out
> better. It may be that only one code is actually relevant to this and
> that the caller generates all the rest as it checks the metadata.
>
> The AEAD interface might suffice as it stands if we pass in the keys
> already generated and passed in as a single key blob. We don't want an
> IV generator as the protocol defines the IV to use.
>
> (4) GetMIC and VerifyMIC.
>
> Both of these need parameterising with extra metadata that will get
> inserted into the hash before the data is hashed. GetMIC will insert the
> checksum into the buffer and VerifyMIC will check it and strip it off.
>
> I'm not sure that the hash API is suitable for this. AEAD might suit for
> GetMIC at least, but using AEAD for VerifyMIC would lead to an extraneous
> copy I'd prefer to avoid.
>
>
> ssize_t crypto_krb5_get_mic(const struct krb5_enctype *krb5,
> struct crypto_shash *shash,
> const struct krb5_buffer *metadata,
> struct scatterlist *sg, unsigned int nr_sg,
> size_t sg_len,
> size_t data_offset, size_t data_len);
> int crypto_krb5_verify_mic(const struct krb5_enctype *krb5,
> struct crypto_shash *shash,
> const struct krb5_buffer *metadata,
> struct scatterlist *sg, unsigned int nr_sg,
> size_t *_offset, size_t *_len,
> int *_error_code);
>
> There's a lot to be said in using an asynchronous overrideable interface for
> encrypt and decrypt. The problem is that we want to do simultaneous hash and
> crypt if we can. I think the Intel AES/SHA instructions permit this to be
> done and there is sufficient register space to do it - and I *think* that it
> may be possible to offload this to the Intel QAT or the Intel IAA on the
> latest 4th Gen Xeons - and maybe NICs that can handle NFS/SMB offload.

I agree that a hardware-based AES/SHA implementation of
encrypt/decrypt seems like a good step forward for storage
consumers like NFS and AFS. That could be a significant
benefit for switching SunRPC GSS to crypto/krb5.


> I think we'll perhaps need a "krb5 encoding type" API that can provide key
> derivation, PRF+ and information - something along the following lines:
>
> struct krb5_enctype {
> int etype; // Encryption (key) type
> int ctype; // Checksum type
> const char *name; // "Friendly" name
> const char *encrypt_name; // Crypto encrypt name
> const char *cksum_name; // Crypto checksum name
> const char *hash_name; // Crypto hash name
> u16 block_len; // Length of encryption block
> u16 conf_len; // Length of confounder
> u16 cksum_len; // Length of checksum
> u16 key_bytes; // Length of raw key
> u16 key_len; // Length of final key
> u16 hash_len; // Length of hash
> u16 prf_len; // Length of PRF() result
> u16 Kc_len; // Length of Kc
> u16 Ke_len; // Length of Ke
> u16 Ki_len; // Length of Ki
> bool keyed_cksum; // T if a keyed cksum
> bool pad; // T if should pad
> };
>
> We need to be able to look the encoding up by ID, not by name.

It's not clear why something like this would need to be
exposed to crypto/krb5 consumers. There are a few items
in here that XDR needs to know about (lengths and such)
but that kind of thing can be provided by a function
call rather than by having direct access to a structure.


--
Chuck Lever