-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Corks are authorization tokens which are based off Google's [Macaroons](https://research.google/pubs/macaroons-cookies-with-contextual-caveats-for-decentralized-authorization-in-the-cloud/) paper. They are bearer tokens which identify the entity possessing them, while providing a mechanism for embedding further restrictions via [Cedar](https://www.cedarpolicy.com/en) policy caveats.
- Loading branch information
Showing
82 changed files
with
15,111 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
name: corks | ||
on: | ||
pull_request: | ||
paths: | ||
- ".github/workflows/corks.yaml" | ||
- "packages/corks/**" | ||
|
||
# Prevent duplicate runs due to Graphite | ||
# https://graphite.dev/docs/troubleshooting#why-are-my-actions-running-twice | ||
concurrency: | ||
group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}-${{ github.ref == 'refs/heads/main' && github.sha || ''}} | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
test: | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
os: | ||
- ubuntu-latest | ||
- macos-14 | ||
- windows-latest | ||
runs-on: ${{ matrix.os }} | ||
# TODO(dnys1): Speed up Rust builds | ||
timeout-minutes: 15 | ||
steps: | ||
- name: Git Checkout | ||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # 4.1.1 | ||
with: | ||
submodules: true | ||
- name: Setup Dart | ||
uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 # main | ||
# Will often be out-of-date on runners | ||
- name: Setup Rust | ||
run: rustup update stable && rustup default stable | ||
- name: Get Packages | ||
working-directory: packages/corks | ||
run: dart pub get | ||
- name: Test | ||
working-directory: packages/corks | ||
run: dart --enable-experiment=native-assets test --fail-fast |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# https://dart.dev/guides/libraries/private-files | ||
# Created by `dart pub` | ||
.dart_tool/ | ||
|
||
# Avoid committing pubspec.lock for library packages; see | ||
# https://dart.dev/guides/libraries/private-files#pubspeclock. | ||
pubspec.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## 0.1.0 | ||
|
||
- Initial version. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Corks | ||
|
||
Corks are authorization tokens which are based off Google's [Macaroons](https://research.google/pubs/macaroons-cookies-with-contextual-caveats-for-decentralized-authorization-in-the-cloud/) paper. They are bearer tokens which identify the entity possessing them, while providing a mechanism for embedding further restrictions via [Cedar](https://www.cedarpolicy.com/en) policy caveats. | ||
|
||
## Development | ||
|
||
Corks use Protobuf for serialization and deserialization of bearers and caveats. The proto definitions are located in the [proto](./proto) directory and the [Buf](https://buf.build) toolchain is used to generate Dart code from the Protobuf files. | ||
|
||
To generate the Dart code, install Buf then run the following command from the `proto/` directory: | ||
|
||
```sh | ||
$ buf generate | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
include: package:lints/recommended.yaml | ||
|
||
analyzer: | ||
exclude: | ||
- "**/*.g.dart" | ||
- "**/*.pb.dart" | ||
- "**/*.pbenum.dart" | ||
- "**/*.pbjson.dart" | ||
- "**/*.pbserver.dart" | ||
errors: | ||
implementation_imports: ignore | ||
public_member_api_docs: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/// Corks are authorization tokens which are based off Google's | ||
/// [Macaroons](https://research.google/pubs/macaroons-cookies-with-contextual-caveats-for-decentralized-authorization-in-the-cloud/) | ||
/// paper. They are bearer tokens which identify the entity possessing them, | ||
/// while providing a mechanism for embedding further restrictions via | ||
/// [Cedar](https://www.cedarpolicy.com/en) policy caveats. | ||
library; | ||
|
||
export 'src/cork.dart'; | ||
export 'src/signer.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
export 'src/proto/cedar/v3/context.pb.dart'; | ||
export 'src/proto/cedar/v3/context.pbenum.dart'; | ||
export 'src/proto/cedar/v3/context.pbjson.dart'; | ||
export 'src/proto/cedar/v3/context.pbserver.dart'; | ||
export 'src/proto/cedar/v3/entity.pb.dart'; | ||
export 'src/proto/cedar/v3/entity.pbenum.dart'; | ||
export 'src/proto/cedar/v3/entity.pbjson.dart'; | ||
export 'src/proto/cedar/v3/entity.pbserver.dart'; | ||
export 'src/proto/cedar/v3/entity_id.pb.dart'; | ||
export 'src/proto/cedar/v3/entity_id.pbenum.dart'; | ||
export 'src/proto/cedar/v3/entity_id.pbjson.dart'; | ||
export 'src/proto/cedar/v3/entity_id.pbserver.dart'; | ||
export 'src/proto/cedar/v3/expr.pb.dart'; | ||
export 'src/proto/cedar/v3/expr.pbenum.dart'; | ||
export 'src/proto/cedar/v3/expr.pbjson.dart'; | ||
export 'src/proto/cedar/v3/expr.pbserver.dart'; | ||
export 'src/proto/cedar/v3/policy.pb.dart'; | ||
export 'src/proto/cedar/v3/policy.pbenum.dart'; | ||
export 'src/proto/cedar/v3/policy.pbjson.dart'; | ||
export 'src/proto/cedar/v3/policy.pbserver.dart'; | ||
export 'src/proto/cedar/v3/value.pb.dart'; | ||
export 'src/proto/cedar/v3/value.pbenum.dart'; | ||
export 'src/proto/cedar/v3/value.pbjson.dart'; | ||
export 'src/proto/cedar/v3/value.pbserver.dart'; | ||
export 'src/proto/corks/v1/cork.pb.dart'; | ||
export 'src/proto/corks/v1/cork.pbenum.dart'; | ||
export 'src/proto/corks/v1/cork.pbjson.dart'; | ||
export 'src/proto/corks/v1/cork.pbserver.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
import 'dart:async'; | ||
import 'dart:collection'; | ||
import 'dart:convert'; | ||
import 'dart:math'; | ||
import 'dart:typed_data'; | ||
|
||
import 'package:cedar/cedar.dart' as cedar; | ||
import 'package:corks/corks_proto.dart' as proto; | ||
import 'package:corks/src/interop/proto_interop.dart'; | ||
import 'package:corks/src/proto/google/protobuf/any.pb.dart' as proto; | ||
import 'package:corks/src/signer.dart'; | ||
import 'package:crypto/crypto.dart'; | ||
import 'package:meta/meta.dart'; | ||
|
||
@immutable | ||
final class Cork { | ||
const Cork._({ | ||
required this.id, | ||
required this.keyId, | ||
this.bearer, | ||
required List<SignedCaveat> caveats, | ||
required this.signature, | ||
}) : _caveats = caveats; | ||
|
||
factory Cork.parse(String base64) => Cork.decode(base64Url.decode(base64)); | ||
|
||
factory Cork.decode(Uint8List bytes) { | ||
final message = proto.Cork.fromBuffer(bytes); | ||
return Cork.fromProto(message); | ||
} | ||
|
||
factory Cork.fromProto(proto.Cork proto) => Cork._( | ||
id: Uint8List.fromList(proto.id), | ||
keyId: Uint8List.fromList(proto.keyId), | ||
bearer: proto.hasBearer() ? SignedBearer.fromProto(proto.bearer) : null, | ||
caveats: [ | ||
for (final caveat in proto.caveats) SignedCaveat.fromProto(caveat), | ||
], | ||
signature: Uint8List.fromList(proto.signature), | ||
); | ||
|
||
static CorkBuilder builder({ | ||
Uint8List? id, | ||
Bearer? bearer, | ||
}) => | ||
CorkBuilder( | ||
id: id, | ||
bearer: bearer, | ||
); | ||
|
||
final Uint8List id; | ||
final Uint8List keyId; | ||
final SignedBearer? bearer; | ||
final List<SignedCaveat> _caveats; | ||
List<SignedCaveat> get caveats => UnmodifiableListView(_caveats); | ||
final Uint8List signature; | ||
|
||
proto.Cork toProto() => proto.Cork( | ||
id: id, | ||
keyId: keyId, | ||
bearer: bearer?.toProto(), | ||
caveats: _caveats.map((caveat) => caveat.toProto()), | ||
signature: signature, | ||
); | ||
|
||
Uint8List encode() => toProto().writeToBuffer(); | ||
|
||
Future<bool> verify(Signer signer) async { | ||
if (Digest(signer.keyId) != Digest(keyId)) { | ||
return false; | ||
} | ||
await signer.sign(id); | ||
if (bearer != null) { | ||
// TODO(dnys1): https://github.com/dart-lang/sdk/issues/54664 | ||
if (!await bearer!.verify(signer)) { | ||
return false; | ||
} | ||
} | ||
for (final caveat in _caveats) { | ||
if (!await caveat.verify(signer)) { | ||
return false; | ||
} | ||
} | ||
return Digest(await signer.close()) == Digest(signature); | ||
} | ||
|
||
@override | ||
String toString() => base64Url.encode(encode()); | ||
} | ||
|
||
final class CorkBuilder { | ||
factory CorkBuilder({ | ||
Uint8List? id, | ||
Bearer? bearer, | ||
}) { | ||
if (id == null) { | ||
const nonceSize = 32; | ||
id = Uint8List(nonceSize); | ||
for (var i = 0; i < nonceSize; i++) { | ||
id[i] = _secureRandom.nextInt(256); | ||
} | ||
} | ||
return CorkBuilder._(id, bearer); | ||
} | ||
|
||
CorkBuilder._( | ||
this._id, | ||
this._bearer, | ||
); | ||
|
||
static final _secureRandom = Random.secure(); | ||
|
||
final Uint8List _id; | ||
final Bearer? _bearer; | ||
final List<Caveat> _caveats = []; | ||
|
||
void addPolicyCaveat(cedar.CedarPolicy policy) { | ||
if (policy.effect != cedar.CedarPolicyEffect.forbid) { | ||
throw ArgumentError('Policy must have effect "forbid"'); | ||
} | ||
_caveats.add(Caveat.policy(policy: policy)); | ||
} | ||
|
||
Future<Cork> build(Signer signer) async { | ||
await signer.sign(_id); | ||
|
||
SignedBearer? signedBearer; | ||
if (_bearer case final bearer?) { | ||
signedBearer = SignedBearer(await bearer.sign(signer)); | ||
} | ||
final signedCaveats = <SignedCaveat>[]; | ||
for (final caveat in _caveats) { | ||
signedCaveats.add(SignedCaveat(await caveat.sign(signer))); | ||
} | ||
return Cork._( | ||
id: _id, | ||
keyId: signer.keyId, | ||
bearer: signedBearer, | ||
caveats: signedCaveats, | ||
signature: await signer.close(), | ||
); | ||
} | ||
} | ||
|
||
@immutable | ||
sealed class Bearer with Signable { | ||
const Bearer(); | ||
|
||
factory Bearer.entity({ | ||
required cedar.CedarEntity entity, | ||
}) => | ||
EntityBearer(entity: entity); | ||
|
||
factory Bearer.entityId({ | ||
required cedar.CedarEntityId entityId, | ||
}) => | ||
EntityBearer(entity: cedar.CedarEntity(id: entityId)); | ||
} | ||
|
||
final class EntityBearer extends Bearer { | ||
const EntityBearer({ | ||
required this.entity, | ||
}); | ||
|
||
final cedar.CedarEntity entity; | ||
|
||
@override | ||
proto.Entity toProto() => entity.toProto(); | ||
} | ||
|
||
extension type SignedBearer(SignedBlock _block) implements SignedBlock { | ||
SignedBearer.fromProto(proto.SignedBlock proto) | ||
: _block = SignedBlock.fromProto(proto); | ||
|
||
Bearer get bearer { | ||
final any = proto.Any( | ||
typeUrl: typeUrl, | ||
value: block, | ||
); | ||
final entity = proto.Entity(); | ||
if (!any.canUnpackInto(entity)) { | ||
throw ArgumentError('Invalid bearer type: $typeUrl'); | ||
} | ||
any.unpackInto(entity); | ||
return Bearer.entity( | ||
entity: entity.fromProto(), | ||
); | ||
} | ||
} | ||
|
||
@immutable | ||
sealed class Caveat with Signable { | ||
const Caveat(); | ||
|
||
factory Caveat.policy({ | ||
required cedar.CedarPolicy policy, | ||
}) => | ||
PolicyCaveat(policy: policy); | ||
} | ||
|
||
final class PolicyCaveat extends Caveat { | ||
const PolicyCaveat({ | ||
required this.policy, | ||
}); | ||
|
||
final cedar.CedarPolicy policy; | ||
|
||
@override | ||
proto.Policy toProto() => policy.toProto(); | ||
} | ||
|
||
extension type SignedCaveat(SignedBlock _block) implements SignedBlock { | ||
SignedCaveat.fromProto(proto.SignedBlock proto) | ||
: _block = SignedBlock.fromProto(proto); | ||
|
||
Caveat get caveat { | ||
final any = proto.Any( | ||
typeUrl: typeUrl, | ||
value: block, | ||
); | ||
final policy = proto.Policy(); | ||
if (!any.canUnpackInto(policy)) { | ||
throw ArgumentError('Invalid caveat type: $typeUrl'); | ||
} | ||
any.unpackInto(policy); | ||
return PolicyCaveat(policy: policy.fromProto()); | ||
} | ||
} |
Oops, something went wrong.