From d91e055156b1242a8905a25894633325d91655af Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 2 Sep 2021 23:04:46 +0200 Subject: [PATCH] new cryptographic scheme --- DESIGN.md | 472 ++------ SPECIFICATIONS.md | 207 ++-- samples/v2/README.md | 1048 +++++++++++++++++ samples/v2/samples.json | 611 ++++++++++ samples/v2/test10_authority_rules.bc | Bin 0 -> 491 bytes .../v2/test11_verifier_authority_caveats.bc | Bin 0 -> 174 bytes samples/v2/test12_authority_caveats.bc | Bin 0 -> 187 bytes samples/v2/test13_block_rules.bc | Bin 0 -> 532 bytes samples/v2/test14_regex_constraint.bc | Bin 0 -> 222 bytes samples/v2/test15_multi_queries_caveats.bc | Bin 0 -> 181 bytes samples/v2/test16_caveat_head_name.bc | Bin 0 -> 308 bytes samples/v2/test17_expressions.bc | Bin 0 -> 1231 bytes .../v2/test18_unbound_variables_in_rule.bc | Bin 0 -> 345 bytes ...est19_generating_ambient_from_variables.bc | Bin 0 -> 329 bytes samples/v2/test1_basic.bc | Bin 0 -> 399 bytes samples/v2/test2_different_root_key.bc | Bin 0 -> 346 bytes samples/v2/test3_invalid_signature_format.bc | Bin 0 -> 351 bytes samples/v2/test4_random_block.bc | Bin 0 -> 364 bytes samples/v2/test5_invalid_signature.bc | Bin 0 -> 399 bytes samples/v2/test6_reordered_blocks.bc | Bin 0 -> 545 bytes .../v2/test7_invalid_block_fact_authority.bc | Bin 0 -> 348 bytes .../v2/test8_invalid_block_fact_ambient.bc | Bin 0 -> 348 bytes samples/v2/test9_expired_token.bc | Bin 0 -> 367 bytes schema.proto | 22 +- 24 files changed, 1868 insertions(+), 492 deletions(-) create mode 100644 samples/v2/README.md create mode 100644 samples/v2/samples.json create mode 100644 samples/v2/test10_authority_rules.bc create mode 100644 samples/v2/test11_verifier_authority_caveats.bc create mode 100644 samples/v2/test12_authority_caveats.bc create mode 100644 samples/v2/test13_block_rules.bc create mode 100644 samples/v2/test14_regex_constraint.bc create mode 100644 samples/v2/test15_multi_queries_caveats.bc create mode 100644 samples/v2/test16_caveat_head_name.bc create mode 100644 samples/v2/test17_expressions.bc create mode 100644 samples/v2/test18_unbound_variables_in_rule.bc create mode 100644 samples/v2/test19_generating_ambient_from_variables.bc create mode 100644 samples/v2/test1_basic.bc create mode 100644 samples/v2/test2_different_root_key.bc create mode 100644 samples/v2/test3_invalid_signature_format.bc create mode 100644 samples/v2/test4_random_block.bc create mode 100644 samples/v2/test5_invalid_signature.bc create mode 100644 samples/v2/test6_reordered_blocks.bc create mode 100644 samples/v2/test7_invalid_block_fact_authority.bc create mode 100644 samples/v2/test8_invalid_block_fact_ambient.bc create mode 100644 samples/v2/test9_expired_token.bc diff --git a/DESIGN.md b/DESIGN.md index e5788d2..a506f12 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -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: + 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: + 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: + 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), 283–294. -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)` diff --git a/SPECIFICATIONS.md b/SPECIFICATIONS.md index 28c298b..70d9a60 100644 --- a/SPECIFICATIONS.md +++ b/SPECIFICATIONS.md @@ -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: + 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: + 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: + 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 diff --git a/samples/v2/README.md b/samples/v2/README.md new file mode 100644 index 0000000..f8ac7c8 --- /dev/null +++ b/samples/v2/README.md @@ -0,0 +1,1048 @@ +# Biscuit samples and expected results + +root secret key: 12aca40167fbdd1a11037e9fd440e3d510d9d9dea70a6646aa4aaf84d718d75a +root public key: acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189 + +------------------------------ + +## basic token: test1_basic.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "write", "check1", "0"] + authority: Block[0] { + symbols: ["read", "write"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read), + right(#authority, "file2", #read), + right(#authority, "file1", #write) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "0"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read) + ] + } + ] +} +``` + +validation: +verifier world: +World { + 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)", +} + privileged rules: {} + 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)\" })"]) + + +------------------------------ + +## different root key: test2_different_root_key.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "check1", "0"] + authority: Block[0] { + symbols: ["read"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "0"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read) + ] + } + ] +} +``` + +validation: +Err(["Format(Signature(InvalidSignature(\"signature error\")))"]) + + +------------------------------ + +## invalid signature format: test3_invalid_signature_format.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "write", "check1", "0"] + authority: Block[0] { + symbols: ["read", "write"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read), + right(#authority, "file2", #read), + right(#authority, "file1", #write) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "0"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read) + ] + } + ] +} +``` + +validation: +Err(["Format(InvalidSignatureSize(16))"]) + + +------------------------------ + +## random block: test4_random_block.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "write", "check1", "0"] + authority: Block[0] { + symbols: ["read", "write"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read), + right(#authority, "file2", #read), + right(#authority, "file1", #write) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "0"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read) + ] + } + ] +} +``` + +validation: +Err(["Format(Signature(InvalidSignature(\"signature error\")))"]) + + +------------------------------ + +## invalid signature: test5_invalid_signature.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "write", "check1", "0"] + authority: Block[0] { + symbols: ["read", "write"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read), + right(#authority, "file2", #read), + right(#authority, "file1", #write) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "0"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read) + ] + } + ] +} +``` + +validation: +Err(["Format(Signature(InvalidSignature(\"signature error\")))"]) + + +------------------------------ + +## reordered blocks: test6_reordered_blocks.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "write", "check1", "0"] + authority: Block[0] { + symbols: ["read", "write"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read), + right(#authority, "file2", #read), + right(#authority, "file1", #write) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "0"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read) + ] + } + ] +} +``` + +biscuit3 (2 checks): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "write", "check1", "0", "check2"] + authority: Block[0] { + symbols: ["read", "write"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read), + right(#authority, "file2", #read), + right(#authority, "file1", #write) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "0"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, $0), operation(#ambient, #read), right(#authority, $0, #read) + ] + }, + Block[2] { + symbols: ["check2"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, "file1") + ] + } + ] +} +``` + +validation: +Err(["Format(Signature(InvalidSignature(\"signature error\")))"]) + + +------------------------------ + +## invalid block fact with authority tag: test7_invalid_block_fact_authority.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "write", "check1", "0"] + authority: Block[0] { + symbols: ["read"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["write", "check1", "0"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #write) + ] + rules: [] + checks: [ + check if operation(#ambient, #read) + ] + } + ] +} +``` + +validation: +Err(["FailedLogic(InvalidBlockFact(0, \"right(#authority, \\\"file1\\\", #write)\"))"]) + + +------------------------------ + +## invalid block fact with ambient tag: test8_invalid_block_fact_ambient.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "write", "check1", "0"] + authority: Block[0] { + symbols: ["read"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["write", "check1", "0"] + version: 1 + context: "" + facts: [ + right(#ambient, "file1", #write) + ] + rules: [] + checks: [ + check if operation(#ambient, #read) + ] + } + ] +} +``` + +validation: +Err(["FailedLogic(InvalidBlockFact(0, \"right(#ambient, \\\"file1\\\", #write)\"))"]) + + +------------------------------ + +## expired token: test9_expired_token.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "check1", "expiration", "date", "time"] + authority: Block[0] { + symbols: [] + version: 1 + context: "" + facts: [] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "expiration", "date", "time"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, "file1"), + check if time(#ambient, $date), $date <= 2018-12-20T00:00:00+00:00 + ] + } + ] +} +``` + +validation: +verifier world: +World { + 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)", +} + privileged rules: {} + 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\" })"]) + + +------------------------------ + +## authority rules: test10_authority_rules.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "1", "read", "owner", "0", "write", "check1", "check2", "alice"] + authority: Block[0] { + symbols: ["1", "read", "owner", "0", "write"] + version: 1 + context: "" + facts: [] + rules: [ + right(#authority, $1, #read) <- resource(#ambient, $1), owner(#ambient, $0, $1), + right(#authority, $1, #write) <- resource(#ambient, $1), owner(#ambient, $0, $1) + ] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["check1", "check2", "alice"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if right(#authority, $0, $1), resource(#ambient, $0), operation(#ambient, $1), + check if resource(#ambient, $0), owner(#ambient, #alice, $0) + ] + } + ] +} +``` + +validation: +verifier world: +World { + 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)", +} + privileged rules: { + "right(#authority, $1, #read) <- resource(#ambient, $1), owner(#ambient, $0, $1)", + "right(#authority, $1, #write) <- resource(#ambient, $1), owner(#ambient, $0, $1)", +} + rules: {} + 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) + + +------------------------------ + +## verifier authority checks: test11_verifier_authority_caveats.bc +biscuit: +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read"] + authority: Block[0] { + symbols: ["read"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read) + ] + rules: [] + checks: [] + } + blocks: [ + + ] +} +``` + +validation: +verifier world: +World { + facts: { + "operation(#ambient, #read)", + "resource(#ambient, \"file2\")", + "revocation_id(0, hex:f3db615323f48dc225b793ec494c30c1d4a800ec8299aa7558fe769803f1446b)", + "right(#authority, \"file1\", #read)", + "unique_revocation_id(0, hex:f3db615323f48dc225b793ec494c30c1d4a800ec8299aa7558fe769803f1446b)", +} + privileged rules: {} + 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)\" })"]) + + +------------------------------ + +## authority checks: test12_authority_caveats.bc +biscuit: +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "check1"] + authority: Block[0] { + symbols: ["check1"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, "file1") + ] + } + blocks: [ + + ] +} +``` + +validation for "file1": +verifier world: +World { + facts: { + "operation(#ambient, #read)", + "resource(#ambient, \"file1\")", + "revocation_id(0, hex:a6d33a7c61185cc962a4100d17176b72a60e95490af7c3cccbd244f3cce02b85)", + "unique_revocation_id(0, hex:a6d33a7c61185cc962a4100d17176b72a60e95490af7c3cccbd244f3cce02b85)", +} + privileged rules: {} + rules: {} + checks: { + "check if resource(#ambient, \"file1\")", +} + policies: { + "allow if true", +} +} + +Ok(0) +validation for "file2": +verifier world: +World { + facts: { + "operation(#ambient, #read)", + "resource(#ambient, \"file2\")", + "revocation_id(0, hex:a6d33a7c61185cc962a4100d17176b72a60e95490af7c3cccbd244f3cce02b85)", + "unique_revocation_id(0, hex:a6d33a7c61185cc962a4100d17176b72a60e95490af7c3cccbd244f3cce02b85)", +} + privileged rules: {} + 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\\\")\" })"]) + + +------------------------------ + +## block rules: test13_block_rules.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "read", "valid_date", "time", "0", "1", "check1"] + authority: Block[0] { + symbols: ["read"] + version: 1 + context: "" + facts: [ + right(#authority, "file1", #read), + right(#authority, "file2", #read) + ] + rules: [] + checks: [] + } + blocks: [ + Block[1] { + symbols: ["valid_date", "time", "0", "1", "check1"] + version: 1 + context: "" + facts: [] + 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) + ] + checks: [ + check if valid_date($0), resource(#ambient, $0) + ] + } + ] +} +``` + +validation for "file1": +verifier world: +World { + 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\")", +} + privileged rules: {} + 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)", +} + checks: { + "check if valid_date($0), resource(#ambient, $0)", +} + policies: { + "allow if true", +} +} + +Ok(0) +validation for "file2": +verifier world: +World { + 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)", +} + privileged rules: {} + 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)", +} + 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)\" })"]) + + +------------------------------ + +## regex_constraint: test14_regex_constraint.bc +biscuit: +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "resource_match", "0"] + authority: Block[0] { + symbols: ["resource_match", "0"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, $0), $0.matches("file[0-9]+.txt") + ] + } + blocks: [ + + ] +} +``` + +validation for "file1": +verifier world: +World { + facts: { + "resource(#ambient, \"file1\")", + "revocation_id(0, hex:1da4cd4d7c60491948662acc237bb10599c6046e1ef09a867267b5e039a4d1b6)", + "unique_revocation_id(0, hex:1da4cd4d7c60491948662acc237bb10599c6046e1ef09a867267b5e039a4d1b6)", +} + privileged rules: {} + 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\\\")\" })"]) +validation for "file123": +verifier world: +World { + facts: { + "resource(#ambient, \"file123.txt\")", + "revocation_id(0, hex:1da4cd4d7c60491948662acc237bb10599c6046e1ef09a867267b5e039a4d1b6)", + "unique_revocation_id(0, hex:1da4cd4d7c60491948662acc237bb10599c6046e1ef09a867267b5e039a4d1b6)", +} + privileged rules: {} + rules: {} + checks: { + "check if resource(#ambient, $0), $0.matches(\"file[0-9]+.txt\")", +} + policies: { + "allow if true", +} +} + +Ok(0) + + +------------------------------ + +## multi queries checks: test15_multi_queries_caveats.bc +biscuit: +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "must_be_present"] + authority: Block[0] { + symbols: ["must_be_present"] + version: 1 + context: "" + facts: [ + must_be_present(#authority, "hello") + ] + rules: [] + checks: [] + } + blocks: [ + + ] +} +``` + +validation: +verifier world: +World { + facts: { + "must_be_present(#authority, \"hello\")", + "revocation_id(0, hex:128099942c46fc6a4f9a8f8f0cc5d8b70c4d55d834255ef6065b62c967eef50c)", + "unique_revocation_id(0, hex:128099942c46fc6a4f9a8f8f0cc5d8b70c4d55d834255ef6065b62c967eef50c)", +} + privileged rules: {} + rules: {} + checks: { + "check if must_be_present(#authority, $0) or must_be_present($0)", +} + policies: { + "allow if true", +} +} + +Ok(0) + + +------------------------------ + +## check head name should be independent from fact names: test16_caveat_head_name.bc +biscuit: +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "check1", "test", "hello"] + authority: Block[0] { + symbols: ["check1", "test", "hello"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if resource(#ambient, #hello) + ] + } + blocks: [ + Block[1] { + symbols: [] + version: 1 + context: "" + facts: [ + check1(#test) + ] + rules: [] + checks: [] + } + ] +} +``` + +validation: +verifier world: +World { + 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)", +} + privileged rules: {} + 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)\" })"]) + + +------------------------------ + +## test expression syntax and all available operations: test17_expressions.bc +biscuit: +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "query", "abc", "hello", "world"] + authority: Block[0] { + symbols: ["query", "abc", "hello", "world"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if true, + check if !false, + check if false or true, + check if 1 < 2, + check if 2 > 1, + check if 1 <= 2, + check if 1 <= 1, + check if 2 >= 1, + check if 2 >= 2, + check if 3 == 3, + check if 1 + 2 * 3 - 4 / 2 == 5, + check if "hello world".starts_with("hello") && "hello world".ends_with("world"), + check if "aaabde".matches("a*c?.e"), + check if "abcD12" == "abcD12", + 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 > 2019-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 >= 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 #abc == #abc, + check if hex:12ab == hex:12ab, + 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 ["abc", "def"].contains("abc"), + check if [hex:12ab, hex:34de].contains(hex:34de), + check if [#hello, #world].contains(#hello) + ] + } + blocks: [ + + ] +} +``` + +validation: +verifier world: +World { + facts: { + "revocation_id(0, hex:09b4fab17d84885149e416bf10990d19b918a02854acd9ad96494994735cd25d)", + "unique_revocation_id(0, hex:09b4fab17d84885149e416bf10990d19b918a02854acd9ad96494994735cd25d)", +} + privileged rules: {} + 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) + + +------------------------------ + +## invalid block rule with unbound_variables: test18_unbound_variables_in_rule.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "check1", "test", "read", "unbound", "any1", "any2"] + authority: Block[0] { + symbols: ["check1", "test", "read"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if operation(#ambient, #read) + ] + } + blocks: [ + Block[1] { + symbols: ["unbound", "any1", "any2"] + version: 1 + context: "" + facts: [] + rules: [ + operation($unbound, #read) <- operation($any1, $any2) + ] + checks: [] + } + ] +} +``` + +validation: +Err(["FailedLogic(InvalidBlockRule(0, \"operation($unbound, #read) <- operation($any1, $any2)\"))"]) + + +------------------------------ + +## invalid block rule generating an #authority or #ambient symbol with a variable: test19_generating_ambient_from_variables.bc +biscuit2 (1 check): +``` +Biscuit { + symbols: ["authority", "ambient", "resource", "operation", "right", "current_time", "revocation_id", "check1", "test", "read", "any"] + authority: Block[0] { + symbols: ["check1", "test", "read"] + version: 1 + context: "" + facts: [] + rules: [] + checks: [ + check if operation(#ambient, #read) + ] + } + blocks: [ + Block[1] { + symbols: ["any"] + version: 1 + context: "" + facts: [] + rules: [ + operation($ambient, #read) <- operation($ambient, $any) + ] + checks: [] + } + ] +} +``` + +validation: +verifier world: +World { + 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)", +} + privileged rules: {} + rules: { + "operation($ambient, #read) <- operation($ambient, $any)", +} + 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)\" })"]) + diff --git a/samples/v2/samples.json b/samples/v2/samples.json new file mode 100644 index 0000000..2f3d22f --- /dev/null +++ b/samples/v2/samples.json @@ -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)\" })" + ] + } + ] + } + } + ] +} diff --git a/samples/v2/test10_authority_rules.bc b/samples/v2/test10_authority_rules.bc new file mode 100644 index 0000000000000000000000000000000000000000..444629a5ac2d3a9865876c2c3398d8bd977e819d GIT binary patch literal 491 zcmWf7z{pj_!63wFD8y2fnwTQQnqQumS|r41AjDc;lv$E$!RV#K#mB)S!~|5uB)|@& zIfS@4n1D1Rn9s+-31xGE_!z3W@u?P4kS^xRT(rZ@`U<=H^eZ}Zqz=rQbL@qzrVN+& zMV30y)D1uE&2s$wSN}3&uP)Zl z|LecbPjIg*zV>&eDyvfey<@70v*&V3J!Isn20E54IU_YW+Ym|{39%;TWF`as7i7j| z!o|hG19Uyu2SNznLih;ZasmCq4D)%A8kY(e8wW2~AF>*Fh;Z@PCCSl;%S^NqIEv@X|cbDZa` zD=^>48uMqtcLqk*+6~jEv?X~gKWP@gb#{)-J;euCxHM-Y5t6dDc&ZRau~fU?*06_(9)b~+IbfMil~Kz literal 0 HcmV?d00001 diff --git a/samples/v2/test11_verifier_authority_caveats.bc b/samples/v2/test11_verifier_authority_caveats.bc new file mode 100644 index 0000000000000000000000000000000000000000..4467ec7bc1d86e2431ab067245af5bb21e4348c1 GIT binary patch literal 174 zcmV;f08#%EhXD#92mlfUa%Ev;H~~Tx3KIwf5&{SS5(gp$W@&6?F%kj@2NEFBxn6y! zni^=t@f_PpdLdi#07m&n0!4SS?o2*LoTup;K((0wQoP5D#HSlHv6IK@it+a2VpR3| zqE!?7IRb+Nk1Gf!{#yIvvDuo_Xu3+R*fs*o;4S4~23Q2h^V+!b1tKB}ARC(QNyuqO c3MdBdDknyq3!?9jYECO-Re&zwV+ObK0#%AessI20 literal 0 HcmV?d00001 diff --git a/samples/v2/test12_authority_caveats.bc b/samples/v2/test12_authority_caveats.bc new file mode 100644 index 0000000000000000000000000000000000000000..b38d81bc696ffb154e263b595e6234dbf8e475cd GIT binary patch literal 187 zcmWfd!pNn^!63wzoRONGZD_$5q`)P|#m&Jk#ID4emYI`kD8$dfB*etQh!9Y4xfXoZ zo5xHl)q#0!>#j2|tNVLB0+J`3byuABV$q4@7%2z!Uk~(-PkPiW|N48=;_fG$tjn3! zv`w)8X(#x-ZG-*ZzR-QsEZ+zDmj*32&~3EYk~wEfMpS`o@w)9_wv@eo_mW9TiA%xe l|A)IJ86h+Hm{P79rR^>K!Z$}YW$#vH=WRYT4FJqaOW^E|e z;5B8DCHL0l*_?WRtfQGtscXevGmqmU7vwuH%UKsTC*KKu6tGixs^`6=C(KGpTnbN4 kxScN-oSmnYwA1Sv+t-MWuZ{UpXRPK-=Bc`uwAZ*00Do+nnE(I) literal 0 HcmV?d00001 diff --git a/samples/v2/test14_regex_constraint.bc b/samples/v2/test14_regex_constraint.bc new file mode 100644 index 0000000000000000000000000000000000000000..367b5d8230514d3ded7827c92a21780f80963701 GIT binary patch literal 222 zcmV<403rVpw*d-K2mlfea%FRGb#h~6Uu|J@V`vfqFgO8HI0`lj1_%cd0uTri3J3xc z0tf*T0uTrqAPNKu0uTra5(*F^4rXa=Wm_;UIbAC*ba-?M1R4Se2ofMW+BP{@c^tBD zLqGoH;6kQz`zPN9+;tG(BPsDcz}argq%M)XSsp0w41&K!}hW-{O9 zeT{6u0vdD*4hR7_0YV501_%cd0tg5aAo#12Yjjf*;wC5NAj?{Hj6^M3R+^G{EW$BY z!eP?k_8LI$PC%O5DVDC<=2a zqKhEl7*?DAyp3n#w8<8rlKKoHA_^dZg7UL8MlR5ER$`*+Qe G@0$HVn0@mA literal 0 HcmV?d00001 diff --git a/samples/v2/test17_expressions.bc b/samples/v2/test17_expressions.bc new file mode 100644 index 0000000000000000000000000000000000000000..917868172ca37b4b4f711148c31ffa4b14c3d1b9 GIT binary patch literal 1231 zcma)*O=uHQ5XU#0eaXu-P1C1HSehEP5~7w+L&5gqN3c~}D6$7dsn~q1h^zzyA{8P8 z>L~{m^iV-T57H_q6v1N&f(RaZD&9nj2X7uks0X$4=H;VV+u~*3dpp1RznR&2Vj%=| z&NacV-*C#a!k^0*1iRw6Zb`74rLtQb@!1gwgU*9y01VLayc-0a!*tfb5IWcFz79(3 zIQeW7ViY4}Ow31C2g8(+oj&}k2=wg~r+ea*CR&X1`ZjC&J#osXa`F27lnYwaH0%jD zMKxrb8DgF)S`l5ug~)4=RjSx0j0gI{8d^4-A*~7Mfo`jdI-a10LkNPF`US)WwjmTO zm4n1}+ez3*n*9L4tw1i9%NL!-{y^bq+98hr!{rMn4rLmg+N*jkNRXZh90I9o^~rap zcD02+st(QeFyv{|Qpe|Iw$RbNOk1aQmw$VP6y;qzF#n^|VvNoi;avVZY4Hxj?uS0= z%O1cNkxxmWVkM~(Ao*uH|YpLb%xsqESoBlL)#60$HQF|SKd-9jITAvxp zS9+bL>hoK3i&sZ8%Xh}!A7;BQ&HcPEcrP<~aprDvem3^%L3C|(TAZ)<4*V$mK5kf; l$D^sQd&i&NepvoidptZ@tU&GA_?K&=E0v`e^U3vfYXhHO#6ti8 literal 0 HcmV?d00001 diff --git a/samples/v2/test18_unbound_variables_in_rule.bc b/samples/v2/test18_unbound_variables_in_rule.bc new file mode 100644 index 0000000000000000000000000000000000000000..90ba0bdfc166046149c091c54832a221638c7333 GIT binary patch literal 345 zcmWfd#>i#B!63wzoRONGZ79T2l3H9M#8Q-+m}0>gB*rDe#m2!d#KgfN#Kpl3q#1#j zQ%GU9v&|%d6K?72Zm#0z(I`-#qEWK)W}y-P7TLNvw{omfq#SPji!iz0>Swl4>dFwuzMhEZXqTC9wCL5%X#NK zH4S&mY+n3S^4EDkkAR2UUWXkxE%ZNnzZLVLxl#^sOT1QJn=y^WQ+3D8_>a>>N&{M> z7Jk?#^);xrbCdE8(aigu-@foUEpFKrnU%46m&`X7u`er6Z_?q=ayYv4KoP5w5|=`w luJY190xyHLRz6|fBHJwL+qu7R)nW4FLCBf9(JO literal 0 HcmV?d00001 diff --git a/samples/v2/test19_generating_ambient_from_variables.bc b/samples/v2/test19_generating_ambient_from_variables.bc new file mode 100644 index 0000000000000000000000000000000000000000..fe8893bdaa0171e658116758e3826ac05c91412d GIT binary patch literal 329 zcmWfd#>i#B!63wzoRONGZ79T2l3H9M#8Q-+m}0>gB*rDe#m2!d#KgfN#Kpl3q#1#j zQ%Iq7l2TmJDa+tM4gs~exvNj8tgm^{9%gW)KtRpT>c#vuQVttFFZbL!P5PtURh5$P zw+}=Q@@h`tNZfA4HKD#sX6dnqJ<+jlS{a*@T@J}`ihOy-8#bw#Nn3ZijKYcCpSfM? z*rhrdxs-v{F(>9#S}=M^aDnX?fY=Xb3vdZ3$l1Jmuvex>BIoEw1v~qTwd;33SN^DL zzX%n_syX-fmoc>2Z(k(L%Ycg-*LWSA|Df z#n)A5Wj_2`Di^7}@bHIDmc3e*S9TdyPQ1abq{O8VAU^y1@^>tjlTPicF8Lt1S|;>Q SNx_!7RgZ&yrIhTLtqA~oVtLL0 literal 0 HcmV?d00001 diff --git a/samples/v2/test1_basic.bc b/samples/v2/test1_basic.bc new file mode 100644 index 0000000000000000000000000000000000000000..1f4f4109415efd151393c69eeebe08a8558f5b9d GIT binary patch literal 399 zcmWgI!^joJ!63v^l$w|##9CgIS(0kO=p@P|%)uhW1e9P`Vol4;Ni_s=*fE8TunU7! za|kIcNKoh9{_^C9y#6_dtx{gjPo1`2=rrSzZ5H>t{#2)^BuhEu*WX;9YOMMxs5~Il zRd7R;YD@D!Z}-dPix-GJiC$m-E8+4&=ARtK^~D7|&yQca;;hceci@cT1I4v3SF4_X z>%%Iwl99`qgHebrIU_YW+faznz=AQzh|7SBje}E&Nq|d;i-QU17DgzW8N%jZ7vh6@ z52Oa@Ga-eay(CGbx9;U;V2YYUZlW zpSq~<$f4B3b~|?`eYCt#!zpZ9dCNQKf~Pi7N3;E@tKdAq}yvsPpyd6efVl?>#xl4qZ{IDq$`i> zUGlE3%CWoZ?cDT!mnVx$)69vl?z3>uZVZn4U$uZkwp6x1&X!YZB_o$J2cr;Maz<)$ zwxJNCfdylb5tjiM8wZCFlK`g>7Y7s2EsRh$GlUIv8z0ocAT?mOMtq%_beqRKb;-51 z%bD7Gb+cJFe0z|qsl2`^FfpXm@}ZPNS+hxm^;Z;zB4_{8?QX&*bI9XhdVhNu1g7An`?h&k<{B?rd#a7 zPd!nwms-il<;=k-#Fm_qnw)JY#Asl_7-Ym{z{SSFDa0heCB((S1T>ow%4UYJIoO5x zpw0!U0lHL3;fDi*Wd&DmO2OHxiWHrY(Q?)G73_<>EQ7mD9%^*h>F!f|PrIRN~%Yq|gc literal 0 HcmV?d00001 diff --git a/samples/v2/test4_random_block.bc b/samples/v2/test4_random_block.bc new file mode 100644 index 0000000000000000000000000000000000000000..36da6aea4254ad5c5640cb120f941e8a36b9ce3c GIT binary patch literal 364 zcmWgI!^joJ!63v^l$w|##9CgIS(0kO=p@P|%)uhW1e9P`Vol4;Ni_s=*fE8TunU7! za|kJjihb0+mDI8In(L7{Gaf7vvQj8?Vo|!XNPI@FZdvHhLMey*yV^nwOnk3wpXYzR zEBW=|6dT{{)us}RX$?Iu+~HXU6_Crc6k|@1*{**~eZ+ zFjqf{xp&~qLAP?@$W+^-Rmbw#=LGHuikEWG)Hp9-y2asNc&DWR33IdIuFyE5ql5((pb8X=S-U-L;o9IZ^UE!_UkO>>Kwz^TsTqsb6UOVuC6C*4|7bkc(3vG zRga5?6ywBPq1Fnab-MqLFIIWk5@Nc(^3^?-=$$^VUVpF)V(Luhl3K~g!Zo}W+dUYT;#%#nY}m7B!@w+t8l-psd8UvH;nDWj4Sm%{yJlC6v4=PdS5_sK{T YpZLE@{`ABwh9lldz6F~LG& zLY7v%*2_JiuUEa8C?S??uX|PH7rT_hBd00ROV%GY{kP+z^wsCnHLZ`AKl4w2 zj*Fv2R$fck;aK z0zHPuH*cJN_Why2%-GN7d#)d3lycY;sM;&bCH`=M%U5AO_D^pvL@>xpN7e@O?B7%! zf6;?|rQ!9~c;7ZpiHOM!3v4Za+IUqQ2w5D)R8Z zo@m9|!vO~V|2Uu8YRgQvvU&6UcbAky#-aev8HSEo#lHC-`zq{OZ5sIvcP-LBVX}7h zruHZX7w38<%cvs;*Ux9hf7VspbbQO>^&ZY~LOmg(e)pCoNpMK5X5@0^U=(64FUl-Q z6=F-yNKMW*6k;?$aup-8t2lzhxJ0rv1h|B_IGBMhWrVoa7n+0DdyCpx?-;;JP#Nn}_ z;svq23=)n)+ivOUM4Z_0>Z|vuUj7gLx)<+An;e<=WTtd#-d$EDB`$?C9e>wL&6BW> cR?zCat-P$)IQ6wisIc@i^8`T+=Dx5n0F}ad5&!@I literal 0 HcmV?d00001 diff --git a/samples/v2/test9_expired_token.bc b/samples/v2/test9_expired_token.bc new file mode 100644 index 0000000000000000000000000000000000000000..857e52074a7f10f364b32d8f2d3ee56323017de5 GIT binary patch literal 367 zcmWg2;$q=ouwWEYFbNi&f6y#%-JP_Jr5v6YR_Y5HtZA7!sfI%Q985w?K&>!=AU!S}E;bGhAtnJ%AubLsuq2QzCC0_V z1*CyOY#I&QUq4{wVv%A3YE~#znsm^W{h__d9kw-D?lQ7ZL*X7A1oB4EdI@QQ1G9@cP3)0XJA?B