Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for ECDSA signatures on curve secp256r1 #165

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions SPECIFICATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ The logic language is described by the following EBNF grammar:
```
<origin_clause> ::= <sp>? "trusting " <origin_element> <sp>? ("," <sp>? <origin_element> <sp>?)*
<origin_element> ::= "authority" | "previous" | <signature_alg> "/" <bytes>
<signature_alg> ::= "ed25519"
<signature_alg> ::= "ed25519" | "secp256r1"

<block> ::= (<origin_clause> ";" <sp>?)? (<block_element> | <comment> )*
<block_element> ::= <sp>? ( <check> | <fact> | <rule> ) <sp>? ";" <sp>?
Expand Down Expand Up @@ -619,6 +619,7 @@ message PublicKey {

enum Algorithm {
Ed25519 = 0;
SECP256R1 = 1;
}

required bytes key = 2;
Expand Down Expand Up @@ -688,16 +689,58 @@ is a Biscuit token, that base 64 string should be prefixed with `biscuit:`.

### Cryptography

Biscuit tokens are based on public key cryptography, with a chain of Ed25519
Biscuit tokens are based on public key cryptography, with a chain of
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.

#### Algorithms

Biscuit supports multiple signature algorithms for its blocks, that can change
between blocks in one token. The algorithm kind is defined in the `Algorithm`
enum of the protobuf serialization of the public key. The `nextSecret` field
in the proof section of the token uses the same algorithm as the `nextKey`
of the last block.

The following algorithms are supported:

##### Ed25519

The default signature algorithm is Ed25519 as introduced in [Bernstein, Daniel J.;
Duif, Niels; Lange, Tanja; Schwabe, Peter; Bo-Yin Yang (2012). "High-speed
high-security signatures" (PDF). Journal of Cryptographic Engineering](https://ed25519.cr.yp.to/ed25519-20110926.pdf)
and specified in [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032).

The protobuf encoding is defined as follows:
- `key` field of the `Publickey` message: [compressed Edwards Y format](https://www.rfc-editor.org/rfc/rfc8032#section-5.1.2)
- `nextSecret` in the `Proof` message: [32 bytes of cryptographically secure random data in little-endian](https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5)
- `signature` field in `Signature` and `ExternalSignature` messages: [concatenation of R and S values](https://www.rfc-editor.org/rfc/rfc8032#section-5.1.6)

##### ECDSA

Biscuit supports the ECDSA algorithm over the secp256r1 curve as defined in
[SEC2v1](https://www.secg.org/sec1-v2.pdf), using the SHA-256 hashing
algorithm. It is recommended to use a deterministic signature algorithm
version like the one defined in [RFC 6979](https://datatracker.ietf.org/doc/html/rfc6979).


The protobuf encoding is defined as follows:
- `key` field of the `Publickey` message: [compressed SEC1 format, defined in section 2.3.3](https://www.secg.org/sec1-v2.pdf). Allowed prefixes: `02`, `03`
- `nextSecret` in the `Proof` message: big endian representation of the secret scalar
- `signature` field in `Signature` and `ExternalSignature` messages: [SEC1 ASN.1 format, defined in section C5](https://www.secg.org/sec1-v2.pdf), only using the `r` and `s` parameters

```
ECDSA-Sig-Value ::= SEQUENCE {
r INTEGER,
s INTEGER
}
```

#### Signature (one block)

- `(pk_0, sk_0)` the root public and private Ed25519 keys
- `(pk_0, sk_0)` the root public and private keys
- `data_0` the serialized Datalog
- `(pk_1, sk_1)` the next key pair, generated at random
- `alg_1` the little endian representation of the signature algorithm fr `pk1, sk1` (see protobuf schema)
Expand Down Expand Up @@ -758,7 +801,7 @@ Token {
##### Optional external signature

Blocks generated by a trusted third party can carry an *extra* signature to provide a proof of their
origin. Same as regular signatures, they rely on Ed25519.
origin. Same as regular signatures, they rely on public key cryptography.

The external signature for block `n+1`, with `(external_pk, external_sk)` is `external_sig_n+1 = sign(external_sk, data_n+1 + alg_n+1 + pk_n+1)`.
It's quite similar to the regular signature, with a crucial difference: the public key appended to the block payload is the one _carried_ by block `n` (and which is used to verify block `n+1`).
Expand Down
89 changes: 86 additions & 3 deletions samples/current/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ check if resource($0), operation("read"), right($0, "read");

### validation

result: `Err(Format(InvalidSignatureSize(16)))`
result: `Err(Format(BlockSignatureDeserializationError("block signature deserialization error: [117, 149, 161, 18, 161, 235, 91, 129, 166, 227, 152, 133, 46, 97, 24, 183]")))`


------------------------------
Expand Down Expand Up @@ -2463,10 +2463,9 @@ World {

result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "reject if test($test), $test" })] }))`


------------------------------

## test null: test30_null.bc
## test null: test030_null.bc
### token

authority:
Expand Down Expand Up @@ -2656,3 +2655,87 @@ World {

result: `Err(FailedLogic(Unauthorized { policy: Allow(0), checks: [Block(FailedBlockCheck { block_id: 0, check_id: 0, rule: "check if fact(null, $value), $value == null" }), Block(FailedBlockCheck { block_id: 0, check_id: 1, rule: "reject if fact(null, $value), $value != null" })] }))`


------------------------------

## ECDSA secp256r1 signatures: test031_secp256r1.bc
### token

authority:
symbols: ["file1", "file2"]

public keys: []

```
right("file1", "read");
right("file2", "read");
right("file1", "write");
```

1:
symbols: ["0"]

public keys: []

```
check if resource($0), operation("read"), right($0, "read");
```

### validation

authorizer code:
```
resource("file1");
operation("read");

allow if true;
```

revocation ids:
- `760785de30d7348e9c847aab8b3bdad6a0d463f4f50ed9667aade563e9112ee6d2f589630dd7553c2eced2a57edf3636d5c874b35df15120c62fddcbdbd2de09`
- `30440220039667c7a4d964e4b449289dc8fd206d7aa0e77eb701a9253b3307d32c177fa8022023f7523c143c5fb55ee4cafe49804702ef05a70883ebf42185b54bd36a7e7cd4`

authorizer world:
```
World {
facts: [
Facts {
origin: {
None,
},
facts: [
"operation(\"read\")",
"resource(\"file1\")",
],
},
Facts {
origin: {
Some(
0,
),
},
facts: [
"right(\"file1\", \"read\")",
"right(\"file1\", \"write\")",
"right(\"file2\", \"read\")",
],
},
]
rules: []
checks: [
Checks {
origin: Some(
1,
),
checks: [
"check if resource($0), operation(\"read\"), right($0, \"read\")",
],
},
]
policies: [
"allow if true",
]
}
```

result: `Ok(0)`
74 changes: 72 additions & 2 deletions samples/current/samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
"result": {
"Err": {
"Format": {
"InvalidSignatureSize": 16
"BlockSignatureDeserializationError": "block signature deserialization error: [117, 149, 161, 18, 161, 235, 91, 129, 166, 227, 152, 133, 46, 97, 24, 183]"
}
}
},
Expand Down Expand Up @@ -2279,7 +2279,7 @@
},
{
"title": "test null",
"filename": "test30_null.bc",
"filename": "test030_null.bc",
"token": [
{
"symbols": [
Expand Down Expand Up @@ -2501,6 +2501,76 @@
]
}
}
},
{
"title": "ECDSA secp256r1 signatures",
"filename": "test031_secp256r1.bc",
"token": [
{
"symbols": [
"file1",
"file2"
],
"public_keys": [],
"external_key": null,
"code": "right(\"file1\", \"read\");\nright(\"file2\", \"read\");\nright(\"file1\", \"write\");\n"
},
{
"symbols": [
"0"
],
"public_keys": [],
"external_key": null,
"code": "check if resource($0), operation(\"read\"), right($0, \"read\");\n"
}
],
"validations": {
"": {
"world": {
"facts": [
{
"origin": [
null
],
"facts": [
"operation(\"read\")",
"resource(\"file1\")"
]
},
{
"origin": [
0
],
"facts": [
"right(\"file1\", \"read\")",
"right(\"file1\", \"write\")",
"right(\"file2\", \"read\")"
]
}
],
"rules": [],
"checks": [
{
"origin": 1,
"checks": [
"check if resource($0), operation(\"read\"), right($0, \"read\")"
]
}
],
"policies": [
"allow if true"
]
},
"result": {
"Ok": 0
},
"authorizer_code": "resource(\"file1\");\noperation(\"read\");\n\nallow if true;\n",
"revocation_ids": [
"760785de30d7348e9c847aab8b3bdad6a0d463f4f50ed9667aade563e9112ee6d2f589630dd7553c2eced2a57edf3636d5c874b35df15120c62fddcbdbd2de09",
"30440220039667c7a4d964e4b449289dc8fd206d7aa0e77eb701a9253b3307d32c177fa8022023f7523c143c5fb55ee4cafe49804702ef05a70883ebf42185b54bd36a7e7cd4"
]
}
}
}
]
}
File renamed without changes.
Binary file added samples/current/test031_secp256r1.bc
Binary file not shown.
1 change: 1 addition & 0 deletions schema.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ message PublicKey {

enum Algorithm {
Ed25519 = 0;
SECP256R1 = 1;
}

required bytes key = 2;
Expand Down