Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved meta.security object #185

Closed
d-stahl-ericsson opened this issue Mar 28, 2018 · 4 comments
Closed

Improved meta.security object #185

d-stahl-ericsson opened this issue Mar 28, 2018 · 4 comments
Labels
protocol All protocol changes protocol-incompat Protocol changes that aren't backwards compatible
Milestone

Comments

@d-stahl-ericsson
Copy link
Contributor

Description

The current meta.security object works, but it leaves a lot up to the wrapping layers to sort out. For instance, it's assumed that there's a naming scheme, that algorithms are handshaken, that keys are distributed etc. Sometimes they may be, and there really are very strict limitations on what a protocol can do here, but the aim should be to do/facilitate everything that can be done/facilitated to ensure secure communication.

I would recommend keeping meta.security optional, but restructuring it:

  • Introduce a mandatory meta.security.authorIdentity (that is, mandatory assuming that meta.security is used). Rather than a free text string, use Distinguished Name (https://tools.ietf.org/html/rfc2253), like X.509 certificates and LDAP.
  • Introduce a new optional object, "sequenceProtection", with the intent of enabling verification of intact sequences of events and easier protection against data loss, race conditions and replay attacks. It would contain an array of objects with two properties: "sequenceName" and "position". The effect is to let publishers state which position in any number of sequences this event has. For instance, in the "myDeployments" sequence, as a publisher I identify the events I send as being number 1, 2, 3 et cetera. If there is any break in sequence or reordering, this is easily detected, e.g. by a dedicated monitor looking for such issues, or by a vigilant event consumer.
  • Replace "sdm" with "integrityProtection". Keep "encryptedDigest", but add "algorithm" as optional field. Its value should be a valid algorithm JCA according to https://tools.ietf.org/html/rfc7518#appendix-A.1. This allows publishers to state which algorithm has been used to sign the event, providing greater flexibility in implementation. Furthermore, add an optional "publicKey", where the public key needed to decrypt is included. This is very convenient, as it means you don't have to distribute keys separately, but is of course not the most secure imaginable key distribution technique. Nevertheless it is commonly used (consider SSH), where the first key received is assumed to be genuine, and any subsequent keys are checked to detect unexpected changes in signature.

Motivation

Enabling the highest possible security is a good thing.

Exemplification

An example meta.security object would look like this:

"security": {
  "authorIdentity": "CN=John Doe,O=Awesome Company,C=Awesomistan",
  "integrityProtection": {
    "encryptedDigest": "abcdef123456",
    "algorithm": "SHA256withECDSA",
    "publicKey": "really-long-string-containing-the-public-key-matching-the-private-key-used-to-generate-the-encrypted-digest"
  },
  "sequenceProtection": [
    {
      "sequenceName": "allEvents",
      "position": 8734
    },
    {
      "sequenceName": "deploymentEvents",
      "position": 18
    }
  ]
}

Benefits

Would significantly strengthen the possibility to protect the integrity not just of individual Eiffel events, but the entire event stream.

Possible Drawbacks

Can't really think of any, except it will require new event type versions, so there's always compatibility to think of.

@ghost
Copy link

ghost commented Apr 3, 2018

It looks good and matches what we need in the project where I'm working.

There is however a practical problem with how to verify the signature when the signature is part of the message like this. It can be solved by specifying a canonical JSON representation for the messages. The issue is that both the event producer and the event consumer need to JSON-serialize the unsigned message (because the bytes of that message are the input to the signature algorithm). If the producer and consumer choose different JSON representations, e.g. by varying the whitespace or object key order, then the digests will not match. Compare with how X.509 uses DER instead of BER.

@d-stahl-ericsson
Copy link
Contributor Author

Right, that would make sense if deserialization and reserialization was expected to be part of the process (e.g. I sign a message, turn it into a POJO, turn it back into JSON, hand it back to you and expect you to be able to verify the signatute). In other words, if what you're signing is the information represented by the message, rather than the message itself.

In this case, I don't see that as being a requirement. If I serialize the message and then sign it, I expect you to get the exact same string of bytes as I signed, with no manipulation in between (with the exception of changing the value of encryptedDigest, but there's no ambiguity there, is there?). That means I can send you two semantically identical messages, which are syntactically different (let's say one has an extra line break at the end), which will end having different signatures. Is that really a problem?

Perhaps I'm missing something. It wouldn't be the first time :) If that's the case, please educate me!

@ghost
Copy link

ghost commented Apr 5, 2018

Well, my point is that just verifying the signature requires you to strip out encryptedDigest. The most natural way to do this is with a JSON library that turns it into a POJO and then back to JSON. And then if sender and receiver use different JSON libraries, they get different inputs to the signature algorithm.

Here's some code to experiment with (just some quick and dirty code, not a proper implementation of the proposal):

#!/usr/bin/env python
import copy
import hashlib
import hmac
import json

def sign_hmac(msg_pojo, key):
    print "\nSigning (pojo):"
    print repr(msg_pojo)
    msg_temp = copy.deepcopy(msg_pojo)
    msg_temp['meta']['security']['integrityProtection']['algorithm'] = 'HmacSHA256'
    msg_temp['meta']['security']['integrityProtection']['encryptedDigest'] = ''
    to_be_signed = json.dumps(msg_temp)
    print "Input to signature algorithm:"
    print to_be_signed
    digest = hmac.new(key, to_be_signed, hashlib.sha256).hexdigest()
    msg_temp['meta']['security']['integrityProtection']['encryptedDigest'] = str(digest)
    signed_text = json.dumps(msg_temp)
    print "Signed message:"
    print signed_text
    return signed_text

def verify_hmac(signed_text, key, indent):
    print "\nVerifying:"
    print signed_text
    msg_temp = json.loads(signed_text)
    assert msg_temp['meta']['security']['integrityProtection']['algorithm'] == 'HmacSHA256'
    encrypted_digest = msg_temp['meta']['security']['integrityProtection']['encryptedDigest']
    msg_temp['meta']['security']['integrityProtection']['encryptedDigest'] = ''
    to_be_signed = json.dumps(msg_temp, indent=indent)  # MUST be the same as to_be_signed in sign_hmac()
    print "Input to signature algorithm:"
    print to_be_signed
    digest = hmac.new(key, to_be_signed, hashlib.sha256).hexdigest()
    print "       Digest in the message:", encrypted_digest
    print " Digest from our computation:", digest
    if encrypted_digest != digest:
        print "BAD message digest"

def main():
    msg_pojo = {'meta': {'security': {'integrityProtection': {}}}}
    key = 'secret'
    signed = sign_hmac(msg_pojo, key)
    verify_hmac(signed, key, indent=None)
    verify_hmac(signed, key, indent=2)  # simulate that we are using a different JSON library

if __name__ == '__main__':
    main()

Output:


Signing (pojo):
{'meta': {'security': {'integrityProtection': {}}}}
Input to signature algorithm:
{"meta": {"security": {"integrityProtection": {"algorithm": "HmacSHA256", "encryptedDigest": ""}}}}
Signed message:
{"meta": {"security": {"integrityProtection": {"algorithm": "HmacSHA256", "encryptedDigest": "17c47dee3c13f8d3db3ba2aa64bf705c2fee4d160a0692b6dca27cbb3ac7cb4b"}}}}

Verifying:
{"meta": {"security": {"integrityProtection": {"algorithm": "HmacSHA256", "encryptedDigest": "17c47dee3c13f8d3db3ba2aa64bf705c2fee4d160a0692b6dca27cbb3ac7cb4b"}}}}
Input to signature algorithm:
{"meta": {"security": {"integrityProtection": {"algorithm": "HmacSHA256", "encryptedDigest": ""}}}}
       Digest in the message: 17c47dee3c13f8d3db3ba2aa64bf705c2fee4d160a0692b6dca27cbb3ac7cb4b
 Digest from our computation: 17c47dee3c13f8d3db3ba2aa64bf705c2fee4d160a0692b6dca27cbb3ac7cb4b

Verifying:
{"meta": {"security": {"integrityProtection": {"algorithm": "HmacSHA256", "encryptedDigest": "17c47dee3c13f8d3db3ba2aa64bf705c2fee4d160a0692b6dca27cbb3ac7cb4b"}}}}
Input to signature algorithm:
{
  "meta": {
    "security": {
      "integrityProtection": {
        "algorithm": "HmacSHA256", 
        "encryptedDigest": ""
      }
    }
  }
}
       Digest in the message: 17c47dee3c13f8d3db3ba2aa64bf705c2fee4d160a0692b6dca27cbb3ac7cb4b
 Digest from our computation: 3817257759b6783c26ad04f936d90838f56fea15715f24e4712c40b83947efdb
BAD message digest

The problem is essentially how to ensure that to_be_signed in verify_hmac() is the same as in sign_hmac().

I see three options:

  • Specify which canonical JSON representation to use.
  • Move the encryptedDigest value to outside of the data to be signed.
  • Verify the syntax of encryptedDigest and use search and replace to remove it before verification. No widely deployed protocol that I'm aware does it this way and it is not semantically correct since it can remove other parts of the message.

@d-stahl-ericsson d-stahl-ericsson added this to the Edition Agen milestone Jun 27, 2018
@d-stahl-ericsson
Copy link
Contributor Author

Got it. Thanks!

Specifying a canonical JSON representation to use in any security sensitive context seems like a good idea regardless, so I would suggest that option. Unfortunately I come up short trying to find any truly established, universal canonical JSON representation. The best I can come up with is this IET draft: https://tools.ietf.org/html/draft-staykov-hu-json-canonical-form-00. It's rather old, but the concent appears sound to me.

d-stahl-ericsson added a commit that referenced this issue Sep 18, 2018
Updated meta.security according to Issue #185. Documentation and schema of all event types has been updated, resulting in a major version step of all event types. Where applicable, examples have been updated, while some examples have been preserved to demonstrate previous behavior in older versions.

Description of information integrity protection has been updated in security.md.
@magnusbaeck magnusbaeck added protocol All protocol changes protocol-incompat Protocol changes that aren't backwards compatible labels Nov 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
protocol All protocol changes protocol-incompat Protocol changes that aren't backwards compatible
Projects
None yet
Development

No branches or pull requests

2 participants