macaroon 0.3.0

Fully functional implementation of macaroons in Rust
Documentation

other implementations:

libmacaroons (C, main upstream)
- https://github.com/rescrv/libmacaroons
- tweetnacl, not libsodium (as of 7 years ago)
- SHA256 for HMAC, implementation in the repo
    https://github.com/rescrv/libmacaroons/blob/master/port.c#L103
- NOTE: tweetnacl indicates sha-512-256 in a header file, but this is not actually used
- python implementation bundled in this repo also uses SHA-256 (from hashlib)

jmacaroons (java)
- https://github.com/nitram509/jmacaroons
- SHA-256 for HMAC
    https://github.com/nitram509/jmacaroons/blob/master/src/main/java/com/github/nitram509/jmacaroons/CryptoTools.java#L62
- generator string: 'macaroons-key-generator'

macaroons (erlang)
- https://github.com/kzemek/macaroons
- uses libsodium
- generator string: "macaroons-key-generator"
- SHA-256 for HMAC
    https://github.com/kzemek/macaroons/blob/master/src/macaroon.hrl#L38

php-macaroons
- https://github.com/immense/php-macaroons
- libsodium, which is now built-in to php (?)
- generator string: 'macaroons-key-generator'
- php built-in 'hash_hmac' with 'sha256'
    https://github.com/immense/php-macaroons/blob/master/lib/Macaroons/Utils.php#L19

pymacaroons
- https://github.com/ecordell/pymacaroons
- no external crypto lib
- uses sha256 for HMAC, from std library (hashlib)
    https://github.com/ecordell/pymacaroons/blob/master/pymacaroons/utils.py#L48

js-macaroon
- https://github.com/go-macaroon/js-macaroon
- tweetnacl
- uses sha256 for HMAC
    https://github.com/go-macaroon/js-macaroon/blob/master/macaroon.js#L444

macaroon (golang)
- https://github.com/go-macaroon/macaroon
- x/crypto library
- uses sha256 for HMAC
    https://github.com/go-macaroon/macaroon/blob/v2/crypto.go#L22

compatability checker:
- https://github.com/go-macaroon/macarooncompat
- examples: https://github.com/go-macaroon/macarooncompat/blob/master/compat_test.go

## libmacaroons README

Basic:

    secret = 'this is our super secret key; only we should know it'
    public = 'we used our secret key'
    location = 'http://mybank/'
    M = macaroons.create(location, secret, public)

    assert M.identifier == 'we used our secret key'
    assert M.signature == 'e3d9e02908526c4c0039ae15114115d97fdd68bf2ba379b342aaf0f617d0552f'
    assert M.serialize(format=2) == 'MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAyZnNpZ25hdHVyZSDj2eApCFJsTAA5rhURQRXZf91ovyujebNCqvD2F9BVLwo'

    M_2 = M.add_first_party_caveat('account = 3735928559')
    assert M_2.identifier == 'we used our secret key'
    assert M_2.signature == '1efe4763f290dbce0c1d08477367e11f4eee456a64933cf662d79772dbb82128'

    M_3 = M_2.add_first_party_caveat('time < 2020-01-01T00:00')
    assert M_3.signature == 'b5f06c8c8ef92f6c82c6ff282cd1f8bd1849301d09a2db634ba182536a611c49'

    M_4 = M_3.add_first_party_caveat('email = alice@example.org')
    assert M_4.signature == 'ddf553e46083e55b8d71ab822be3d8fcf21d6bf19c40d617bb9fb438934474b6'

    # do serialization/deserialization and confirm same

Caveats:

    N = macaroons.deserialize('MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNl\nY3JldCBrZXkKMDAxZGNpZCBhY2NvdW50ID0gMzczNTkyODU1OQowMDIwY2lkIHRpbWUgPCAyMDIw\nLTAxLTAxVDAwOjAwCjAwMjJjaWQgZW1haWwgPSBhbGljZUBleGFtcGxlLm9yZwowMDJmc2lnbmF0\ndXJlID8f19FL+bkC9p/aoMmIecC7GxdOcLVyUnrv6lJMM7NSCg==\n')
    print N.inspect()
    location http://mybank/
    identifier we used our secret key
    cid account = 3735928559
    cid time < 2020-01-01T00:00
    cid email = alice@example.org
    signature 3f1fd7d14bf9b902f69fdaa0c98879c0bb1b174e70b572527aefea524c33b352

    V.verify(N, secret) => false

3rd party caveats:

    public = 'we used our other secret key'
    location = 'http://mybank/'
    M = macaroons.create(location, secret, public)
    M = M.add_first_party_caveat('account = 3735928559')
    print M.inspect()
    location http://mybank/
    identifier we used our other secret key
    cid account = 3735928559
    signature 1434e674ad84fdfdc9bc1aa00785325c8b6d57341fc7ce200ba4680c80786dda

    caveat_key = '4; guaranteed random by a fair toss of the dice'
    predicate = 'user = Alice'
    # send_to_auth(caveat_key, predicate)
    # identifier = recv_from_auth()
    identifier = 'this was how we remind auth of key/pred'
    M = M.add_third_party_caveat('http://auth.mybank/', caveat_key, identifier)
    print M.inspect()
    location http://mybank/
    identifier we used our other secret key
    cid account = 3735928559
    cid this was how we remind auth of key/pred
    vid AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr
    cl http://auth.mybank/
    signature d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c

    M.third_party_caveats()
    [('http://auth.mybank/', 'this was how we remind auth of key/pred')]


    D = macaroons.create('http://auth.mybank/', caveat_key, identifier)
    D = D.add_first_party_caveat('time < 2020-01-01T00:00')
    print D.inspect()
    location http://auth.mybank/
    identifier this was how we remind auth of key/pred
    cid time < 2020-01-01T00:00
    signature 2ed1049876e9d5840950274b579b0770317df54d338d9d3039c7c67d0d91d63c

Unit tests:

These have a specific format (there are several more)

    # a simple test of a macaroon with an exact caveat verified with the correct
    # root key and verifier
    version 1
    authorized
    key this is the key
    exact account = 3735928559
    TURBeU1XeHZZMkYwYVc5dUlHaDBkSEE2THk5bGVHRnRjR3hsTG05eVp5OEtNREF4Tldsa1pXNTBhV1pwWlhJZ2EyVjVhV1FLTURBeFpHTnBaQ0JoWTJOdmRXNTBJRDBnTXpjek5Ua3lPRFUxT1Fvd01ESm1jMmxuYm1GMGRYSmxJUFZJQl9iY2J0LUl2dzl6QnJPQ0pXS2pZbE05djNNNXVtRjJYYVM5SloySENn


## jmacaroons

    location = "http://mybank/";
    secret = "this is our super secret key; only we should know it";
    identifier = "we used our secret key";

    V1: "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAyZnNpZ25hdHVyZSDj2eApCFJsTAA5rhURQRXZf91ovyujebNCqvD2F9BVLwo"


    add_first_party_caveat("account = 3735928559")
    V1: "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAxZGNpZCBhY2NvdW50ID0gMzczNTkyODU1OQowMDJmc2lnbmF0dXJlIB7-R2PykNvODB0IR3Nn4R9O7kVqZJM89mLXl3LbuCEoCg");

(others seem mostly like libmacaroon?)

## pymacaroons

README:

    location='cool-picture-service.example.com',
    identifier='key-for-bob',
    key='asdfasdfas-a-very-secret-signing-key'

    m.add_first_party_caveat('picture_id = bobs_cool_cat.jpg')

    # location cool-picture-service.example.com
    # identifier key-for-bob
    # cid picture_id = bobs_cool_cat.jpg
    # signature 83d8fa280b09938d3cffe045634f544ffaf712ff2c51ac34828ae8a42b277f8f

    serialized: MDAyZWxvY2F0aW9uIGNvb2wtcGljdHVyZS1zZXJ2aWNlLmV4YW1wbGUuY29tCjAwMWJpZGVudGlmaWVyIGtleS1mb3ItYm9iCjAwMjdjaWQgcGljdHVyZV9pZCA9IGJvYnNfY29vbF9jYXQuanBnCjAwMmZzaWduYXR1cmUgg9j6KAsJk408_

    n = Macaroon.deserialize("MDAyZWxvY2F0aW9uIGNvb2wtcGljdHVyZS1zZXJ2aWNlLmV4YW1wbGUuY29tCjAwMWJpZGVudGlmaWVyIGtleS1mb3ItYm9iCjAwMjdjaWQgcGljdHVyZV9pZCA9IGJvYnNfY29vbF9jYXQuanBnCjAwMmZzaWduYXR1cmUgg9j6KAsJk408_-BFY09UT_r3Ev8sUaw0goropCsnf48K")

    def picture_access_validator(predicate):
        # in this case, predicate = 'picture_id = bobs_cool_cat.jpg'
        if predicate.split(' = ')[0] != 'picture_id':
            return False
        return predicate.split(' = ')[1] == 'bobs_cool_cat.jpg'

    v.satisfy_general(picture_access_validator)
    v.satisfy_exact('picture_id = bobs_cool_cat.jpg')