From a843a895b61789e0a87cb9e3e5ebc431c1017bd4 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Fri, 8 Mar 2024 21:23:26 -0800 Subject: [PATCH] Improve security of corks --- packages/corks/lib/src/cork.dart | 138 ++++++-------- .../corks/lib/src/proto/corks/v1/cork.pb.dart | 179 +++++------------- .../lib/src/proto/corks/v1/cork.pbjson.dart | 53 ++---- packages/corks/lib/src/signer.dart | 91 +++++---- packages/corks/proto/corks/v1/cork.proto | 23 +-- 5 files changed, 181 insertions(+), 303 deletions(-) diff --git a/packages/corks/lib/src/cork.dart b/packages/corks/lib/src/cork.dart index 96df444c..8aaf96d7 100644 --- a/packages/corks/lib/src/cork.dart +++ b/packages/corks/lib/src/cork.dart @@ -5,10 +5,9 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:cedar_core/cedar_core.dart' as cedar; +import 'package:corks/corks_proto.dart' as proto; import 'package:corks/src/interop/proto_interop.dart'; -import 'package:corks/src/interop/to_proto.dart'; -import 'package:corks/src/proto/cedar/v3/policy.pb.dart' as proto; -import 'package:corks/src/proto/corks/v1/cork.pb.dart' as proto; +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'; @@ -70,14 +69,16 @@ final class Cork { if (Digest(signer.keyId) != Digest(keyId)) { return false; } - - await signer.sign(SignableBytes(id)); - - if (bearer case final bearer?) { - await signer.sign(bearer.block); + await signer.sign(id); + if (bearer != null) { + if (!await bearer.verify(signer)) { + return false; + } } for (final caveat in _caveats) { - await signer.sign(caveat.block); + if (!await caveat.verify(signer)) { + return false; + } } return Digest(await signer.close()) == Digest(signature); } @@ -112,19 +113,23 @@ final class CorkBuilder { final Bearer? _bearer; final List _caveats = []; - void addPolicyCaveat(cedar.CedarPolicy policy) => - _caveats.add(_PolicyCaveat(policy: policy.toProto())); + 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 build(Signer signer) async { - await signer.sign(SignableBytes(_id)); + await signer.sign(_id); SignedBearer? signedBearer; if (_bearer case final bearer?) { - signedBearer = await signer.sign(bearer); + signedBearer = SignedBearer(await bearer.sign(signer)); } final signedCaveats = []; for (final caveat in _caveats) { - signedCaveats.add(await signer.sign(caveat)); + signedCaveats.add(SignedCaveat(await caveat.sign(signer))); } return Cork._( id: _id, @@ -137,7 +142,7 @@ final class CorkBuilder { } @immutable -sealed class Bearer implements Signable { +sealed class Bearer with Signable { const Bearer(); factory Bearer.entity({ @@ -159,87 +164,64 @@ final class EntityBearer extends Bearer { final cedar.CedarEntity entity; @override - Uint8List encode() => entity.toProto().writeToBuffer(); - - @override - SignedBearer signed(Uint8List signature) => SignedBearer( - block: this, - signature: signature, - ); + proto.Entity toProto() => entity.toProto(); } -final class SignedBearer implements Signed, ToProto { - const SignedBearer({ - required this.block, - required this.signature, - }); - - factory SignedBearer.fromProto(proto.Bearer proto) => SignedBearer( - block: EntityBearer(entity: proto.entity.fromProto()), - signature: Uint8List.fromList(proto.signature), - ); - - @override - final Bearer block; +extension type SignedBearer(SignedBlock _block) implements SignedBlock { + SignedBearer.fromProto(proto.SignedBlock proto) + : _block = SignedBlock.fromProto(proto); - @override - final Uint8List signature; - - @override - proto.Bearer toProto() { - final message = proto.Bearer(signature: signature); - return switch (block) { - EntityBearer(:final entity) => message..entity = entity.toProto(), - }; + 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 implements Signable { +sealed class Caveat with Signable { const Caveat(); + + factory Caveat.policy({ + required cedar.CedarPolicy policy, + }) => + PolicyCaveat(policy: policy); } -final class _PolicyCaveat extends Caveat { - const _PolicyCaveat({ +final class PolicyCaveat extends Caveat { + const PolicyCaveat({ required this.policy, }); - final proto.Policy policy; + final cedar.CedarPolicy policy; @override - SignedCaveat signed(Uint8List signature) => SignedCaveat( - block: this, - signature: signature, - ); - - @override - Uint8List encode() => policy.writeToBuffer(); + proto.Policy toProto() => policy.toProto(); } -final class SignedCaveat implements Signed, ToProto { - const SignedCaveat({ - required this.block, - required this.signature, - }); +extension type SignedCaveat(SignedBlock _block) implements SignedBlock { + SignedCaveat.fromProto(proto.SignedBlock proto) + : _block = SignedBlock.fromProto(proto); - factory SignedCaveat.fromProto(proto.Caveat proto) => SignedCaveat( - block: _PolicyCaveat(policy: proto.policy), - signature: Uint8List.fromList(proto.signature), - ); - - @override - final Caveat block; - - @override - final Uint8List signature; - - @override - proto.Caveat toProto() { - final message = proto.Caveat( - signature: signature, + Caveat get caveat { + final any = proto.Any( + typeUrl: typeUrl, + value: block, ); - return switch (block) { - _PolicyCaveat(:final policy) => message..policy = policy, - }; + final policy = proto.Policy(); + if (!any.canUnpackInto(policy)) { + throw ArgumentError('Invalid caveat type: $typeUrl'); + } + any.unpackInto(policy); + return PolicyCaveat(policy: policy.fromProto()); } } diff --git a/packages/corks/lib/src/proto/corks/v1/cork.pb.dart b/packages/corks/lib/src/proto/corks/v1/cork.pb.dart index d9923cd9..b4c15a56 100644 --- a/packages/corks/lib/src/proto/corks/v1/cork.pb.dart +++ b/packages/corks/lib/src/proto/corks/v1/cork.pb.dart @@ -13,15 +13,12 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; -import '../../cedar/v3/entity.pb.dart' as $4; -import '../../cedar/v3/policy.pb.dart' as $5; - class Cork extends $pb.GeneratedMessage { factory Cork({ $core.List<$core.int>? id, $core.List<$core.int>? keyId, - Bearer? bearer, - $core.Iterable? caveats, + SignedBlock? bearer, + $core.Iterable? caveats, $core.List<$core.int>? signature, }) { final $result = create(); @@ -49,8 +46,8 @@ class Cork extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Cork', package: const $pb.PackageName(_omitMessageNames ? '' : 'corks.v1'), createEmptyInstance: create) ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'id', $pb.PbFieldType.OY) ..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'keyId', $pb.PbFieldType.OY) - ..aOM(3, _omitFieldNames ? '' : 'bearer', subBuilder: Bearer.create) - ..pc(4, _omitFieldNames ? '' : 'caveats', $pb.PbFieldType.PM, subBuilder: Caveat.create) + ..aOM(3, _omitFieldNames ? '' : 'bearer', subBuilder: SignedBlock.create) + ..pc(4, _omitFieldNames ? '' : 'caveats', $pb.PbFieldType.PM, subBuilder: SignedBlock.create) ..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY) ..hasRequiredFields = false ; @@ -95,18 +92,18 @@ class Cork extends $pb.GeneratedMessage { void clearKeyId() => clearField(2); @$pb.TagNumber(3) - Bearer get bearer => $_getN(2); + SignedBlock get bearer => $_getN(2); @$pb.TagNumber(3) - set bearer(Bearer v) { setField(3, v); } + set bearer(SignedBlock v) { setField(3, v); } @$pb.TagNumber(3) $core.bool hasBearer() => $_has(2); @$pb.TagNumber(3) void clearBearer() => clearField(3); @$pb.TagNumber(3) - Bearer ensureBearer() => $_ensure(2); + SignedBlock ensureBearer() => $_ensure(2); @$pb.TagNumber(4) - $core.List get caveats => $_getList(3); + $core.List get caveats => $_getList(3); @$pb.TagNumber(5) $core.List<$core.int> get signature => $_getN(4); @@ -118,116 +115,32 @@ class Cork extends $pb.GeneratedMessage { void clearSignature() => clearField(5); } -enum Bearer_Bearer { - entity, - notSet -} - -class Bearer extends $pb.GeneratedMessage { - factory Bearer({ +class SignedBlock extends $pb.GeneratedMessage { + factory SignedBlock({ + $core.List<$core.int>? block, + $core.List<$core.int>? typeUrl, $core.List<$core.int>? signature, - $4.Entity? entity, }) { final $result = create(); - if (signature != null) { - $result.signature = signature; + if (block != null) { + $result.block = block; } - if (entity != null) { - $result.entity = entity; + if (typeUrl != null) { + $result.typeUrl = typeUrl; } - return $result; - } - Bearer._() : super(); - factory Bearer.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory Bearer.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); - - static const $core.Map<$core.int, Bearer_Bearer> _Bearer_BearerByTag = { - 2 : Bearer_Bearer.entity, - 0 : Bearer_Bearer.notSet - }; - static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Bearer', package: const $pb.PackageName(_omitMessageNames ? '' : 'corks.v1'), createEmptyInstance: create) - ..oo(0, [2]) - ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY) - ..aOM<$4.Entity>(2, _omitFieldNames ? '' : 'entity', subBuilder: $4.Entity.create) - ..hasRequiredFields = false - ; - - @$core.Deprecated( - 'Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - Bearer clone() => Bearer()..mergeFromMessage(this); - @$core.Deprecated( - 'Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - Bearer copyWith(void Function(Bearer) updates) => super.copyWith((message) => updates(message as Bearer)) as Bearer; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static Bearer create() => Bearer._(); - Bearer createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static Bearer getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static Bearer? _defaultInstance; - - Bearer_Bearer whichBearer() => _Bearer_BearerByTag[$_whichOneof(0)]!; - void clearBearer() => clearField($_whichOneof(0)); - - @$pb.TagNumber(1) - $core.List<$core.int> get signature => $_getN(0); - @$pb.TagNumber(1) - set signature($core.List<$core.int> v) { $_setBytes(0, v); } - @$pb.TagNumber(1) - $core.bool hasSignature() => $_has(0); - @$pb.TagNumber(1) - void clearSignature() => clearField(1); - - @$pb.TagNumber(2) - $4.Entity get entity => $_getN(1); - @$pb.TagNumber(2) - set entity($4.Entity v) { setField(2, v); } - @$pb.TagNumber(2) - $core.bool hasEntity() => $_has(1); - @$pb.TagNumber(2) - void clearEntity() => clearField(2); - @$pb.TagNumber(2) - $4.Entity ensureEntity() => $_ensure(1); -} - -enum Caveat_Block { - policy, - notSet -} - -class Caveat extends $pb.GeneratedMessage { - factory Caveat({ - $core.List<$core.int>? signature, - $5.Policy? policy, - }) { - final $result = create(); if (signature != null) { $result.signature = signature; } - if (policy != null) { - $result.policy = policy; - } return $result; } - Caveat._() : super(); - factory Caveat.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory Caveat.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); - - static const $core.Map<$core.int, Caveat_Block> _Caveat_BlockByTag = { - 2 : Caveat_Block.policy, - 0 : Caveat_Block.notSet - }; - static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Caveat', package: const $pb.PackageName(_omitMessageNames ? '' : 'corks.v1'), createEmptyInstance: create) - ..oo(0, [2]) - ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY) - ..aOM<$5.Policy>(2, _omitFieldNames ? '' : 'policy', subBuilder: $5.Policy.create) + SignedBlock._() : super(); + factory SignedBlock.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory SignedBlock.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SignedBlock', package: const $pb.PackageName(_omitMessageNames ? '' : 'corks.v1'), createEmptyInstance: create) + ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'block', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'typeUrl', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY) ..hasRequiredFields = false ; @@ -235,45 +148,49 @@ class Caveat extends $pb.GeneratedMessage { 'Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Will be removed in next major version') - Caveat clone() => Caveat()..mergeFromMessage(this); + SignedBlock clone() => SignedBlock()..mergeFromMessage(this); @$core.Deprecated( 'Using this can add significant overhead to your binary. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Will be removed in next major version') - Caveat copyWith(void Function(Caveat) updates) => super.copyWith((message) => updates(message as Caveat)) as Caveat; + SignedBlock copyWith(void Function(SignedBlock) updates) => super.copyWith((message) => updates(message as SignedBlock)) as SignedBlock; $pb.BuilderInfo get info_ => _i; @$core.pragma('dart2js:noInline') - static Caveat create() => Caveat._(); - Caveat createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); + static SignedBlock create() => SignedBlock._(); + SignedBlock createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); @$core.pragma('dart2js:noInline') - static Caveat getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static Caveat? _defaultInstance; - - Caveat_Block whichBlock() => _Caveat_BlockByTag[$_whichOneof(0)]!; - void clearBlock() => clearField($_whichOneof(0)); + static SignedBlock getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static SignedBlock? _defaultInstance; @$pb.TagNumber(1) - $core.List<$core.int> get signature => $_getN(0); + $core.List<$core.int> get block => $_getN(0); @$pb.TagNumber(1) - set signature($core.List<$core.int> v) { $_setBytes(0, v); } + set block($core.List<$core.int> v) { $_setBytes(0, v); } @$pb.TagNumber(1) - $core.bool hasSignature() => $_has(0); + $core.bool hasBlock() => $_has(0); @$pb.TagNumber(1) - void clearSignature() => clearField(1); + void clearBlock() => clearField(1); @$pb.TagNumber(2) - $5.Policy get policy => $_getN(1); + $core.List<$core.int> get typeUrl => $_getN(1); @$pb.TagNumber(2) - set policy($5.Policy v) { setField(2, v); } + set typeUrl($core.List<$core.int> v) { $_setBytes(1, v); } @$pb.TagNumber(2) - $core.bool hasPolicy() => $_has(1); + $core.bool hasTypeUrl() => $_has(1); @$pb.TagNumber(2) - void clearPolicy() => clearField(2); - @$pb.TagNumber(2) - $5.Policy ensurePolicy() => $_ensure(1); + void clearTypeUrl() => clearField(2); + + @$pb.TagNumber(3) + $core.List<$core.int> get signature => $_getN(2); + @$pb.TagNumber(3) + set signature($core.List<$core.int> v) { $_setBytes(2, v); } + @$pb.TagNumber(3) + $core.bool hasSignature() => $_has(2); + @$pb.TagNumber(3) + void clearSignature() => clearField(3); } diff --git a/packages/corks/lib/src/proto/corks/v1/cork.pbjson.dart b/packages/corks/lib/src/proto/corks/v1/cork.pbjson.dart index d679e58f..edcbef9e 100644 --- a/packages/corks/lib/src/proto/corks/v1/cork.pbjson.dart +++ b/packages/corks/lib/src/proto/corks/v1/cork.pbjson.dart @@ -19,8 +19,8 @@ const Cork$json = { '2': [ {'1': 'id', '3': 1, '4': 1, '5': 12, '10': 'id'}, {'1': 'key_id', '3': 2, '4': 1, '5': 12, '10': 'keyId'}, - {'1': 'bearer', '3': 3, '4': 1, '5': 11, '6': '.corks.v1.Bearer', '9': 0, '10': 'bearer', '17': true}, - {'1': 'caveats', '3': 4, '4': 3, '5': 11, '6': '.corks.v1.Caveat', '10': 'caveats'}, + {'1': 'bearer', '3': 3, '4': 1, '5': 11, '6': '.corks.v1.SignedBlock', '9': 0, '10': 'bearer', '17': true}, + {'1': 'caveats', '3': 4, '4': 3, '5': 11, '6': '.corks.v1.SignedBlock', '10': 'caveats'}, {'1': 'signature', '3': 5, '4': 1, '5': 12, '10': 'signature'}, ], '8': [ @@ -30,42 +30,23 @@ const Cork$json = { /// Descriptor for `Cork`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List corkDescriptor = $convert.base64Decode( - 'CgRDb3JrEg4KAmlkGAEgASgMUgJpZBIVCgZrZXlfaWQYAiABKAxSBWtleUlkEi0KBmJlYXJlch' - 'gDIAEoCzIQLmNvcmtzLnYxLkJlYXJlckgAUgZiZWFyZXKIAQESKgoHY2F2ZWF0cxgEIAMoCzIQ' - 'LmNvcmtzLnYxLkNhdmVhdFIHY2F2ZWF0cxIcCglzaWduYXR1cmUYBSABKAxSCXNpZ25hdHVyZU' - 'IJCgdfYmVhcmVy'); - -@$core.Deprecated('Use bearerDescriptor instead') -const Bearer$json = { - '1': 'Bearer', - '2': [ - {'1': 'signature', '3': 1, '4': 1, '5': 12, '10': 'signature'}, - {'1': 'entity', '3': 2, '4': 1, '5': 11, '6': '.cedar.v3.Entity', '9': 0, '10': 'entity'}, - ], - '8': [ - {'1': 'bearer'}, - ], -}; - -/// Descriptor for `Bearer`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List bearerDescriptor = $convert.base64Decode( - 'CgZCZWFyZXISHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmUSKgoGZW50aXR5GAIgASgLMh' - 'AuY2VkYXIudjMuRW50aXR5SABSBmVudGl0eUIICgZiZWFyZXI='); - -@$core.Deprecated('Use caveatDescriptor instead') -const Caveat$json = { - '1': 'Caveat', + 'CgRDb3JrEg4KAmlkGAEgASgMUgJpZBIVCgZrZXlfaWQYAiABKAxSBWtleUlkEjIKBmJlYXJlch' + 'gDIAEoCzIVLmNvcmtzLnYxLlNpZ25lZEJsb2NrSABSBmJlYXJlcogBARIvCgdjYXZlYXRzGAQg' + 'AygLMhUuY29ya3MudjEuU2lnbmVkQmxvY2tSB2NhdmVhdHMSHAoJc2lnbmF0dXJlGAUgASgMUg' + 'lzaWduYXR1cmVCCQoHX2JlYXJlcg=='); + +@$core.Deprecated('Use signedBlockDescriptor instead') +const SignedBlock$json = { + '1': 'SignedBlock', '2': [ - {'1': 'signature', '3': 1, '4': 1, '5': 12, '10': 'signature'}, - {'1': 'policy', '3': 2, '4': 1, '5': 11, '6': '.cedar.v3.Policy', '9': 0, '10': 'policy'}, - ], - '8': [ - {'1': 'block'}, + {'1': 'block', '3': 1, '4': 1, '5': 12, '10': 'block'}, + {'1': 'type_url', '3': 2, '4': 1, '5': 12, '10': 'typeUrl'}, + {'1': 'signature', '3': 3, '4': 1, '5': 12, '10': 'signature'}, ], }; -/// Descriptor for `Caveat`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List caveatDescriptor = $convert.base64Decode( - 'CgZDYXZlYXQSHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmUSKgoGcG9saWN5GAIgASgLMh' - 'AuY2VkYXIudjMuUG9saWN5SABSBnBvbGljeUIHCgVibG9jaw=='); +/// Descriptor for `SignedBlock`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List signedBlockDescriptor = $convert.base64Decode( + 'CgtTaWduZWRCbG9jaxIUCgVibG9jaxgBIAEoDFIFYmxvY2sSGQoIdHlwZV91cmwYAiABKAxSB3' + 'R5cGVVcmwSHAoJc2lnbmF0dXJlGAMgASgMUglzaWduYXR1cmU='); diff --git a/packages/corks/lib/src/signer.dart b/packages/corks/lib/src/signer.dart index bcd065a8..961bceb3 100644 --- a/packages/corks/lib/src/signer.dart +++ b/packages/corks/lib/src/signer.dart @@ -1,47 +1,57 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:typed_data'; import 'package:corks/corks_proto.dart' as proto; +import 'package:corks/src/interop/to_proto.dart'; import 'package:crypto/crypto.dart'; import 'package:meta/meta.dart'; -abstract interface class Signable { - S signed(Uint8List signature); - Uint8List encode(); -} - -final class SignableBytes implements Signable { - const SignableBytes(this.bytes); - - final Uint8List bytes; - - @override - Uint8List encode() => bytes; - - @override - SignedBytes signed(Uint8List signature) => SignedBytes( - block: this, - signature: signature, - ); +mixin Signable implements ToProto { + Future sign(Signer signer) async { + final proto = toProto(); + final typeUrl = utf8.encode(proto.info_.qualifiedMessageName); + await signer.sign(typeUrl); + final bytes = proto.writeToBuffer(); + final signature = await signer.sign(bytes); + return SignedBlock( + block: bytes, + typeUrl: proto.info_.qualifiedMessageName, + signature: signature, + ); + } } @immutable -abstract interface class Signed { - Signable get block; - Uint8List get signature; -} - -final class SignedBytes implements Signed { - const SignedBytes({ +final class SignedBlock implements ToProto { + const SignedBlock({ required this.block, + required this.typeUrl, required this.signature, }); - @override - final SignableBytes block; + factory SignedBlock.fromProto(proto.SignedBlock proto) => SignedBlock( + block: Uint8List.fromList(proto.block), + typeUrl: utf8.decode(proto.typeUrl), + signature: Uint8List.fromList(proto.signature), + ); - @override + Future verify(Signer signer) async { + await signer.sign(utf8.encode(typeUrl)); + final signature = await signer.sign(block); + return Digest(signature) == Digest(signature); + } + + final Uint8List block; + final String typeUrl; final Uint8List signature; + + @override + proto.SignedBlock toProto() => proto.SignedBlock( + block: block, + typeUrl: utf8.encode(typeUrl), + signature: signature, + ); } abstract interface class Signer { @@ -49,36 +59,35 @@ abstract interface class Signer { const Signer._(); Uint8List get keyId; - Future sign(Signable block); + Future sign(Uint8List bytes); Future close(); } final class _Signer extends Signer { _Signer(this.keyId, Uint8List key) - : _hmac = Hmac(sha256, key), + : hmac = Hmac(sha256, key), super._(); @override final Uint8List keyId; - Hmac _hmac; - late Uint8List _signature; - var _closed = false; + Hmac hmac; + late Uint8List signature; + var closed = false; @override - Future sign(Signable block) async { - if (_closed) { + Future sign(Uint8List bytes) async { + if (closed) { throw StateError('Signer is closed'); } - final bytes = block.encode(); - _signature = await Future(() => _hmac.convert(bytes).bytes as Uint8List); - _hmac = Hmac(sha256, _signature); - return block.signed(_signature); + signature = await Future(() => hmac.convert(bytes).bytes as Uint8List); + hmac = Hmac(sha256, signature); + return signature; } @override Future close() async { - _closed = true; - return _signature; + closed = true; + return signature; } } diff --git a/packages/corks/proto/corks/v1/cork.proto b/packages/corks/proto/corks/v1/cork.proto index 179c0725..8d8d1c48 100644 --- a/packages/corks/proto/corks/v1/cork.proto +++ b/packages/corks/proto/corks/v1/cork.proto @@ -2,27 +2,16 @@ syntax = "proto3"; package corks.v1; -import "cedar/v3/entity.proto"; -import "cedar/v3/policy.proto"; - message Cork { bytes id = 1; bytes key_id = 2; - optional Bearer bearer = 3; - repeated Caveat caveats = 4; + optional SignedBlock bearer = 3; + repeated SignedBlock caveats = 4; bytes signature = 5; } -message Bearer { - bytes signature = 1; - oneof bearer { - cedar.v3.Entity entity = 2; - } -} - -message Caveat { - bytes signature = 1; - oneof block { - cedar.v3.Policy policy = 2; - } +message SignedBlock { + bytes block = 1; + bytes type_url = 2; + bytes signature = 3; }