Closed kdenhartog closed 4 years ago
@dhh1128 you are correct with your https://github.com/hyperledger/aries-rfcs/issues/133#issuecomment-535691424 in that putting a reference to the Sender's key inside the spk
at the moment of sending does not solve the timing problem.
The thing is I'm not trying to solve that problem - I'm attempting to solve the "reverse lookup" problem although in a sloppy way I admit. But now I went back to one of your comments on #104 and see that you had actually proposed to insert this claim as sid
("sender ID"). I'm fine with sid
.
@dhh1128 about onion routing bloat: from what I can tell the main cause of bloat is base64 encoding ciphertext for packing, then encrypting and b64encoding it again. Each encode is a 4/3 multiplier on ciphertext size. The rest of the bloat is from the (effectively constant) size of the additional JSON wrapping, headers, etc, and most of that bloat remains with a header encryption chain, but that's definitely much more manageable (linear with onion layer depth rather than exponential). An ascii-safe encoding for binary data like the ciphertext is important for transmission, but an alternative solution would be:
Then when decrypting, the recipient will base64 decode and decrypt the ciphertext as usual, and then will see if it's an envelope to send on, in which case it base64 encodes just the ciphertext of this envelope.
You could split this between packing/unpacking and sending/receiving: when packing, don't b64 encode the ciphertext, and only b64 encode the ciphertext of the final envelope once all envelope layers have been packed. When receiving, b64 decode the ciphertext of the received envelope, and then unpack multiple layers if possible. (This split dodges the extra work of base64 encoding then immediately decoding the ciphertext when creating an onion envelope or peeling multiple layers in one agent)
@lovesh , you may want to comment on @Moopli 's idea. (Lovesh had previously proposed a solution to this problem.)
@dhh1128 @Baha-sk @kdenhartog @Moopli @lovesh
I think it's a good idea to reduce the size of the pack()
output. However, can we consider solving the "runaway bloat" problem related specifically for routing in a separate RFC? I think it would be cleaner if the re-encryption problem is addressed in the outer envelope (the Forward
message). Yes, we would be re-encrypting the output of pack()
but hopefully only once, and hopefully the metadata on this outer envelope is editable such that we can add hops as we please without having to touch the ciphertext again.
Just my 2 c.
I should note that I'm not trying to propose this as an alternative - I want to point out that the repeated base64 encoding (really, any binary -> ascii-safe encoding, if repeated on data) is the source of the exponential bloat. In fact it occurs to me that my scheme runs into problems when decoding, since arbitrary binary data (the payload) could contain unescaped string-terminating characters and in general break json parsing (if your payload is an envelope, which you must parse as JSON to forward to the next recipient, and by my scheme the ciphertext within that envelope is binary rather than for example base64).
@llorllale fair enough
@Moopli As you pointed out in your last comment, having the ciphertext of the inner envelope as bytes is not safe (you could represent the bytes as integers but that will increase the size as well). Secondly, decoding the inner envelope from base64 each time before re-encoding the outer envelope is expensive.
@lovesh can you referencing me to some documentation that shows that re-encrypting is not safe? I was not aware that this was a problem and would like to read up on it so I'm more aware of the problems it presents.
Here's a concern we've been running into recently with aries-framework-go. The current envelope format (I checked ACA-Py and Indy SDK) uses signing keys everywhere, converting them to encryption keys (as you can do with ed25519/curve25519) solely for encryption. Note that this extends to packing signing keys in the envelope, with the recipient doing conversion as well. @Baha-sk's current Go implementation of this draft spec packs the encryption keys, and since it's interoperable with the PHP example I'd assume @gamringer is doing the same.
There's a hole here:
Now, for RSA, for example, this should not be an issue, verification keys are encryption keys and could technically be used for both purposes, but for Curve25519, where we'd want to use Ed25519 for EdDSA, we do need to convert.
Personally I'd prefer that we generate signing and encryption keypairs as distinct keypairs, and develop a DIDexchange protocol that will exchange both, no matter whether the asymmetric crypto algorithm in use allows us to use the same keys or derive one from the other.
Agreed. I think @kdenhartog was planning to go this direction.
On Fri, Oct 11, 2019, 10:57 AM Filip Burlacu notifications@github.com wrote:
Here https://github.com/hyperledger/aries-framework-go/issues/454's a concern we've been running into recently with aries-framework-go. The current envelope format uses signing keys everywhere, converting them to encryption keys (as you can do with ed25519/curve25519) solely for encryption. Note that this extends to packing signing keys in the envelope, with the recipient doing conversion as well. @Baha-sk https://github.com/Baha-sk's current Go implementation of this draft spec packs the encryption keys, and since it's interoperable with the PHP example I'd assume @gamringer https://github.com/gamringer is doing the same.
There's a hole here:
- DIDexchange does not exchange encryption pubkeys in addition to verification keys
- This spec does not mandate that signing keys be used to create encryption keys, or that the keys included as part of the envelope be verification keys
Now, for RSA, for example, this should not be an issue, verification keys are encryption keys and could technically be used for both purposes, but for Curve25519, where we'd want to use Ed25519 for EdDSA, we do need to convert.
Personally I'd prefer that we generate signing and encryption keypairs as distinct keypairs, and develop a DIDexchange protocol that will exchange both, no matter whether the asymmetric crypto algorithm in use allows us to use the same keys or derive one from the other.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/hyperledger/aries-rfcs/issues/133?email_source=notifications&email_token=AAQ3JCFWIMMRVDXBGNVC433QOCV6DA5CNFSM4IE6JH2KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBATA4Y#issuecomment-541143155, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAQ3JCE2I3SDIBMACGNPVZLQOCV6DANCNFSM4IE6JH2A .
@Moopli @dhh1128 @kdenhartog would it make sense to bake encryption keys into the ~service
decorator and DIDDoc service
attribute by adding a note in the didcomm conventions RFC?
Here's a concern we've been running into recently with aries-framework-go. The current envelope format (I checked ACA-Py and Indy SDK) uses signing keys everywhere, converting them to encryption keys (as you can do with ed25519/curve25519) solely for encryption. Note that this extends to packing signing keys in the envelope, with the recipient doing conversion as well. @Baha-sk's current Go implementation of this draft spec packs the encryption keys, and since it's interoperable with the PHP example I'd assume @gamringer is doing the same.
There's a hole here:
* DIDexchange does not exchange encryption pubkeys in addition to verification keys * This spec does not mandate that signing keys be used to create encryption keys, or that the keys included as part of the envelope be verification keys
Now, for RSA, for example, this should not be an issue, verification keys are encryption keys and could technically be used for both purposes, but for Curve25519, where we'd want to use Ed25519 for EdDSA, we do need to convert.
Personally I'd prefer that we generate signing and encryption keypairs as distinct keypairs, and develop a DIDexchange protocol that will exchange both, no matter whether the asymmetric crypto algorithm in use allows us to use the same keys or derive one from the other.
What's being done in both the IndySDK implementations, the ACA-py implementation, and DIDComm-js is a conversion between an ed25519
key to an x25519
key. This is made possible by birational equivalence between Montgomery curves and twisted Edwards curves.
See here for the math: https://en.wikipedia.org/wiki/Montgomery_curve#Equivalence_with_twisted_Edwards_curves
As for whether this is secure or not is another question. In the use of RSA keys, it's been proven that reuse of an RSA key for both signing and encryption is insecure when using textbook RSA. However, with elliptic curves, there's been no one to produce proof showing it's secure or insecure to reuse keys for DHKE and signing operations. With that said, I tend to agree that the use of independent keys is a better practice and would suggest that's the direction we go.
However, I will point out that doing this will introduce new complexity around how we're managing keys and would need some new did doc conventions to list these keys in the DID Doc.
@Moopli @dhh1128 @kdenhartog would it make sense to bake encryption keys into the
~service
decorator and DIDDocservice
attribute by adding a note in the didcomm conventions RFC?
This is another option to consider. It could be useful to do this, but I'm of the opinion that stuffing more of the cryptography into the semantic layer is an anti-pattern because it requires developers who want to focus on business logic and protocols to now have to learn about proper key rotation, key reuse, and the different tradeoffs between key types. Essentially, they have to learn enough crypto to be dangerous. Rather it's of my opinion that we contain the cryptography to the secure channel protocol aka DIDComm Crypto layer. Especially if we expect the use of multiple parties in the communication.
@kdenhartog thanks for the detailed reply. I believe the envelope exchange is only used to encrypt/decrypt the packed payload, therefore it would make sense for the exchanged keys to be crypto keys and the payload should contain signing keys needed to verify its content.
So the message flow in an agent would look like this:
The sending agent will:
The receiving agent will:
This way, steps 3 and 4 above don't handle signing keys at the Envelope
layer while the payload
has all it needs for signing/verification.
The PHP example and Go example I posted earlier (here) is using encryption keys only (no signing keys) in the envelope. This should be in concordance with your opinion.
By the way, I liked the video about the Noise protocol from the YouTube video you posted. We do implement some of the features in the framework in our example, but stick to JWE compliance (with ephemeral keys + nonce, the Noise framework states nonces are not needed with ephemeral keys, that's another topic)
@kdenhartog thanks for the detailed reply. I believe the envelope exchange is only used to encrypt/decrypt the packed payload, therefore it would make sense for the exchanged keys to be crypto keys and the payload should contain signing keys needed to verify its content.
So the message flow in an agent would look like this:
* The sending agent will: 1. Sign a payload and embed its public verification (signing) key in the payload (as a DID document) 2. Call Pack(payload above + encryption keys), no signing keys involved for Pack * The receiving agent will: 1. Call Unpack(received envelope + their encryption key), no signing key involved here 2. Verify payload extracted from Unpack above using the embedded corresponding recipient verification(signing) from the extracted payload (the receiving agent can fetch from their wallet a matching sender verification key).
This way, steps 3 and 4 above don't handle signing keys at the
Envelope
layer while thepayload
has all it needs for signing/verification.The PHP example and Go example I posted earlier (here) is using encryption keys only (no signing keys) in the envelope. This should be in concordance with your opinion.
By the way, I liked the video about the Noise protocol from the YouTube video you posted. We do implement some of the features in the framework in our example, but stick to JWE compliance (with ephemeral keys + nonce, the Noise framework states nonces are not needed with ephemeral keys, that's another topic)
This is a good idea. In the JWS, we can provide a reference to a different key than the one referenced in the outer JWE layer. This raises the question though, what happens when the key used to sign the JWS is in a different DID Doc than the one in the JWE? There's some weird edge cases we should be accounting for here, but in total - I agree this is a good solution.
I want to re-emphasize something about key references that I've said before (possibly on different discussion threads): crypto can't depend on DID resolution. We cannot be looking up key references in DID documents to do either of the following operations:
The value of the kid
field in JW structures is not valid input to either of these operations. We could* use a key reference to confirm that the key we're seeing has proper standing with respect to the party that's purporting to communicate with us--but that is not the same thing as the cryptographic operation of validating a signature.
Are we aligned on this, or did I just raise eyebrows?
@dhh1128 that's what I'm trying to get at, JWE must not have any verification key stored in its headers. The keys stored in the envelope should be for encryption/decryption of the JWE only. This is one of the reasons why we should push the RFC to use the new JWE format I was referring to in my previous post.
@Baha-sk : I am content with the idea that we should split verification keys and encryption keys apart; the conflation of these was an accident of us using Ed25519. And I get that you're advocating the removal of the verkeys from the JWE. That's all fine. But what I don't know if we're aligned on is this: I want an encryption key value--NOT a reference to an encryption key named in a DID doc--to be named in the JWE, and I want a verkey value--NOT a reference to a verkey in a DID doc--as the field that a JWS uses to verify.
@dhh1128 the encryption keys in the JWE example are real public key values. They are not reference IDs nor are they stored in the DID doc.
Saw this update on the DIF issue tracker: https://github.com/decentralized-identity/DIDComm-js/issues/8#issuecomment-545125297
(referring to https://github.com/digitalbazaar/minimal-cipher and https://github.com/digitalbazaar/encrypted-data-vaults/issues/5)
The above noted effort allows:
content encryption algorithms:
C20P
)AES-GCM
(to allow for a FIPS compatibility mode)XC20P
)key encryption algorithms:
ECDH-ES+A256KW
Also note the kid format:
${did}#${keyAgreementKey.fingerprint()}
The kid format is fatally flawed unless it includes a versionstamp or (less optimally) a timestamp. How can we get it upgraded?
It would be nice to have alignment across communities on these topics.
I was also going to mention that it would be nice to have overlapping algorithm support in the envelope. Note that they have ECDH-ES+A256KW
and additionally allow for 256-bit AES-GCM
. The rationale for the latter also seems like a good idea (allowing for FIPS).
How is the kid
fatally flawed? In an encrypted envelope the kid is just suppose to act as an alias to a private key for the recipient, DID resolution isn't even required. The kid can literally be anything, as long as it is meaningful to the recipient, it really comes down to what the recipient indexes the keys in their kms against.
I think we need to be clearer around the intention of the kid in different circumstances.
When we are talking about encryption/decryption, the kid is really acting as an alias to a private key the recipient should have, whereas when we are verifying a signature, the kid should allow the audience of the signature to obtain a public key to perform verification.
I agree that we need to be clearer about intention. However, that doesn't affect my assertion that the kid
format of ${did}#${keyAgreementKey.fingerprint()}
is fatally flawed.
Understand: I'm not saying that the idea of kid
is fatally flawed. I'm staying that the specific kid
format is.
It's flawed because without a versionstamp or timestamp, it can't be dereferenced -- either for encryption or for signature validation.
Analogy: you want an email address, and I give you "@example.com" as the answer. I claim that answer is fatally flawed, because it doesn't include the mailbox identifier.
A kid
without a versionstamp or timestamp might be dereferencable, sometimes, if no key rotations have ever happened. And people who are building trivial, non-production systems get away with using such a kid
because they ignore this. But to really use a kid
to solve production problems, you can't ignore it. If you do, your kid
is fatally flawed.
I understand your reasoning, but this key format proposed I dont think is fundamentally flawed in the way you are describing for encryption and decryption, which is the context it is being applied in, because the fragment portion is a fingerprint. Which implies an immutable and unique reference to a single key.
Say you rotate a key but some sender continues to send you encrypted messages to your old key, provided you still have the old key indexed in your kms by this kid format, you should still be able to decrypt the message. And safely decrypt messages from new keys linked to the same did.
I do understand that because identifiers for keys in did docs are not enforced as immutable by default (e.g not every key featured in a did doc has an id corresponding to the keys unique fingerprint) this is not a universal rule for all did methods. However, this as a kid format if it were universally supported I believe would work without major issue for encryption/decryption.
Essentially when it comes to a kid for a signature, the easiest option to preserve verification is to make the kid the public key, no dereferencing required. However when it is an encrypted message the kid must be some meaningful alias to the recipient, there really is no need to have a versionstamp or timestamp, either the alias means something to me as a recipient or it doesn't.
I dont think is fundamentally flawed in the way you are describing for encryption and decryption, which is the context it is being applied in, because the fragment portion is a fingerprint. Which implies an immutable and unique reference to a single key.
The problem is that the key value is immutable, but the key's meaning with respect to the DID doc -- the context in which that key is related to the DID -- is not. That key value might have been authorized to encrypt on behalf of the DID at point A, and not authorized to encrypt at point B, and you wouldn't know that. So you could do naked decryption with just the key value, but you can't do the semantic validation that should follow naked decryption, without the additional context.
However when it is an encrypted message the kid must be some meaningful alias to the recipient, there really is no need to have a versionstamp or timestamp, either the alias means something to me as a recipient or it doesn't.
I disagree. You are assuming that all decryption happens at the time of transmission, which is also the time of reception. If you separate those timeframes, the "either it means something to me or it doesn't" unravels. It might not mean something to me at the time I receive it, but it might be important that it means something to me later. Or vice versa.
A kid
value is used to match a particular key. No less and no more. They're assigned by the party that creates the key and they're simply strings that are intended to uniquely identify that key. They work great for that purpose and are in widespread use.
@selfissued : Right. 100% agree. I think kid
s are great.
My concern is that the intersection between kid
s and DIDs is suffering because of a design decision in the DID spec (which I vehemently disagreed with but failed to change) that the relationship between a key identifier and a key value inside a DID doc is mutable. It is illegal to assume that did:foo:123#identifier always refers to the same thing.
Now, fans of JW* in DID circles have suggested eliminating id
as a required property on a key, claiming that kid
is an equivalent. I don't think they realize the mess they're signing up for. An id
has meaning in the larger context of the rest of the doc, and one of those meanings, according to the DID spec, is that you can use it to construct DID URLs with particular semantics. Among those semantics is the idea of mutability--that the data behind the identifier can be rotated. So if kid
is an equivalent of id
, then it follows that a kid
that's a DID URL composed of a DID doc plus an identifier is not allowed to be assumed to be stable with respect to time. This is in direct conflict with the intention of kid
, which is the simple and stable mapping you articulated.
Possible solution 1: we stop thinking that kid
and id
mean the same thing. If someone wants a key to have a mutable relationship between its identifier and its value, they use id
; if they want immutability, they use kid
. (Personally, I would never use 'id'...) We don't confuse them. A DID URL in the format did:foo:123#xyz is an id
-based URL, NOT a kid
-based URL. If we want a kid
-based URL, we use something different, like maybe did:foo:123;kid=xyz.
Possible solution 2: we like kid
= id
, and we accept that this means DID URLs that reference keys by kid
must have a versionstamp or timestamp to disambiguate. We stop kidding ourselves that did:foo:123#identifier is enough to accomplish what kid
does.
IMO the case you describe @dhh1128 should be offloaded for the recipient software to deal with, e.g if recipient software rotates a key and the same kid is used in a did doc, what does the recipient software do with the old key? If its deleted then you can't decrypt anyway regardless of having a timestamp. If the software keeps the key, then how is its indexed in the kms? Could you not preserve its association to the kid
it use to represent? Then if you receive a message that is encrypted to a kid where there have been multiple keys represented by that kid
over time, you try each one until you can successfully decrypt.
Alternatively if you don't want to traverse all previous keys associated to a kid
, we could add an optional JOSE header field that describes when the message was encrypted, that would then give the recipient further clue as to which key they need to use to decrypt.
I do however disagree with trying to pack more meaning into the kid
field, like a timestamp/versionstamp
The DID spec provides a format for identifying a DID Doc at a specific time using either the version-id
or version-time
parameters, doesn't that satisfy the need to refer to a specific key? The hl
property (hash) can also be used to ensure the document integrity.
"kid": "did:method:ident;version-id=GJdXXRq7Kxu7uKQCb77ik3;hl= zQmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e#key-ident"
It does, but doing this adds a lot of complexity in parsing this field. kid
is simply meant to be a string alias or a hint to a key, by making the requirement in DIDComm that this is a full URL we now need a url parser to extract this information and will need to evolve this syntax as DID resolution continues to evolve.
If there is a problem with kid reuse for multiple keys over time, I think the simple choice is for the audience or consumer of the message to iterate through the keys associated to one kid or for there to be a seperate field in the kid header that hints at timing information and therefore allows them to limit the possible keys the kid
is referring too.
IIRC, I heard there was discussion about this during the connectathon. Would anyone be able to provide details about that discussion here for historical context?
My memory of the conversation was that we got to a stalemate. @tplooker , please confirm. The comment thread here boils down to 2 basic positions, which also summarize the connectathon discussion:
kid
include a timestamp or version, so let's not do that.kid
, it must include a timestamp or version. (The assertion that we could have an ambiguous kid
that can be disambiguated by iterating over candidates until we find a match strikes me as worse than a code smell--it feels like a security vulnerability.)FWIW, the ticket about this issue in the DID spec repo includes a comment from one of the JWE spec authors (Mike Jones) that suggests the intent of kid
is to be unambiguous--else its use in caching is impossible.
@dhh1128, yes your summary is correct.
Continue issue here: https://github.com/decentralized-identity/didcomm-messaging/issues/22
Recently there's been discussion to update this work to be fully JWE compliant. One of the proposed solutions that's come up is to generate the following format:
Here's a provided example of what this would look like:
I'm curious what other's opinions are on this approach. I personally am not a fan of the bloat the compact JWE adds in order to encrypt the sender's public key. However, I've not found another approach yet that seems satisfactory. If anyone has some suggestions it would be appreciated.