On Fri, Jul 14, 2023 at 04:27:15PM -0500, Orie Steele wrote:
> Inline:
> 
> On Fri, Jul 14, 2023 at 3:42 PM lgl island-resort.com <[email protected]>
> wrote:
> 
> > While pKR (and sKR) are in/out args for Seal() and Open() they are
> > not transmitted in a COSE_Encrypt. So it doesn’t matter for
> > COSE-HPKE what they are.
> >
> >
> I agree regarding COSE_Encrypt, but disagree regarding COSE Key and
> support for HPKE in COSE more generally.

Yes, COSE_Key needs a way to represent public and private keys of all
HPKE KEMs.
 

> > I suppose this is a down side of specifying a protocol with an API.
> > API arguments that are transmitted protocol messages sit right next
> > to arguments that are not with no distinction in the specification.
> >
> >
> Yes, but can be fixed in downstream specs... If people want to fix it.

Well, is there a choice? There are parameters that have to be
transported and parameters that have to match up somehow. So it is up
to downstream spec to ensure those are transported or match up.


> > Note that pKR in 9180 is passed completely through and down into DH().  So
> > the format of pKR passed into Seal() only has to be the same as the format
> > consumed by DH(). DH() is just whatever DH API you have. These are all APIs
> > here, not transmitted messages, so pKR could be a key handle or a pointer.
> >
> >
> Yes, and in the context of vanilla HPKE that seems excellent.

As a note, making pKR be a key handle or a pointer is not really useful,
because it is a public key anyway. It is sKR that is useful to make a
key handle or a pointer in order to enable privilege separation.

 
> When building a higher order API like for COSE-HPKE or JOSE-HPKE, you
> might prefer to.... exploit that these can be whatever... in order to
> build a safer API, possibly with some better upfront validation.

You don't need to exploit that to archive those goals, as the higher-
level API code can just translate the COSE_Key/JWK into HPKE internal
format, which is then supported by HPKE API used.

There are currently 5 such translations, and new ones should be very
slow to appear.

In my prototype code, there are two functions that perform exactly this
kind of translation on COSE_Key.

However, this will not work for private keys that are handles or
pointers, as neither COSE_Key nor JWK can represent such things.

 
> A higher order API will throw if you try to get a crypto key for
> something that is not well formed, because operating on malformed keys
> is not a great thing to do...

If you pass something malformed to HPKE, it will fail anyway.


> At the end of Encap, the lower level hpke api will return opaque
> bytes, according to the kem id spec.
> 
> A higher order library might validate them... in case of a supply
> chain compromise, or for defense in depth, or just for sanity.

Such checks are waste of time. Way before such checks start failing,
interop starts failing. And for good portition of KEMs, the checks one
can do are extremely weak.

And breaking interop tends to generate high-priority bugs that get
looked at pretty quickly.


> That validation might look like:
> 
> 1. confirming the enc is correct with respect to the kem id.
> 2. in the case enc is a serialized ecdsa public key, this means
> checking lengths on x or y....

The cheap checks one can perform are:

- enc is of correct length.
- Any prefix bytes are correct.
- Numbers are in correct range.
- Uncompressed EC points satsify the curve equation.

Other checks are way too expensive to be worthwhile: E.g., square root
checks are not trivial cost, and only reject 50% of random garbage.


> The higher order library might choose to serialize enc after
> validating it.
> 
> (of course it does not need to, it can just forward the opaque bytes,
> without even validating them... unless the higher order spec says
> otherwise...).

I think that doing anything except forwarding opaque bytes would be a
bad idea, being more complicated than necressary.


> HPKE requires you to validate kem inputs and outputs...
> 
> https://datatracker.ietf.org/doc/html/rfc9180#section-4.1
> 
> But those are validations that apply to... the kem inputs and
> outputs.... not higher order key representations...
> 
> If your higher order API has JWK and JWE or COSE KEY and
> COSE_Encrypt....
> 
> You might also want to validate those structures... possibly throwing
> errors before things even get to the HPKE validate functions.

That requirement is on HPKE implementations, not on applications using
HPKE.

Those higher order keys only have be valid enough for conversion to HPKE
internal form to be possible. If the values are valid enough for that,
but still invalid, then HPKE will catch those.


> I don't read HPKE as "preventing" this... but some folks might not
> think doing that is "worth it",
> and HPKE is indeed constructed to not require this... just like how
> many folks prefer fire arms with no safety... and others prefer ones
> with biometric locks...

Where I think doing input checks is worthwhile:

- If one is not sure if lower level API does the checks. However, this
  is not the case here.
- If calling API implemented in unsafe language (proactive security).

I think output checks are just waste of time, unless specifically
required.


> The API I am looking for as a developer looks like this:
> 
> encrypt = (publicKey: application/cose-key, message: ... some plaintext
> binary) => application/cose (COSE_Encrypt)
> decrypt = (privateKey: application/cose-key, message: application/cose) =>
> ... some plaintext binary
> 
> encrypt = (publicKey: application/jwk+json message: ... some binary) =>
> application/jose (JWE-Like thing....)
> decrypt = (privateKey: application/jwk+json, message: application/jose) =>
> ... some binary

To me, that does not look safe...


BTW, here are the (prototype) APIs from my prototype COSE-HPKE code:

pub struct HpkeRecipientAlgorithm<'a>
{
        pub encryption: AEAD,
        pub compact: bool,
        //...
}


pub fn encrypt_to_single_recipient(recipient: &PublicKey, alginfo: 
HpkeRecipientAlgorithm, ad: &dyn EncryptAdditionalInfo, msg: &[u8], to: impl 
MessageSink, rng: &mut TemporaryRandomStream) -> Result<(), String>;

pub struct MultiRecipientEncrypt
{
        //...
}

impl MultiRecipientEncrypt
{
        pub fn new(alg: EncryptionAlgorithm, rng: &mut TemporaryRandomStream) 
-> Result<MultiRecipientEncrypt, String>;
        pub fn add_recipient(&mut self, recipient: &[CBOR]);
        pub fn add_recipient_hpke(&mut self, recipient: &PublicKey, alg: 
HpkeRecipientAlgorithm, sender: Option<&[u8]>, rng: &mut TemporaryRandomStream) 
-> Result<(), String>;
        pub fn finish(self, ad: &dyn EncryptAdditionalInfo, msg: &[u8], to: 
impl MessageSink) -> Result<(), String>;
}

pub struct ParsedCoseEncrypt<'a>
{
        pub protected: Vec<u8>,
        pub unprotected: CBOR,
        pub payload: Cow<'a, [u8]>,
        pub recipients: Option<CBOR>,
}

impl<'a> ParsedCoseEncrypt<'a>
{
        pub fn parse(x: &'a [u8], fuel: usize) -> Result<ParsedCoseEncrypt<'a>, 
String>;
        pub fn decrypt(&self, keys: &[PrivateKey], ad: &dyn AdditionalInfo, to: 
impl MessageSink) -> Result<(), String>;
}


> Internally, these APIs will call seal, and unseal, they will validate
> kem inputs and outputs, etc... they have to implement support for what
> the RFCs say for each kem.

Except for a few special-case key conversions (which know about the key
format used by those KEMs), there is no need for these APIs to know
about internals of HPKE.

Sometimes the key conversion might wind up as identity function. E.g.,
converting COSE_Key OKP/x25519 into KEM 32 key...


> As an aside, of course hardware and software isolation will demand not
> passing serialized keys around...
> so of course you will have cases where a pointer gets snaked all the way
> through HPKE, as is the case for WebCrypto, and I assume
> https://github.com/dajiaji/hpke-js#base-mode
>
> In those cases you get a pointer to a private key that supports the
> operations, but you never pass a serialized private key to a decrypt
> api...

For that to be possible, one obivously needs HPKE API that supports
pointers as sKR.


> You obviously can't validate a pointer to a private key.... so some of
> the validation assumed by HPKE is already not going to happen for
> isolated APIs, hardware, or remote kms...

HPKE specifies no checks on private keys. Only on public keys and
ciphertexts. Neither of those needs to be a pointer.


> See https://datatracker.ietf.org/doc/html/rfc9180#section-8.2
> 
> Specifically this part:
> 
> > As another example, some implementations of the DHKEM specified in this
> document may choose to transform ValidationError from DH() into an
> EncapError or DecapError from Encap() or Decap(), respectively, whereas
> others may choose to raise ValidationError unmodified.
> 
> Seems like throwing an Encap or Decap error when your higher order key
> serialization does not support HPKE could be a good idea... or I could be
> reading this sentence wrong.

I think you are reading it wrong.

It is just allowing reporting validation errors as generic errors. This
allows skipping double checking for errors that would cause crypto
operations to fail anyway.




-Ilari

_______________________________________________
COSE mailing list
[email protected]
https://www.ietf.org/mailman/listinfo/cose

Reply via email to