new cryptographic scheme

This commit is contained in:
Geoffroy Couprie 2021-09-02 23:04:46 +02:00
parent 7bd158a3ed
commit d91e055156
24 changed files with 1868 additions and 492 deletions

472
DESIGN.md
View File

@ -440,418 +440,124 @@ It reuses the token's symbol table. If new symbols must be added to the
table when adding facts and rules, the new block will only hold the new
symbols.
When serializing the new token, the new block must first be serialized
to a byte array via Protobuf encoding. Then a new aggregated signature is created
from the previous blocks, the previous aggregated signature and the
new key pair for this block. The new serialized token will have the same
authority block as the previous one, its blocks field will have the previous
one's blocks with the new block appended, and the new signature.
to a byte array via Protobuf encoding. Then a new signature is created
from the previous blocks, and the next key pair is generated. The new
serialized token will have the same authority block as the previous one,
its blocks field will have the previous one's blocks with the new block
appended, and the new signature.
## Cryptography
This design requires a non interactive signature aggregation scheme.
We have multiple propositions, described in annex to the document.
We have not chosen yet which scheme will be used. The choice will
depend on the speed on the algorithm (for signature, aggregation and
verification), the size of the keys and signatures, and pending
an audit.
The system needs to be non interactive, so that delegation can
This design requires a signature scheme that can be extended without
interaction with the origin token creator, so that delegation can
be done "offline", without talking to the initial authorization
system, or any of the other participants in the delegation chain.
A signature aggregation scheme, can take a list of tuples
(message, signature, public key), and produce one signature
that can be verified with the list of messages and public keys.
An additional important property we need here: we cannot get
the original signatures from an aggregated one.
### Biscuit signature scheme
Assuming we have the following primitives:
Biscuit tokens are based on public key cryptography, with a chain of Ed25519
signatures. Each block contains the serialized Datalog, the next public key,
and the signature by the previous key. The token also contains the private key
corresponding to the last public key, to sign a new block and attenuate the
token, or a signature of the last block by the last private key, to seal the
token.
- `Keygen()` can give use a publick key `pk` and a private key `sk`
- `Sign(sk, message)` can give us a signature `S`, with `message` a byte array or arbitrary length
- `Aggregate(S1, S2)` can give us an aggregated signature `S`. Additionally, `Aggregate`
can be called with an aggregated signature `S` and a single signature `S'`, and return a new
aggregated signature `S"`
- `Verify([message], [pk], S)` will return true if the signature `S`
is valid for the list of messages `[message]` and the list of public keys `[pk]`
#### Signature (one block)
#### First layer of the authorization token
* `(pk_0, sk_0)` the root public and private Ed25519 keys
* `data_0` the serialized Datalog
* `(pk_1, sk_1)` the next key pair, generated at random
* `sig_0 = sign(sk_0, data_0 + pk_1)`
The issuing server performs the following steps:
- `(pk1, sk1) <- Keygen()` (done once)
- create the first block (we can omit `pk1` from that block, since we assume the
token will be verified on a system that knows that public key)
- Serialize that first block to `m1`
- `S <- Sign(sk1, m1)`
- `token1 <- m1||S`
#### Adding a block to the token
The holder of a token can attenuate it by adding a new block and
signing it, with the following steps:
- With `token1` containing `[messages]||S`, and a way to get
the list of public keys `[pk]` for each block from the blocks, or
from the environment
- `(pk2, sk2) <- Keygen()`
- With `message2` the block we want to add (containing `pk2`, so it
can be found in further verifications)`
- `S2 <- Sign(sk2, message2)`
- `S' <- Aggregate(S, S2)`
- `token2 <- [messages]||message2||S'`
Note: the block can contain `sealed: true` in its keys and values, to
indicate a token should not be attenuated further.
Question: should the previous signature be verified before adding the
new block?
#### Verifying the token
- With `token` containing `[messages]||S`
- extract `[pk]` from `[messages]` and the environment: the first public
key should already be known, and for performance reasons, some public keys
could also be present in the list of common keys and values
- `b <- Verify([messages], [pk], S)`
- if `b` is true, the signature is valid
- proceed to validating rights
### Sealed Biscuit scheme
In some cases, we might want to convert the token to a symmetric key based
token that cannot be attenuated further. Common use case: contact the verifier
once, the verifier checks the signature, and generates from it a short lived
token with the same authorization, but that can be checked much faster than
public key based tokens.
TODO: specify an AEAD scheme that would be usable for this
## Annex 1: Cryptographic design proposals
### Pairing based cryptography
proposed by @geal
Assuming we have a pairing e: G1 x G2 -> Gt with G1 and G2 two additive cyclic groups of prime order q, Gt a multiplicative cyclic group of order q
with a, b from Fq* finite field of order q
with P from G1, Q from G2
We have the following properties:
- `e(aP, bQ) == e(P, Q)^(ab)`
- `e != 1`
More specifically:
- `e(aP, Q) == e(P, aQ) == e(P,Q)^a`
- `e(P1 + P2, Q) == e(P1, Q) * e(P2, Q)`
#### Signature
- choose k from Fq* as private key, g2 a generator of G2
- public key P = k*g2
- Signature S = k*H1(message) with H1 function to hash message to G1
- Verifying: knowing message, P and S
```
e(S, g2) == e( k*H1(message), g2)
== e( H1(message), k*g2)
== e( H1(message), P)
```
#### Signature aggregation
- knowing messages m1 and m2, public keys P1 and P2
- signatures S1 = Sign(k1, m1), S2 = Sign(k2, m2)
- the aggregated signature S = S1 + S2
Verifying:
```
e(S, g2) == e(S1+S2, g2)
== e(S1, g2)*e(S2, g2)
== e(k1*H1(m1), g2) * e(k2*HA(m2), g2)
== e(H1(m1), k1*g2) * e(H1(m2), k2*g2)
== e(H1(m1), P1) * e(H1(m2), P2)
```
so we calculate signature verification pairing for every caveat
then we multiply the result and check equality
we use curve BLS12-381 (Boneh Lynn Shacham) for security reasons
(cf https://github.com/zcash/zcash/issues/2502
for comparions with Barreto Naehrig curves)
assumes computational Diffe Hellman is hard
Performance is not stellar (with the pairing crate, we can
spend 30ms verifying a token with 3 blocks, with mcl 1.7ms).
Example of library this can be implemented with:
- pairing crate: https://github.com/zkcrypto/pairing
- mcl: https://github.com/herumi/mcl
### Elliptic curve verifiable random functions
proposed by @KellerFuchs
https://tools.ietf.org/html/draft-irtf-cfrg-vrf-04
Using the primitives defined in https://tools.ietf.org/html/draft-irtf-cfrg-vrf-04#section-5 :
The token will contain:
```
F - finite field
2n - length, in octets, of a field element in F
E - elliptic curve (EC) defined over F
m - length, in octets, of an EC point encoded as an octet string
G - subgroup of E of large prime order
q - prime order of group G
cofactor - number of points on E divided by q
g - generator of group G
Hash - cryptographic hash function
hLen - output length in octets of Hash
```
Token {
root_key_id: <optional number indicating the root key to use for verification>
authority: Block {
data_0,
pk_1,
sig_0,
}
blocks: [],
proof: Proof {
nextSecret: sk_1,
},
}```
Constraints on options:
#### Signature (appending)
Field elements in F have bit lengths divisible by 16
With a token containing blocks 0 to n:
hLen is equal to 2n
Block n contains:
- `data_n`
- `pk_n+1`
- `sig_n`
Steps:
The token also contains `sk_n+1`
Keygen:
`(pk, sk) <- Keygen()`: sk random x with 0 < x < q
We generate at random `(pk_n+2, sk_n+2)` and the signature `sig_n+1 = sign(sk_n+1, data_n+1 + pk_n+2)`
#### Basic EC-VRF behaviour
Sign(pk, sk, message):
creating a proof pi = ECVRF_prove(pk, sk, message):
- h = ECVRF_hash_to_curve(pk, message)
- gamma = h^sk
- k = ECVRF_nonce(sk, h)
- c = ECVRF_hash_points(h, gamma, g^k, h^k)
- s = k + c * sk mod q
- pi = (gamma, c, s)
Verify(pk, pi, message) for one message and its signature:
- (gamma, c, s) = pi
```
u = pk^-c * g^s
= g^(sk*-c)*g^(k + c*sk)
= g^k
```
- h = ECVRF_hash_to_curve(pk, message)
```
v = gamma^-c * h^s
= h^(sk*-c)*h^(k + c*sk)
= h^k
```
- c' = ECVRF_hash_points(h, gamma, u, v)
- return c == c'
#### Aggregating signatures
Sign:
First block: Sign0(pk, sk, message)
- `h = ECVRF_hash_to_curve(pk, message)`
- `gamma = h^sk`
- `k = ECVRF_nonce(sk, h)`
- `c = ECVRF_hash_points(h, gamma, g^k, h^k)`
- `s = k + c * sk mod q`
- `W = 1`
- `S = s`
- `PI_0 = ([gamma], [c], S, W)`
Block n+1: Sign( pk_(n+1), sk_(n+1), message_(n+1), PI_n):
- `([gamma_i], [c_i], S_n, W_n) = PI_n`
- `h_(n+1) = ECVRF_hash_to_curve(pk_(n+1), message_(n+1))`
- `gamma_(n+1) = h_(n+1)^sk_(n+1)`
- `k = ECVRF_nonce(sk, h)`
```
u_n = pk_0^-c_0 * .. * pk_n^-c_n * g^S_n
= g^(sk_0*-c_0) * .. * g^(sk_n*-c_n) * g^(k_0 + sk0*c_0 + .. + k_n + sk_n*c_n)
= g^(k_0 + .. + k_n)
v_n = W* gamma_0^-c_0 * h_0^S * .. * gamma_n^-c_n * h_n^S
= h_0^(s_0 - S) * .. * h_n^(s_0 - S) * h_0^(sk_0*-c_0 + S) * .. * h_n^(sk_n*-c_n + S)
= h_0^(k_0 + sk_0*c_0 - S - sk_0*c_0 + S) * .. * h_n^(k_n + sk_n*c_n - S - sk_n*c_n + S)
= h_0^k_0 * .. * h_n^k_n
```
The token will contain:
```
c_(n+1) = ECVRF_hash_points(g, h_(n+1), pk_0 * .. * pk_(n+1) ,
gamma_0 * .. * gamma_(n+1), u_n * g^k_(n+1), v_n * h_(n+1)^k_(n+1))
```
- `s_(n+1) = k_(n+1) + c_(n+1) * sk_(n+1) mod q`
- `S_(n+1) = S_n + s_(n+1)`
- `W_(n+1) = W_n * (h_0 * .. * h_n)^(-s_(n+1)) * h_(n+1)^(-Sn) == h_0^(s_0 - S_(n+1)) * .. * h_(n+1)^(s_(n+1) - S_(n+1))`
- `PI_(n+1) = ([gamma_i], [c_i], S_(n+1), W_(n+1))`
Token {
root_key_id: <optional number indicating the root key to use for verification>
authority: Block_0,
blocks: [Block_1, .., Block_n,
Block_n+1 {
data_n+1,
pk_n+2,
sig_n+1,
}]
proof: Proof {
nextSecret: sk_n+2,
},
}```
Verify([pk], PI, [message]) (with n blocks):
#### Verifying
For each block i from 0 to n:
Aggregate(pk', pi', [pk], PI) with [pk] list of public keys and PI aggregated signature:
- `([gamma], [c], S, W, C) = PI`
- check that `n = |[pk]| == |[message]| == |[gamma]| == |[c]|`
```
U = pk_0^-c_0 * .. * pk_n^-c_n * g^S
= g^(sk_0*-c_0) * .. * g^(sk_n*-c_n) * g^(k_0 + sk0*c_0 + .. + k_n + sk_n*c_n)
= g^(k_0 + .. + k_n)
```
- verify(pk_i, sig_i, data_i+pk_i+1)
If all signatures are verified, extract pk_n+1 from the last block and
sk_n+1 from the proof field, and check that they are from the same
key pair.
#### Signature (appending)
With a token containing blocks 0 to n:
Block n contains:
- `data_n`
- `pk_n+1`
- `sig_n`
The token also contains `sk_n+1`
We generate the signature `sig_n+1 = sign(sk_n+1, data_n + pk_n+1 + sig_n)` (we sign
the last block with the last private key).
The token will contain:
```
V = W* gamma_0^-c_0 * h_0^S * .. * gamma_n^-c_n * h_n^S
= h_0^(s_0 - S) * .. * h_n^(s_0 - S) * h_0^(sk_0*-c_0 + S) * .. * h_n^(sk_n*-c_n + S)
= h_0^(k_0 + sk_0*c_0 - S - sk_0*c_0 + S) * .. * h_n^(k_n + sk_n*c_n - S - sk_n*c_n + S)
= h_0^k_0 * .. * h_n^k_n
```
- `C = ECVRF_hash_points(h_n, gamma_0 * .. * gamma_n, U, V)`
- verify that `C == c_n`
### Elliptic curve verifiable random functions: second method
This is a variant of the previous scheme, for which the product of
gamma points is precalculated, so that we do not need to do it to
aggregate a new signature or verify it. This also reduces the size
of the signature.
Same primitives as before:
```
F - finite field
2n - length, in octets, of a field element in F
E - elliptic curve (EC) defined over F
m - length, in octets, of an EC point encoded as an octet string
G - subgroup of E of large prime order
q - prime order of group G
cofactor - number of points on E divided by q
g - generator of group G
Hash - cryptographic hash function
hLen - output length in octets of Hash
Token {
root_key_id: <optional number indicating the root key to use for verification>
authority: Block_0,
blocks: [Block_1, .., Block_n]
proof: Proof {
finalSignature: sig_n+1
},
}
```
Constraints on options:
#### Verifying (sealed)
Field elements in F have bit lengths divisible by 16
For each block i from 0 to n:
hLen is equal to 2n
- verify(pk_i, sig_i, data_i+pk_i+1)
Steps:
Keygen:
`(pk, sk) <- Keygen()`: sk random x with 0 < x < q
#### Aggregating signatures
Sign:
First block: Sign0(pk, sk, message)
- `h = ECVRF_hash_to_curve(pk, message)`
- `gamma = h^sk`
- `k = ECVRF_nonce(sk, h)`
- `c = ECVRF_hash_points(h, pk, g^k, h^k)`
- `s = k + c * sk mod q`
- `W = 1`
- `S = s`
- `PI_0 = (-c * gamma, [c], S, W)`
Block n+1: Sign( pk_(n+1), sk_(n+1), message_(n+1), PI_n):
- `(gamma_agg, [c_i], S_n, W_n) = PI_n`
- `h_(n+1) = ECVRF_hash_to_curve(pk_(n+1), message_(n+1))`
- `gamma_(n+1) = h_(n+1)^sk_(n+1)`
- `k = ECVRF_nonce(sk, h)`
```
u_n = pk_0^-c_0 * .. * pk_n^-c_n * g^S
= g^(sk_0*-c_0) * .. * g^(sk_n*-c_n) * g^(k_0 + sk0*c_0 + .. + k_n + sk_n*c_n)
= g^(k_0 + .. + k_n)
```
```
v_n = W * gamma_agg * h_0^S * ... * h_n^S
= W * gamma_0^-c_0 * h_0^S * .. * gamma_n^-c_n * h_n^S
= h_0^(s_0 - S) * .. * h_n^(s_0 - S) * h_0^(sk_0*-c_0 + S) * .. * h_n^(sk_n*-c_n + S)
= h_0^(k_0 + sk_0*c_0 - S - sk_0*c_0 + S) * .. * h_n^(k_n + sk_n*c_n - S - sk_n*c_n + S)
= h_0^k_0 * .. * h_n^k_n
```
```
c_(n+1) = ECVRF_hash_points(g, h_(n+1), pk_0 * .. * pk_(n+1) ,
u_n * g^k, v_n * h^k)
```
- `s_(n+1) = k_(n+1) - c_(n+1) * sk_(n+1) mod q`
- `S_(n+1) = S_n + s_(n+1)`
- `W_(n+1) = W_n * (h_0 * .. * h_n)^(-s_(n+1)) * h_(n+1)^(-Sn) == h_0^(s_0 - S_(n+1)) * .. * h_(n+1)^(s_(n+1) - S_(n+1))`
- `PI_(n+1) = (gamma_agg * (-c_(n+1) * gamma_(n+1)), [c_i], S_(n+1), W_(n+1))`
Verify([pk], PI, [message]) (with n blocks):
Aggregate(pk', pi', [pk], PI) with [pk] list of public keys and PI aggregated signature:
- `([gamma], [c], S, W, C) = PI`
- check that `n = |[pk]| == |[message]| == |[gamma]| == |[c]|`
```
u = pk_0^-c_0 * .. * pk_n^-c_n * g^S
= g^(sk_0*-c_0) * .. * g^(sk_n*-c_n) * g^(k_0 + sk0*c_0 + .. + k_n + sk_n*c_n)
= g^(k_0 + .. + k_n)
```
```
v = W * gamma_agg * h_0^S * ... * h_n^S
= W * gamma_0^-c_0 * h_0^S * .. * gamma_n^-c_n * h_n^S
= h_0^(s_0 - S) * .. * h_n^(s_0 - S) * h_0^(sk_0*-c_0 + S) * .. * h_n^(sk_n*-c_n + S)
= h_0^(k_0 + sk_0*c_0 - S - sk_0*c_0 + S) * .. * h_n^(k_n + sk_n*c_n - S - sk_n*c_n + S)
= h_0^k_0 * .. * h_n^k_n
```
- `C = ECVRF_hash_points(h_n, pk_0 * ... pk_n, U, V)`
- verify that `C == c_n`
### Challenge tokens
Another method based on a more classical PKI, where the token contains
the secret key of the last caveat. To send the token for verification,
that key is used to sign the token with a nonce and current time, to
prove that we own it. We send the token without the key, but with the
signature. The verification token cannot be further attenuated.
Here's a description of the scheme:
```
(pk1, sk1) = keygen()
(pk2, sk2) = keygen()
s1 = sign(sk1, caveat1+pk2)
token1=caveat1+pk2+s1+sk2
```
Minting a new token
```
(pk3, sk3) = keygen()
s2 = sign(sk2, caveat2+pk3)
token2=caveat1+pk2+s1+caveat2+pk3+s2+sk3
```
Sending token2 for verification:
```
verif_token2=caveat1+pk2+s1+caveat2+pk3+s2
h = sign(sk3, nonce+time+verif_token2)
sending verif_token2+h
```
The verifier knows pk1 and can check the chain, and h allows checking that we hold sk3
### Gamma signatures
proposed by @tarcieri
Yao, A. C.-C., & Yunlei Zhao. (2013). Online/Offline Signatures for Low-Power Devices. IEEE Transactions on Information Forensics and Security, 8(2), 283294.
Aggregation of Gamma-Signatures and Applications to Bitcoin, Yunlei Zhao https://eprint.iacr.org/2018/414.pdf
### BIP32 derived keys
proposed by @tarcieri
https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
If all signatures are verified, extract pk_n+1 from the last block and
sig from the proof field, and check `verify(pk_n+1, sig_n+1, data_n+pk_n+1+sig_n)`

View File

@ -437,43 +437,37 @@ transmitted over the wire is either the normal Biscuit wrapper:
```proto
message Biscuit {
required bytes authority = 1;
repeated bytes blocks = 2;
repeated bytes keys = 3;
required Signature signature = 4;
optional uint32 rootKeyId = 1;
required SignedBlock authority = 2;
repeated SignedBlock blocks = 3;
required Proof proof = 4;
}
message Signature {
repeated bytes parameters = 1;
required bytes z = 2;
}
```
The `keys` and `parameters` arrays contain Ristretto points in their
canonical representation, serialized to a 32 bytes array[CompressedRistretto].
Thee `z` field is a 32 bytes array containing the canonical representation
of an element of Ristretto's scalar field[Scalar].
The `keys` field contains the public keys used to sign each block. It contains
as many elements as the `blocks` field plus one. The first element is the root key.
The `parameters` field must have as many elements as the `keys` field. All of
their elements must be distinct.
or the "sealed" Biscuit wrapper (a token that cannot be attenuated offline):
```proto
message SealedBiscuit {
required bytes authority = 1;
repeated bytes blocks = 2;
message SignedBlock {
required bytes block = 1;
required bytes nextKey = 2;
required bytes signature = 3;
}
message Proof {
oneof Content {
bytes nextSecret = 1;
bytes finalSignature = 2;
}
}
```
The signature part of those tokens covers the content of authority and
blocks members.
The `rootKeyId` is a hint to decide which root public key should be used
for signature verification.
Each block contains a serialized byte array of the Datalog data (`block`),
the next public key (`nextKey`) and the signature of that block and key
by the previous key.
Those members are byte arrays, containing `Block` structures serialized
The `proof` field contains either the private key corresponding to the
public key in the last block (attenuable tokens) or a signature of the last
block by the private key (sealed tokens).
The `block` field is a byte array, containing a `Block` structure serialized
in Protobuf format as well:
```proto
@ -528,92 +522,111 @@ or `sealed-biscuit:` accordingly.
### Cryptography
#### Attenuable tokens
Biscuit tokens are based on public key cryptography, with a chain of Ed25519
signatures. Each block contains the serialized Datalog, the next public key,
and the signature by the previous key. The token also contains the private key
corresponding to the last public key, to sign a new block and attenuate the
token, or a signature of the last block by the last private key, to seal the
token.
Those tokens are based on public key cryptography, specifically aggregated
gamma signatures[Aggregated Gamma Signatures]. Signature aggregation allows
Biscuit to make a new token with a valid signature from an existing one,
by signing the new data and adding the new signature to the old one.
#### Signature (one block)
Every public key operation in Biscuit is defined over the Ristretto prime
order group[Ristretto], that is designed to prevent some implementation
mistakes.
* `(pk_0, sk_0)` the root public and private Ed25519 keys
* `data_0` the serialized Datalog
* `(pk_1, sk_1)` the next key pair, generated at random
* `sig_0 = sign(sk_0, data_0 + pk_1)`
Definitions:
- `R`: Ristretto group
- `l`: order of the Ristretto group
- `Z/l`: scalar of order `l` associated to the Ristretto group
- `P`: Ristretto base point
- `H1`: point hashing function
- `H2`: message hashing function
The token will contain:
##### Key generation
Private key:
`x <- Z/l*` chosen at random
Public key:
`X = sk * P`
##### Signature (one block)
With secret key `x`, public key `X`, message `message`:
* `r <- Z/l*` chosen at random
* `A = r * P`
* `d = H1(A)`
* `e = H2(X, message)`
* `z = rd - ex mod l`
The signature is `([A], z)`. The `[A]` array corresponds to the `parameters`
field in the protobuf schema.
```
Token {
root_key_id: <optional number indicating the root key to use for verification>
authority: Block {
data_0,
pk_1,
sig_0,
}
blocks: [],
proof: Proof {
nextSecret: sk_1,
},
}```
#### Signature (appending)
With `([A0, ..., An], s)` the current signature:
Same process as the signature for a single block,
with secret key `x`, public key `X`, message `message`:
With a token containing blocks 0 to n:
* `r <- Z/l*` chosen at random
* `A = r * P`
* `d = H1(A)`
* `e = H2(X, message)`
* `z = rd - ex mod l`
Block n contains:
- `data_n`
- `pk_n+1`
- `sig_n`
The new signature is `([A0, ..., An, A], s + z)`
The token also contains `sk_n+1`
We generate at random `(pk_n+2, sk_n+2)` and the signature `sig_n+1 = sign(sk_n+1, data_n+1 + pk_n+2)`
The token will contain:
```
Token {
root_key_id: <optional number indicating the root key to use for verification>
authority: Block_0,
blocks: [Block_1, .., Block_n,
Block_n+1 {
data_n+1,
pk_n+2,
sig_n+1,
}]
proof: Proof {
nextSecret: sk_n+2,
},
}```
#### Verifying
With:
For each block i from 0 to n:
* `([A0, ..., An], s)` the current signature
* `[P0, ..., Pn]` the list of public keys
* `[m0, ..., mn]` the list of messages
- verify(pk_i, sig_i, data_i+pk_i+1)
We verify as follows:
* check that `|[A0, ..., An]| == |[P0, ..., Pn]| == |[m0, ..., mn]|`
* check that `P0` is the root public key we are expecting
* check that `[A0, ..., An]` are distinct
* check that `[(P0, m0), ..., (Pn, mn)]` are distinct
* `X = H2(P0, m0) * P0 + ... + H2(Pn, mn) * Pn - ( H1(A0) * A0 + ... + H1(An) * An )`
* if `s * P + X` is the point at infinite, the signature is verified
If all signatures are verified, extract pk_n+1 from the last block and
sk_n+1 from the proof field, and check that they are from the same
key pair.
##### Point hashing
#### Signature (appending)
`H1(X) = Scalar::from_hash(sha512(X.compress().to_bytes()))`
With a token containing blocks 0 to n:
##### Message hashing
Block n contains:
- `data_n`
- `pk_n+1`
- `sig_n`
`H2(X, message) = Scalar::from_hash(sha512(X.compress().to_bytes()|message))` (with `|` the concatenation operator)
The token also contains `sk_n+1`
#### Sealed tokens
We generate the signature `sig_n+1 = sign(sk_n+1, data_n + pk_n+1 + sig_n)` (we sign
the last block with the last private key).
A sealed token contains the same kind of block as regular tokens,
but it cannot be attenuated offline, and can only be verified by
knowing the secret used to create it.
The token will contain:
The signature is the HMAC-SHA256 hash of the secret key and the
concatenation of all the blocks.
```
Token {
root_key_id: <optional number indicating the root key to use for verification>
authority: Block_0,
blocks: [Block_1, .., Block_n]
proof: Proof {
finalSignature: sig_n+1
},
}
```
#### Verifying (sealed)
For each block i from 0 to n:
- verify(pk_i, sig_i, data_i+pk_i+1)
If all signatures are verified, extract pk_n+1 from the last block and
sig from the proof field, and check `verify(pk_n+1, sig_n+1, data_n+pk_n+1+sig_n)`
### Blocks
@ -685,8 +698,4 @@ We provide sample tokens and the expected result of their verification at
- DATALOG: "Datalog with Constraints: A Foundation for Trust Management Languages" http://crypto.stanford.edu/~ninghui/papers/cdatalog_padl03.pdf
- Trust Management Languages" https://www.cs.purdue.edu/homes/ninghui/papers/cdatalog_padl03.pdf
- MACAROONS: "Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud" https://ai.google/research/pubs/pub41892
- Aggregated Gamma Signatures: "Aggregation of Gamma-Signatures and Applications to Bitcoin, Yunlei Zhao" https://eprint.iacr.org/2018/414.pdf
- Ristretto: "Ristretto: prime order elliptic curve groups with non-malleable encodings" https://ristretto.group
- Scalar: https://doc.dalek.rs/curve25519_dalek/scalar/struct.Scalar.html
- CompressedRistretto: https://doc.dalek.rs/curve25519_dalek/ristretto/struct.CompressedRistretto.html

1048
samples/v2/README.md Normal file

File diff suppressed because it is too large Load Diff

611
samples/v2/samples.json Normal file
View File

@ -0,0 +1,611 @@
{
"root_private_key": "12aca40167fbdd1a11037e9fd440e3d510d9d9dea70a6646aa4aaf84d718d75a",
"root_public_key": "acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189",
"testcases": [
{
"title": "basic token",
"filename": "test1_basic.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"write\", \"check1\", \"0\"]\n authority: Block[0] {\n symbols: [\"read\", \"write\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read),\n right(#authority, \"file2\", #read),\n right(#authority, \"file1\", #write)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)\n ]\n }\n ]\n}"
},
"validations": {
"": [
{
"facts": [
"resource(#ambient, \"file1\")",
"revocation_id(0, hex:415b9d4bcfbcc052eb30b66bed5151a7291bd3ededa8679140753f97d9a0b3e6)",
"revocation_id(1, hex:057ef57833aac9fb405ba1abadca1b088f2557700ea2004c79004ea688abeb47)",
"right(#authority, \"file1\", #read)",
"right(#authority, \"file1\", #write)",
"right(#authority, \"file2\", #read)",
"unique_revocation_id(0, hex:415b9d4bcfbcc052eb30b66bed5151a7291bd3ededa8679140753f97d9a0b3e6)",
"unique_revocation_id(1, hex:057ef57833aac9fb405ba1abadca1b088f2557700ea2004c79004ea688abeb47)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)"
],
"policies": [
"allow if true"
]
},
{
"Err": [
"Block(FailedBlockCheck { block_id: 1, check_id: 0, rule: \"check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)\" })"
]
}
]
}
},
{
"title": "different root key",
"filename": "test2_different_root_key.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"check1\", \"0\"]\n authority: Block[0] {\n symbols: [\"read\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)\n ]\n }\n ]\n}"
},
"validations": {
"": [
null,
{
"Err": [
"Format(Signature(InvalidSignature(\"signature error\")))"
]
}
]
}
},
{
"title": "invalid signature format",
"filename": "test3_invalid_signature_format.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"write\", \"check1\", \"0\"]\n authority: Block[0] {\n symbols: [\"read\", \"write\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read),\n right(#authority, \"file2\", #read),\n right(#authority, \"file1\", #write)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)\n ]\n }\n ]\n}"
},
"validations": {
"": [
null,
{
"Err": [
"Format(InvalidSignatureSize(16))"
]
}
]
}
},
{
"title": "random block",
"filename": "test4_random_block.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"write\", \"check1\", \"0\"]\n authority: Block[0] {\n symbols: [\"read\", \"write\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read),\n right(#authority, \"file2\", #read),\n right(#authority, \"file1\", #write)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)\n ]\n }\n ]\n}"
},
"validations": {
"": [
null,
{
"Err": [
"Format(Signature(InvalidSignature(\"signature error\")))"
]
}
]
}
},
{
"title": "invalid signature",
"filename": "test5_invalid_signature.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"write\", \"check1\", \"0\"]\n authority: Block[0] {\n symbols: [\"read\", \"write\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read),\n right(#authority, \"file2\", #read),\n right(#authority, \"file1\", #write)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)\n ]\n }\n ]\n}"
},
"validations": {
"": [
null,
{
"Err": [
"Format(Signature(InvalidSignature(\"signature error\")))"
]
}
]
}
},
{
"title": "reordered blocks",
"filename": "test6_reordered_blocks.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"write\", \"check1\", \"0\"]\n authority: Block[0] {\n symbols: [\"read\", \"write\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read),\n right(#authority, \"file2\", #read),\n right(#authority, \"file1\", #write)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)\n ]\n }\n ]\n}",
"biscuit3 (2 checks)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"write\", \"check1\", \"0\", \"check2\"]\n authority: Block[0] {\n symbols: [\"read\", \"write\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read),\n right(#authority, \"file2\", #read),\n right(#authority, \"file1\", #write)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read)\n ]\n },\n\tBlock[2] {\n symbols: [\"check2\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, \"file1\")\n ]\n }\n ]\n}"
},
"validations": {
"": [
null,
{
"Err": [
"Format(Signature(InvalidSignature(\"signature error\")))"
]
}
]
}
},
{
"title": "invalid block fact with authority tag",
"filename": "test7_invalid_block_fact_authority.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"write\", \"check1\", \"0\"]\n authority: Block[0] {\n symbols: [\"read\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"write\", \"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #write)\n ]\n rules: []\n checks: [\n check if operation(#ambient, #read)\n ]\n }\n ]\n}"
},
"validations": {
"": [
null,
{
"Err": [
"FailedLogic(InvalidBlockFact(0, \"right(#authority, \\\"file1\\\", #write)\"))"
]
}
]
}
},
{
"title": "invalid block fact with ambient tag",
"filename": "test8_invalid_block_fact_ambient.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"write\", \"check1\", \"0\"]\n authority: Block[0] {\n symbols: [\"read\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"write\", \"check1\", \"0\"]\n version: 1\n context: \"\"\n facts: [\n right(#ambient, \"file1\", #write)\n ]\n rules: []\n checks: [\n check if operation(#ambient, #read)\n ]\n }\n ]\n}"
},
"validations": {
"": [
null,
{
"Err": [
"FailedLogic(InvalidBlockFact(0, \"right(#ambient, \\\"file1\\\", #write)\"))"
]
}
]
}
},
{
"title": "expired token",
"filename": "test9_expired_token.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"check1\", \"expiration\", \"date\", \"time\"]\n authority: Block[0] {\n symbols: []\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"expiration\", \"date\", \"time\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, \"file1\"),\n check if time(#ambient, $date), $date <= 2018-12-20T00:00:00+00:00\n ]\n }\n ]\n}"
},
"validations": {
"": [
{
"facts": [
"operation(#ambient, #read)",
"resource(#ambient, \"file1\")",
"revocation_id(0, hex:96123a8ee182336c4c63ad29f2b23549020da2a90841ac63ccec4c20413753b0)",
"revocation_id(1, hex:60e6f54cb7a20ee0859495abe176da0306dfe91b4ee270244dfecf954da340bb)",
"time(#ambient, SystemTime { tv_sec: 1608542592, tv_nsec: 0 })",
"unique_revocation_id(0, hex:96123a8ee182336c4c63ad29f2b23549020da2a90841ac63ccec4c20413753b0)",
"unique_revocation_id(1, hex:60e6f54cb7a20ee0859495abe176da0306dfe91b4ee270244dfecf954da340bb)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if resource(#ambient, \"file1\")",
"check if time(#ambient, $date), $date <= 2018-12-20T00:00:00+00:00"
],
"policies": [
"allow if true"
]
},
{
"Err": [
"Block(FailedBlockCheck { block_id: 1, check_id: 1, rule: \"check if time(#ambient, $date), $date <= 2018-12-20T00:00:00+00:00\" })"
]
}
]
}
},
{
"title": "authority rules",
"filename": "test10_authority_rules.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"1\", \"read\", \"owner\", \"0\", \"write\", \"check1\", \"check2\", \"alice\"]\n authority: Block[0] {\n symbols: [\"1\", \"read\", \"owner\", \"0\", \"write\"]\n version: 1\n context: \"\"\n facts: []\n rules: [\n right(#authority, $1, #read) <- resource(#ambient, $1), owner(#ambient, $0, $1),\n right(#authority, $1, #write) <- resource(#ambient, $1), owner(#ambient, $0, $1)\n ]\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"check1\", \"check2\", \"alice\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if right(#authority, $0, $1), resource(#ambient, $0), operation(#ambient, $1),\n check if resource(#ambient, $0), owner(#ambient, #alice, $0)\n ]\n }\n ]\n}"
},
"validations": {
"": [
{
"facts": [
"operation(#ambient, #read)",
"owner(#ambient, #alice, \"file1\")",
"resource(#ambient, \"file1\")",
"revocation_id(0, hex:7b1c49cfd08df0bca951d50aa6f5062db8e4decce6713974186abd050382ab67)",
"revocation_id(1, hex:c5fdfd4294c92dca9f14fa659c45c811828853bf913e71a5d18ef9eecd7a6cab)",
"right(#authority, \"file1\", #read)",
"right(#authority, \"file1\", #write)",
"unique_revocation_id(0, hex:7b1c49cfd08df0bca951d50aa6f5062db8e4decce6713974186abd050382ab67)",
"unique_revocation_id(1, hex:c5fdfd4294c92dca9f14fa659c45c811828853bf913e71a5d18ef9eecd7a6cab)"
],
"rules": [],
"privileged_rules": [
"right(#authority, $1, #read) <- resource(#ambient, $1), owner(#ambient, $0, $1)",
"right(#authority, $1, #write) <- resource(#ambient, $1), owner(#ambient, $0, $1)"
],
"checks": [
"check if resource(#ambient, $0), owner(#ambient, #alice, $0)",
"check if right(#authority, $0, $1), resource(#ambient, $0), operation(#ambient, $1)"
],
"policies": [
"allow if true"
]
},
{
"Ok": 0
}
]
}
},
{
"title": "verifier authority checks",
"filename": "test11_verifier_authority_caveats.bc",
"print_token": {
"biscuit": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\"]\n authority: Block[0] {\n symbols: [\"read\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read)\n ]\n rules: []\n checks: []\n }\n blocks: [\n \n ]\n}"
},
"validations": {
"": [
{
"facts": [
"operation(#ambient, #read)",
"resource(#ambient, \"file2\")",
"revocation_id(0, hex:f3db615323f48dc225b793ec494c30c1d4a800ec8299aa7558fe769803f1446b)",
"right(#authority, \"file1\", #read)",
"unique_revocation_id(0, hex:f3db615323f48dc225b793ec494c30c1d4a800ec8299aa7558fe769803f1446b)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if right(#authority, $0, $1), resource(#ambient, $0), operation(#ambient, $1)"
],
"policies": [
"allow if true"
]
},
{
"Err": [
"Verifier(FailedVerifierCheck { check_id: 0, rule: \"check if right(#authority, $0, $1), resource(#ambient, $0), operation(#ambient, $1)\" })"
]
}
]
}
},
{
"title": "authority checks",
"filename": "test12_authority_caveats.bc",
"print_token": {
"biscuit": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"check1\"]\n authority: Block[0] {\n symbols: [\"check1\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, \"file1\")\n ]\n }\n blocks: [\n \n ]\n}"
},
"validations": {
"file1": [
{
"facts": [
"operation(#ambient, #read)",
"resource(#ambient, \"file1\")",
"revocation_id(0, hex:a6d33a7c61185cc962a4100d17176b72a60e95490af7c3cccbd244f3cce02b85)",
"unique_revocation_id(0, hex:a6d33a7c61185cc962a4100d17176b72a60e95490af7c3cccbd244f3cce02b85)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if resource(#ambient, \"file1\")"
],
"policies": [
"allow if true"
]
},
{
"Ok": 0
}
],
"file2": [
{
"facts": [
"operation(#ambient, #read)",
"resource(#ambient, \"file2\")",
"revocation_id(0, hex:a6d33a7c61185cc962a4100d17176b72a60e95490af7c3cccbd244f3cce02b85)",
"unique_revocation_id(0, hex:a6d33a7c61185cc962a4100d17176b72a60e95490af7c3cccbd244f3cce02b85)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if resource(#ambient, \"file1\")"
],
"policies": [
"allow if true"
]
},
{
"Err": [
"Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: \"check if resource(#ambient, \\\"file1\\\")\" })"
]
}
]
}
},
{
"title": "block rules",
"filename": "test13_block_rules.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"read\", \"valid_date\", \"time\", \"0\", \"1\", \"check1\"]\n authority: Block[0] {\n symbols: [\"read\"]\n version: 1\n context: \"\"\n facts: [\n right(#authority, \"file1\", #read),\n right(#authority, \"file2\", #read)\n ]\n rules: []\n checks: []\n }\n blocks: [\n Block[1] {\n symbols: [\"valid_date\", \"time\", \"0\", \"1\", \"check1\"]\n version: 1\n context: \"\"\n facts: []\n rules: [\n valid_date(\"file1\") <- time(#ambient, $0), resource(#ambient, \"file1\"), $0 <= 2030-12-31T12:59:59+00:00,\n valid_date($1) <- time(#ambient, $0), resource(#ambient, $1), $0 <= 1999-12-31T12:59:59+00:00, ![\"file1\"].contains($1)\n ]\n checks: [\n check if valid_date($0), resource(#ambient, $0)\n ]\n }\n ]\n}"
},
"validations": {
"file1": [
{
"facts": [
"resource(#ambient, \"file1\")",
"revocation_id(0, hex:d0e882a6d2405213cc7a8f2ac3f0041fecbf535177b6a6b4a581b48783a9d19b)",
"revocation_id(1, hex:cab9e5395e49e41c53c3418796f73379a167d9c2d1504c99dac5e9bb06ec02cc)",
"right(#authority, \"file1\", #read)",
"right(#authority, \"file2\", #read)",
"time(#ambient, SystemTime { tv_sec: 1608542592, tv_nsec: 0 })",
"unique_revocation_id(0, hex:d0e882a6d2405213cc7a8f2ac3f0041fecbf535177b6a6b4a581b48783a9d19b)",
"unique_revocation_id(1, hex:cab9e5395e49e41c53c3418796f73379a167d9c2d1504c99dac5e9bb06ec02cc)",
"valid_date(\"file1\")"
],
"rules": [
"valid_date(\"file1\") <- time(#ambient, $0), resource(#ambient, \"file1\"), $0 <= 2030-12-31T12:59:59+00:00",
"valid_date($1) <- time(#ambient, $0), resource(#ambient, $1), $0 <= 1999-12-31T12:59:59+00:00, ![\"file1\"].contains($1)"
],
"privileged_rules": [],
"checks": [
"check if valid_date($0), resource(#ambient, $0)"
],
"policies": [
"allow if true"
]
},
{
"Ok": 0
}
],
"file2": [
{
"facts": [
"resource(#ambient, \"file2\")",
"revocation_id(0, hex:d0e882a6d2405213cc7a8f2ac3f0041fecbf535177b6a6b4a581b48783a9d19b)",
"revocation_id(1, hex:cab9e5395e49e41c53c3418796f73379a167d9c2d1504c99dac5e9bb06ec02cc)",
"right(#authority, \"file1\", #read)",
"right(#authority, \"file2\", #read)",
"time(#ambient, SystemTime { tv_sec: 1608542592, tv_nsec: 0 })",
"unique_revocation_id(0, hex:d0e882a6d2405213cc7a8f2ac3f0041fecbf535177b6a6b4a581b48783a9d19b)",
"unique_revocation_id(1, hex:cab9e5395e49e41c53c3418796f73379a167d9c2d1504c99dac5e9bb06ec02cc)"
],
"rules": [
"valid_date(\"file1\") <- time(#ambient, $0), resource(#ambient, \"file1\"), $0 <= 2030-12-31T12:59:59+00:00",
"valid_date($1) <- time(#ambient, $0), resource(#ambient, $1), $0 <= 1999-12-31T12:59:59+00:00, ![\"file1\"].contains($1)"
],
"privileged_rules": [],
"checks": [
"check if valid_date($0), resource(#ambient, $0)"
],
"policies": [
"allow if true"
]
},
{
"Err": [
"Block(FailedBlockCheck { block_id: 1, check_id: 0, rule: \"check if valid_date($0), resource(#ambient, $0)\" })"
]
}
]
}
},
{
"title": "regex_constraint",
"filename": "test14_regex_constraint.bc",
"print_token": {
"biscuit": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"resource_match\", \"0\"]\n authority: Block[0] {\n symbols: [\"resource_match\", \"0\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, $0), $0.matches(\"file[0-9]+.txt\")\n ]\n }\n blocks: [\n \n ]\n}"
},
"validations": {
"file1": [
{
"facts": [
"resource(#ambient, \"file1\")",
"revocation_id(0, hex:1da4cd4d7c60491948662acc237bb10599c6046e1ef09a867267b5e039a4d1b6)",
"unique_revocation_id(0, hex:1da4cd4d7c60491948662acc237bb10599c6046e1ef09a867267b5e039a4d1b6)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if resource(#ambient, $0), $0.matches(\"file[0-9]+.txt\")"
],
"policies": [
"allow if true"
]
},
{
"Err": [
"Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: \"check if resource(#ambient, $0), $0.matches(\\\"file[0-9]+.txt\\\")\" })"
]
}
],
"file123": [
{
"facts": [
"resource(#ambient, \"file123.txt\")",
"revocation_id(0, hex:1da4cd4d7c60491948662acc237bb10599c6046e1ef09a867267b5e039a4d1b6)",
"unique_revocation_id(0, hex:1da4cd4d7c60491948662acc237bb10599c6046e1ef09a867267b5e039a4d1b6)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if resource(#ambient, $0), $0.matches(\"file[0-9]+.txt\")"
],
"policies": [
"allow if true"
]
},
{
"Ok": 0
}
]
}
},
{
"title": "multi queries checks",
"filename": "test15_multi_queries_caveats.bc",
"print_token": {
"biscuit": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"must_be_present\"]\n authority: Block[0] {\n symbols: [\"must_be_present\"]\n version: 1\n context: \"\"\n facts: [\n must_be_present(#authority, \"hello\")\n ]\n rules: []\n checks: []\n }\n blocks: [\n \n ]\n}"
},
"validations": {
"": [
{
"facts": [
"must_be_present(#authority, \"hello\")",
"revocation_id(0, hex:128099942c46fc6a4f9a8f8f0cc5d8b70c4d55d834255ef6065b62c967eef50c)",
"unique_revocation_id(0, hex:128099942c46fc6a4f9a8f8f0cc5d8b70c4d55d834255ef6065b62c967eef50c)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if must_be_present(#authority, $0) or must_be_present($0)"
],
"policies": [
"allow if true"
]
},
{
"Ok": 0
}
]
}
},
{
"title": "check head name should be independent from fact names",
"filename": "test16_caveat_head_name.bc",
"print_token": {
"biscuit": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"check1\", \"test\", \"hello\"]\n authority: Block[0] {\n symbols: [\"check1\", \"test\", \"hello\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if resource(#ambient, #hello)\n ]\n }\n blocks: [\n Block[1] {\n symbols: []\n version: 1\n context: \"\"\n facts: [\n check1(#test)\n ]\n rules: []\n checks: []\n }\n ]\n}"
},
"validations": {
"": [
{
"facts": [
"check1(#test)",
"revocation_id(0, hex:08321b952cecd6cc7ca5d3493ae391e44fcf3d3d55e63aa7e8b098217b7736c3)",
"revocation_id(1, hex:e166c05f9ec0632fe286df76048a527a621d7ca08e2cd9f3995b4ee33b1e001c)",
"unique_revocation_id(0, hex:08321b952cecd6cc7ca5d3493ae391e44fcf3d3d55e63aa7e8b098217b7736c3)",
"unique_revocation_id(1, hex:e166c05f9ec0632fe286df76048a527a621d7ca08e2cd9f3995b4ee33b1e001c)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if resource(#ambient, #hello)"
],
"policies": [
"allow if true"
]
},
{
"Err": [
"Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: \"check if resource(#ambient, #hello)\" })"
]
}
]
}
},
{
"title": "test expression syntax and all available operations",
"filename": "test17_expressions.bc",
"print_token": {
"biscuit": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"query\", \"abc\", \"hello\", \"world\"]\n authority: Block[0] {\n symbols: [\"query\", \"abc\", \"hello\", \"world\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if true,\n check if !false,\n check if false or true,\n check if 1 < 2,\n check if 2 > 1,\n check if 1 <= 2,\n check if 1 <= 1,\n check if 2 >= 1,\n check if 2 >= 2,\n check if 3 == 3,\n check if 1 + 2 * 3 - 4 / 2 == 5,\n check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\"),\n check if \"aaabde\".matches(\"a*c?.e\"),\n check if \"abcD12\" == \"abcD12\",\n check if 2019-12-04T09:46:41+00:00 < 2020-12-04T09:46:41+00:00,\n check if 2020-12-04T09:46:41+00:00 > 2019-12-04T09:46:41+00:00,\n check if 2019-12-04T09:46:41+00:00 <= 2020-12-04T09:46:41+00:00,\n check if 2020-12-04T09:46:41+00:00 >= 2020-12-04T09:46:41+00:00,\n check if 2020-12-04T09:46:41+00:00 >= 2019-12-04T09:46:41+00:00,\n check if 2020-12-04T09:46:41+00:00 >= 2020-12-04T09:46:41+00:00,\n check if 2020-12-04T09:46:41+00:00 == 2020-12-04T09:46:41+00:00,\n check if #abc == #abc,\n check if hex:12ab == hex:12ab,\n check if [1, 2].contains(2),\n check if [2019-12-04T09:46:41+00:00, 2020-12-04T09:46:41+00:00].contains(2020-12-04T09:46:41+00:00),\n check if [false, true].contains(true),\n check if [\"abc\", \"def\"].contains(\"abc\"),\n check if [hex:12ab, hex:34de].contains(hex:34de),\n check if [#hello, #world].contains(#hello)\n ]\n }\n blocks: [\n \n ]\n}"
},
"validations": {
"": [
{
"facts": [
"revocation_id(0, hex:09b4fab17d84885149e416bf10990d19b918a02854acd9ad96494994735cd25d)",
"unique_revocation_id(0, hex:09b4fab17d84885149e416bf10990d19b918a02854acd9ad96494994735cd25d)"
],
"rules": [],
"privileged_rules": [],
"checks": [
"check if !false",
"check if \"aaabde\".matches(\"a*c?.e\")",
"check if \"abcD12\" == \"abcD12\"",
"check if \"hello world\".starts_with(\"hello\") && \"hello world\".ends_with(\"world\")",
"check if #abc == #abc",
"check if 1 + 2 * 3 - 4 / 2 == 5",
"check if 1 < 2",
"check if 1 <= 1",
"check if 1 <= 2",
"check if 2 > 1",
"check if 2 >= 1",
"check if 2 >= 2",
"check if 2019-12-04T09:46:41+00:00 < 2020-12-04T09:46:41+00:00",
"check if 2019-12-04T09:46:41+00:00 <= 2020-12-04T09:46:41+00:00",
"check if 2020-12-04T09:46:41+00:00 == 2020-12-04T09:46:41+00:00",
"check if 2020-12-04T09:46:41+00:00 > 2019-12-04T09:46:41+00:00",
"check if 2020-12-04T09:46:41+00:00 >= 2019-12-04T09:46:41+00:00",
"check if 2020-12-04T09:46:41+00:00 >= 2020-12-04T09:46:41+00:00",
"check if 3 == 3",
"check if [\"abc\", \"def\"].contains(\"abc\")",
"check if [#hello, #world].contains(#hello)",
"check if [1, 2].contains(2)",
"check if [2019-12-04T09:46:41+00:00, 2020-12-04T09:46:41+00:00].contains(2020-12-04T09:46:41+00:00)",
"check if [false, true].contains(true)",
"check if [hex:12ab, hex:34de].contains(hex:34de)",
"check if false or true",
"check if hex:12ab == hex:12ab",
"check if true"
],
"policies": [
"allow if true"
]
},
{
"Ok": 0
}
]
}
},
{
"title": "invalid block rule with unbound_variables",
"filename": "test18_unbound_variables_in_rule.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"check1\", \"test\", \"read\", \"unbound\", \"any1\", \"any2\"]\n authority: Block[0] {\n symbols: [\"check1\", \"test\", \"read\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if operation(#ambient, #read)\n ]\n }\n blocks: [\n Block[1] {\n symbols: [\"unbound\", \"any1\", \"any2\"]\n version: 1\n context: \"\"\n facts: []\n rules: [\n operation($unbound, #read) <- operation($any1, $any2)\n ]\n checks: []\n }\n ]\n}"
},
"validations": {
"": [
null,
{
"Err": [
"FailedLogic(InvalidBlockRule(0, \"operation($unbound, #read) <- operation($any1, $any2)\"))"
]
}
]
}
},
{
"title": "invalid block rule generating an #authority or #ambient symbol with a variable",
"filename": "test19_generating_ambient_from_variables.bc",
"print_token": {
"biscuit2 (1 check)": "Biscuit {\n symbols: [\"authority\", \"ambient\", \"resource\", \"operation\", \"right\", \"current_time\", \"revocation_id\", \"check1\", \"test\", \"read\", \"any\"]\n authority: Block[0] {\n symbols: [\"check1\", \"test\", \"read\"]\n version: 1\n context: \"\"\n facts: []\n rules: []\n checks: [\n check if operation(#ambient, #read)\n ]\n }\n blocks: [\n Block[1] {\n symbols: [\"any\"]\n version: 1\n context: \"\"\n facts: []\n rules: [\n operation($ambient, #read) <- operation($ambient, $any)\n ]\n checks: []\n }\n ]\n}"
},
"validations": {
"": [
{
"facts": [
"operation(#ambient, #write)",
"revocation_id(0, hex:cfbc25eee0ffc9bca3930e88469c45b8aa43e856464fc401db213c3d9587783a)",
"revocation_id(1, hex:0e180a4400430a812b58751a3d3877af6ac2fe87559a32656c9ae78a4e973781)",
"unique_revocation_id(0, hex:cfbc25eee0ffc9bca3930e88469c45b8aa43e856464fc401db213c3d9587783a)",
"unique_revocation_id(1, hex:0e180a4400430a812b58751a3d3877af6ac2fe87559a32656c9ae78a4e973781)"
],
"rules": [
"operation($ambient, #read) <- operation($ambient, $any)"
],
"privileged_rules": [],
"checks": [
"check if operation(#ambient, #read)"
],
"policies": [
"allow if true"
]
},
{
"Err": [
"Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: \"check if operation(#ambient, #read)\" })"
]
}
]
}
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
samples/v2/test1_basic.bc Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,21 +3,23 @@ syntax = "proto2";
package biscuit.format.schema;
message Biscuit {
required bytes authority = 1;
repeated bytes blocks = 2;
repeated bytes keys = 3;
required Signature signature = 4;
optional uint32 rootKeyId = 1;
required SignedBlock authority = 2;
repeated SignedBlock blocks = 3;
required Proof proof = 4;
}
message SealedBiscuit {
required bytes authority = 1;
repeated bytes blocks = 2;
message SignedBlock {
required bytes block = 1;
required bytes nextKey = 2;
required bytes signature = 3;
}
message Signature {
repeated bytes parameters = 1;
required bytes z = 2;
message Proof {
oneof Content {
bytes nextSecret = 1;
bytes finalSignature = 2;
}
}
message Block {