Runes - Simple Cookies You Can Extend (similar to Macaroons)

Overview

Runes - Simple Cookies You Can Extend (similar to Macaroons)

https://research.google/pubs/pub41892/ is a paper called "Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud". It has one good idea, some extended ideas nobody implements, and lots and lots of words.

The idea: a server issues a cookie to Alice. She can derive cookies with extra restrictions and hand them to Bob and Carol to send back to the server, and they can't remove the restrictions.

But they did it using a Message Authetication Code (MAC, get it?), which is actually counter-productive, since it's simpler and better to use Length Extension to achieve the same results. I call that a Rune; this version really only handles strings, but you can use hex or another encoding.

Rune Language

A rune is a series of restrictions; you have to pass all of them (so appending a new one always makes the rune less powerful). Each restriction is one or more alternatives ("cmd=foo OR cmd=bar"), any one of which can pass.

The form of each alternative is a simple string:

ALTERNATIVE := FIELDNAME CONDITION VALUE

FIELDNAME contains only UTF-8 characters, exclusive of ! " # $ % & ' ( ) * +, - . / : ; ? @ [ \ ] ^ _ ` { | } ~ (C's ispunct()). These can appear inside a VALUE, but &, | and \\ must be escaped with \ (escaping is legal for any character, but unnecessary).

CONDITION is one of the following values:

  • !: Pass if field is missing (value ignored)
  • =: Pass if exists and exactly equals
  • ^: Pass if exists and begins with
  • $: Pass if exists and ends with
  • ~: Pass if exists and contains
  • <: Pass if exists, is a valid decimal (may be signed), and numerically less than
  • >: Pass if exists, is a valid decimal (may be signed), and numerically greater than
  • }: Pass if exists and lexicograpically greater than (or longer)
  • {: Pass if exists and lexicograpically less than (or shorter)
  • #: Always pass: no condition, this is a comment.

Grouping using ( and ) may be added in future.

A restriction is a group of alternatives separated by |; restrictions are separated by &. e.g.

cmd=foo | cmd=bar
& subcmd! | subcmd{get

The first requires cmd be present, and to be foo or bar. The second requires that subcmd is not present, or is lexicographically less than get. Both must be true for authorization to succeed.

Rune Authorization

A run also comes with a SHA-256 authentication code. This is generated as SHA-256 of the following bytestream:

  1. The secret (less than 56 bytes, known only to the server which issued it).
  2. For every restriction:
    1. Pad the stream as per SHA-256 (i.e. append 0x80, then zeroes, then the big-endian 64-bit bitcount so far, such that it's a multiple of 64 bytes).
    2. Append the restriction.

By using the same padding scheme as SHA-256 usually uses to end the data, we have the property that we can initialize the SHA-256 function with the result from any prior restriction, and continue.

The server can validate the rune authorization by repeating this procedure and checking the result.

Rune Encoding

Runes are encoded as base64, starting with the 256-bit SHA256 authentication code, the followed by one or more restrictions separated by &.

Not because base64 is good, but because it's familiar to Web people; we use RFC3548 with + and / replaced by - and _ to make it URL safe.

API Example

Here's the server, making you a rune! (spoiler: it's "-YpZTBZ4Tb5SsUz3XIukxBxR619iEthm9oNJnC0LxZM=")

import runes
import secrets

# Secret determined by fair dice roll.
secret = bytes([5] * 16)

# Make an unrestricted rune.
rune = runes.MasterRune(secret)

# We could add our own restrictions here, if we wanted.
print("Your rune is {}".format(rune.to_base64()))

Here's the server, checking a rune. You will need to define what conditions you provide for the rune to test; one of the most useful ones is time, but other common things are the resource being accessed, (e.g. URL, or command and parameters), or who is accessing it (assuming you have authenticated them already in some way).

import runes
import time
import sys

secret = bytes([5] * 16)

# In real life, this would come from the web data.
runestring = sys.argv[1]

# This checks the format is correct, it's authorized, an that it meets
# our values.  I assume we have values time (UNIX, seconds since
# 1970), command and optional id.
# (You can also use rune.check() if you don't care *why* it failed)
ok, whyfail = rune.check_with_reason(secret, runestring,
                                     {'time': int(time.time()),
                                      'command': 'somecommand',
                                      'id': 'DEADBEEF'})
if not ok:
    print("Rune restrictions failed: {}".format(whyfail))
    sys.exit(1)

print("Yes, you passed!")

Here's the client Alice. She gets the rune and gives Bob a variant that can only be used for 1 minute:

import runes
import time

# In real life, this would come from the web data.
runestring = sys.argv[1]

# You'd catch exceptions here, usually.
rune = runes.from_base64(runestring)

# You can construct a Restriction class from a sequence of Alternative
# but it's easier to use decode() to translate a string
rune.add_restriction(rune.Restriction.decode("time < {}".format((int)time.time() + 60))

print("Your restricted rune is {}".format(rune.to_base64()))

You can find more examples in the examples/ subdirectory.

Author

Rusty Russell wrote it; but I blame @roasbeef for raving about them long enough at LnConf that I actually read the paper. It only took me 18 months to find a day to implement them.

You might also like...
Give you a better view of your Docker registry disk usage.

registry-du Give you a better view of your Docker registry disk usage. This small tool will analysis your Docker registry(vanilla or Harbor both work)

This code renames subtitle file names to your video files names, so you don't need to rename them manually.

Rename Subtitle This code renames your subtitle file names to your video file names so you don't need to do it manually Note: It only works for series

Keval allows you to call arbitrary Windows kernel-mode functions from user mode, even (and primarily) on another machine.
Keval allows you to call arbitrary Windows kernel-mode functions from user mode, even (and primarily) on another machine.

Keval Keval allows you to call arbitrary Windows kernel-mode functions from user mode, even (and primarily) on another machine. The user mode portion

A clock app, which helps you with routine tasks.

Clock This app helps you with routine tasks. Alarm Clock Timer Stop Watch World Time (Which city you want) About me Full name: Matin Ardestani Age: 14

This tool lets you perform some quick tasks for CTFs and Pentesting.
This tool lets you perform some quick tasks for CTFs and Pentesting.

This tool lets you convert strings and numbers between number bases (2, 8, 10 and 16) as well as ASCII text. You can use the IP address analyzer to find out details on IPv4 and perform abbreviation as well as expansion on IPv6 addresses.It can also perform a two's complement calculation as well.

This python program will display all SSID usernames and SSID passwords you once connected to your laptop
This python program will display all SSID usernames and SSID passwords you once connected to your laptop

Windows-Wifi-password-extractor This python program will display all SSID usernames and SSID passwords you once connected to your laptop How to run th

This repository contains scripts that help you validate QR codes.

Validation tools This repository contains scripts that help you validate QR codes. It's hacky, and a warning for Apple Silicon users: the dependencies

JeNot - A tool to notify you when Jenkins builds are done.
JeNot - A tool to notify you when Jenkins builds are done.

JeNot - Jenkins Notifications NOTE: under construction, buggy, and not production-ready What A tool to notify you when Jenkins builds are done. Why Je

A small python library that helps you to generate localization strings for your mobile projects.

LocalizationUtiltiy A small python library that helps you to generate localization strings for your mobile projects. This small script aims to help yo

Comments
  • Cannot install runes with pip or pip3

    Cannot install runes with pip or pip3

    It appears that there is a problem installing runes because the pypi package "sha256" cannot be installed. I have tried using the legacy-resolver with no luck. Couldn't find that package on github to submit an issue there, so this is still an issue for installing runes. Here's my output when I try pip install runes

    Defaulting to user installation because normal site-packages is not writeable
    Collecting runes
      Using cached runes-0.4.0-py3-none-any.whl (8.8 kB)
    Collecting sha256
      Using cached sha256-0.1.tar.gz (30 kB)
      Preparing metadata (setup.py): started
      Preparing metadata (setup.py): finished with status 'done'
    Using legacy 'setup.py install' for sha256, since package 'wheel' is not installed.
    Installing collected packages: sha256, runes
      Running setup.py install for sha256: started
      Running setup.py install for sha256: finished with status 'error'
      error: subprocess-exited-with-error
      
      × Running setup.py install for sha256 did not run successfully.
      │ exit code: 1
      ╰─> [125 lines of output]
          /usr/lib/python3.10/site-packages/setuptools/dist.py:717: UserWarning: Usage of dash-separated 'description-file' will not be supported in future versions. Please use the underscore name 'description_file' instead
            warnings.warn(
          running install
          /usr/lib/python3.10/site-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
            warnings.warn(
          running build
          running build_ext
          building 'sha256' extension
          creating build
          creating build/temp.linux-x86_64-3.10
          gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -fexceptions -Wp,-D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fstack-clash-protection -fcf-protection -flto -ffat-lto-objects -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -fexceptions -Wp,-D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fstack-clash-protection -fcf-protection -flto -march=x86-64 -mtune=generic -O3 -pipe -fno-plt -fexceptions -Wp,-D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fstack-clash-protection -fcf-protection -flto -fPIC -I/usr/include/python3.10 -c sha256.c -o build/temp.linux-x86_64-3.10/sha256.o
          sha256.c: In function ‘PyInit_sha256’:
          sha256.c:2823:28: error: ‘PyTypeObject’ {aka ‘struct _typeobject’} has no member named ‘tp_print’
           2823 |   __pyx_type_6sha256_sha256.tp_print = 0;
                |                            ^
          sha256.c: In function ‘__Pyx_ParseOptionalKeywords’:
          sha256.c:3061:21: warning: ‘_PyUnicode_get_wstr_length’ is deprecated [-Wdeprecated-declarations]
           3061 |                     (PyUnicode_GET_SIZE(**name) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                     ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:446:26: note: declared here
            446 | static inline Py_ssize_t _PyUnicode_get_wstr_length(PyObject *op) {
                |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
          sha256.c:3061:21: warning: ‘PyUnicode_AsUnicode’ is deprecated [-Wdeprecated-declarations]
           3061 |                     (PyUnicode_GET_SIZE(**name) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                     ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:580:45: note: declared here
            580 | Py_DEPRECATED(3.3) PyAPI_FUNC(Py_UNICODE *) PyUnicode_AsUnicode(
                |                                             ^~~~~~~~~~~~~~~~~~~
          sha256.c:3061:21: warning: ‘_PyUnicode_get_wstr_length’ is deprecated [-Wdeprecated-declarations]
           3061 |                     (PyUnicode_GET_SIZE(**name) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                     ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:446:26: note: declared here
            446 | static inline Py_ssize_t _PyUnicode_get_wstr_length(PyObject *op) {
                |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
          sha256.c:3061:21: warning: ‘_PyUnicode_get_wstr_length’ is deprecated [-Wdeprecated-declarations]
           3061 |                     (PyUnicode_GET_SIZE(**name) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                     ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:446:26: note: declared here
            446 | static inline Py_ssize_t _PyUnicode_get_wstr_length(PyObject *op) {
                |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
          sha256.c:3061:21: warning: ‘PyUnicode_AsUnicode’ is deprecated [-Wdeprecated-declarations]
           3061 |                     (PyUnicode_GET_SIZE(**name) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                     ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:580:45: note: declared here
            580 | Py_DEPRECATED(3.3) PyAPI_FUNC(Py_UNICODE *) PyUnicode_AsUnicode(
                |                                             ^~~~~~~~~~~~~~~~~~~
          sha256.c:3061:21: warning: ‘_PyUnicode_get_wstr_length’ is deprecated [-Wdeprecated-declarations]
           3061 |                     (PyUnicode_GET_SIZE(**name) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                     ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:446:26: note: declared here
            446 | static inline Py_ssize_t _PyUnicode_get_wstr_length(PyObject *op) {
                |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
          sha256.c:3077:25: warning: ‘_PyUnicode_get_wstr_length’ is deprecated [-Wdeprecated-declarations]
           3077 |                         (PyUnicode_GET_SIZE(**argname) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                         ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:446:26: note: declared here
            446 | static inline Py_ssize_t _PyUnicode_get_wstr_length(PyObject *op) {
                |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
          sha256.c:3077:25: warning: ‘PyUnicode_AsUnicode’ is deprecated [-Wdeprecated-declarations]
           3077 |                         (PyUnicode_GET_SIZE(**argname) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                         ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:580:45: note: declared here
            580 | Py_DEPRECATED(3.3) PyAPI_FUNC(Py_UNICODE *) PyUnicode_AsUnicode(
                |                                             ^~~~~~~~~~~~~~~~~~~
          sha256.c:3077:25: warning: ‘_PyUnicode_get_wstr_length’ is deprecated [-Wdeprecated-declarations]
           3077 |                         (PyUnicode_GET_SIZE(**argname) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                         ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:446:26: note: declared here
            446 | static inline Py_ssize_t _PyUnicode_get_wstr_length(PyObject *op) {
                |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
          sha256.c:3077:25: warning: ‘_PyUnicode_get_wstr_length’ is deprecated [-Wdeprecated-declarations]
           3077 |                         (PyUnicode_GET_SIZE(**argname) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                         ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:446:26: note: declared here
            446 | static inline Py_ssize_t _PyUnicode_get_wstr_length(PyObject *op) {
                |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
          sha256.c:3077:25: warning: ‘PyUnicode_AsUnicode’ is deprecated [-Wdeprecated-declarations]
           3077 |                         (PyUnicode_GET_SIZE(**argname) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                         ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:580:45: note: declared here
            580 | Py_DEPRECATED(3.3) PyAPI_FUNC(Py_UNICODE *) PyUnicode_AsUnicode(
                |                                             ^~~~~~~~~~~~~~~~~~~
          sha256.c:3077:25: warning: ‘_PyUnicode_get_wstr_length’ is deprecated [-Wdeprecated-declarations]
           3077 |                         (PyUnicode_GET_SIZE(**argname) != PyUnicode_GET_SIZE(key)) ? 1 :
                |                         ^
          In file included from /usr/include/python3.10/unicodeobject.h:1046,
                           from /usr/include/python3.10/Python.h:83,
                           from sha256.c:24:
          /usr/include/python3.10/cpython/unicodeobject.h:446:26: note: declared here
            446 | static inline Py_ssize_t _PyUnicode_get_wstr_length(PyObject *op) {
                |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
          error: command '/usr/bin/gcc' failed with exit code 1
          [end of output]
      
      note: This error originates from a subprocess, and is likely not a problem with pip.
    error: legacy-install-failure
    
    × Encountered error while trying to install package.
    ╰─> sha256
    
    note: This is an issue with the package mentioned above, not pip.
    hint: See above for output from the failure.
    
    opened by PestToast 2
Owner
Rusty Russell
GPG: 15EE 8D6C AB0E 7F0C F999 BFCB D920 0E6C D1AD B8F1 Rusty Russell
Rusty Russell
Script to decrypt / import chromium (edge/chrome) cookies

Cloonie Script to decrypt / import chromium (edge/chrome) cookies. Requirements Install the python dependencies via pip: pip install -r requirements.t

Lorenzo Bernardi 5 Sep 13, 2022
🔩 Like builtins, but boltons. 250+ constructs, recipes, and snippets which extend (and rely on nothing but) the Python standard library. Nothing like Michael Bolton.

Boltons boltons should be builtins. Boltons is a set of over 230 BSD-licensed, pure-Python utilities in the same spirit as — and yet conspicuously mis

Mahmoud Hashemi 6k Jan 4, 2023
Similar looking domain detection using python fuzzywuzzy

Major cause of phishing and BEC incident is similar looking domain, if you detect it early, you can prevent incidents early, python fuzzywuzzy module let you do that

null 2 Nov 7, 2021
This is Cool Utility tools that you can use in python.

This is Cool Utility tools that you can use in python. There are a few tools that you might find very useful, you can use this on pretty much any project and some utils might help you a lot and save so much time since it’s a simple function.

Senarc Studios 6 Apr 18, 2022
SH-PUBLIC is a python based cloning script. You can clone unlimited UID facebook accounts by using this tool.

SH-PUBLIC is a python based cloning script. You can clone unlimited UID facebook accounts by using this tool. This tool works on any Android devices without root.

(Md. Tanvir Ahmed) 5 Mar 9, 2022
A simple dork generator written in python that outputs dorks with the domain extensions you enter

Dork Gen A simple dork generator written in python that outputs dorks with the domain extensions you enter in a ".txt file". Usage The code is pretty

Z3NToX 4 Oct 30, 2022
Multipurpose Growtopia Server tools, can be used for newbie to learn things.

Information Multipurpose Growtopia Server tools, can be used for newbie to learn things. Requirements - Python 3.x - Operating System (Recommended : W

Morphias 2 Oct 29, 2021
This two python programs can convert km to miles and miles to km

km-to-miles These two little python programs can convert kilometers to miles and miles to kilometers Needed Python3 or a online python compiler with t

Chandula Janith 3 Jan 30, 2022
Pampy: The Pattern Matching for Python you always dreamed of.

Pampy: Pattern Matching for Python Pampy is pretty small (150 lines), reasonably fast, and often makes your code more readable and hence easier to rea

Claudio Santini 3.5k Jan 6, 2023
This utility lets you draw using your laptop's touchpad on Linux.

FingerPaint This utility lets you draw using your laptop's touchpad on Linux. Pressing any key or clicking the touchpad will finish the drawing

Wazzaps 95 Dec 17, 2022