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')