Skip to content

dmunch/biscuit-net

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

biscuit-net

biscuit-net is an implementation of Biscuit in .NET/C#. It aims to be fully compatible with other existing implementations, so that tokens issued by, for example, the Rust version, could be validated by this library and vice versa.

Documentation and specifications

Biscuit introduction

Taken from biscuit-rust:

Biscuit is an authorization token for microservices architectures with the following properties:

  • decentralized validation: any node could validate the token only with public information;
  • offline delegation: a new, valid token can be created from another one by attenuating its rights, by its holder, without communicating with anyone;
  • capabilities based: authorization in microservices should be tied to rights related to the request, instead of relying to an identity that might not make sense to the verifier;
  • flexible rights managements: the token uses a logic language to specify attenuation and add bounds on ambient data;
  • small enough to fit anywhere (cookies, etc).

Non goals:

  • This is not a new authentication protocol. Biscuit tokens can be used as opaque tokens delivered by other systems such as OAuth.
  • Revocation: while tokens come with expiration dates, revocation requires external state management.

Basic Usage

These usage samples and additinal ones, as well es required usings can be found in BiscuitBuilderTests.cs.

Create a biscuit

var rootKey = Ed25519.NewSigningKey();
var token = Biscuit.New(rootKey)
    .AuthorityBlock("""
        right("/a/file1.txt", "read");
        right("/a/file1.txt", "write");
        right("/a/file2.txt", "read");
        right("/a/file2.txt", "write");
    """)
    .EndBlock()
.Serialize();

// token is now a byte[], ready to be shared

Attenuate a biscuit

var attenuatedToken = Biscuit.Attenuate(token)
    .AddBlock()
        .Add("""check if resource("file4")""")
        .Add("""check if resource("file5")""")
    .EndBlock()
    .Serialize();
// attenuatedToken is now a byte[] attenuation of the original token, and ready to be shared

Verify a biscuit

var verificationKey = new Ed25519.VerificationKey(rootKey.Public);
if(!Biscuit.TryDeserialize(token, verificationKey, out var biscuit, out var formatErr))
{
    throw new Exception($"Couldn't deserialize/validate biscuit: {formatErr}");
}

if(!Parser.Authorizer("""resource("file5"); allow if true;""").TryAuthorize(biscuit, out err))
{
    throw new Exception($"Couldn't authorize biscuit: {err}");
}

Seal a biscuit

Sealing a biscuit means it can no longer be attenuated.

var rootKey = Ed25519.NewSigningKey();        
var token = Biscuit.New(rootKey)
    .AuthorityBlock()
        .Add("resource", "file4")
    .EndBlock()
    .Seal();


//this will throw an exception
Biscuit.Attenuate(token);

Third-party blocks

You can learn more about third-party blocks in the relevant section of the Biscuit specifiction, or in this blog Post.

Adding a third-party block

var rootKey = Ed25519.NewSigningKey();
var thirdPartyKey = Ed25519.NewSigningKey();

var verificationKey = new Ed25519.VerificationKey(rootKey.Public);        
        
var token1 = Biscuit.New(rootKey)
    .AuthorityBlock()
        .Add("resource", "file4")
    .EndBlock()
    .Serialize();

var token2 = Biscuit.Attenuate(token1)
    .AddThirdPartyBlock(request => 
        //the request would usually be send to a third-party over the wire
        //the third party processes the requests, builds a third-party block, signs
        //it, it sends it back.
        //for the sake of the example, everything here happens in-process
        Biscuit.NewThirdParty()
            .Add("""check if resource("file4")""")
            .Add("""check if resource("file5")""")
        .Sign(thirdPartyKey, request)
    )
    .Serialize();
// token2 is now a byte[], containing the third party block

Trusting a third-party block issuer

var rootKey = Ed25519.NewSigningKey();
var thirdPartyKey = Ed25519.NewSigningKey();

var token1 = Biscuit.New(rootKey)
    .AuthorityBlock()
        .Add("resource", "file4")                
    .EndBlock()
    .AddBlock()
        //this block trusts any blocks signed by the thirdPartyKey
        //even if these blocks have been appended only later-on 
        .Trusts(thirdPartyKey.Public)
        .Add("""check if resource("file5");""")
    .EndBlock()
    .AddBlock()                
        .AddCheck(Check.CheckKind.One)
            .AddRule("""resource("file5")""")
                //while the overall block only trusts authority and the authorizer
                //this rule also trusts blocks signed by the thirdPartyKey
                .Trusts(thirdPartyKey.Public)
            .EndRule()
        .EndCheck()
    .EndBlock()
    .Serialize();

Project Structure

The most interesting bits of this implementation are:

Test coverage

The implementation currently passes all the tests of the conformance test suite published as part of the Biscuit specification.

There are also additional unit-like tests, however those could be a more complete. Feel free to chip in.

NuGet Prereleases

NuGet preleases are currently available on Github Packages. To consume them, your project needs to configure an additional NuGet source like follows.

# create a new project-specif nuget.config, if you don't already have one
dotnet new nugetconfig  
# add the GitHub nuget source 
dotnet nuget add source https://nuget.pkg.github.com/dmunch/index.json   
# install biscuit_net.Parser, which pulls in the other packages as dependies and is required for the examples to work 
dotnet add package biscuit_net.Parser

A word of caution: hic sunt dracones

This code should NOT be used in production scenarios before further scrutinizing and reviewing happend. You have been warned.

License

Licensed under Apache License, Version 2.0.

Releases

No releases published

Packages 3

 
 
 

Languages