8000 fix(cbio): require injected vendor material instead of compiled constants by shroominic · Pull Request #166 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// Masked bytes are logged; the authentication frame's plaintext is not, because
// it carries the link credential.
import 'dart:async';
import 'dart:io' show Platform;

import 'package:cgm_cbio/cgm_cbio.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart' as fbp;
Expand All @@ -40,6 +41,23 @@ const int _maxWrites = 9;

const String _targetDeviceId = String.fromEnvironment('CBIO_TARGET_DEVICE_ID');

8000 /// Vendor material for this run.
///
/// Nothing in the repository carries the stream key or the link credential, so
/// the harness reads them from the process environment or from `--dart-define`
/// and aborts before touching the radio when they are absent. Provide them
/// without writing them to a committed file, for example with
/// `--dart-define-from-file` against a git-ignored local file.
final CbioMapCredentialSource _credentials =
CbioMapCredentialSource(<String, String>{
...Platform.environment,
if (cbioStreamKeyHex.isNotEmpty) cbioStreamKeyDefine: cbioStreamKeyHex,
if (cbioAuthMaterialHex.isNotEmpty)
cbioAuthMaterialDefine: cbioAuthMaterialHex,
if (cbioAuthTriggerHex.isNotEmpty)
cbioAuthTriggerDefine: cbioAuthTriggerHex,
});

/// First raw (`08`) index to request. Zero starts a full history replay; a
/// higher value resumes partway so a bounded window can reach the newest
/// stored record instead of re-reading the whole archive.
Expand Down Expand Up @@ -67,6 +85,15 @@ void main() {
}

Future<void> _runSession() async {
if (!_credentials.isConfigured) {
_emit(
'CBIO-A abort=vendor-material-missing '
'missing=${_credentials.missing.join(",")}',
);
return;
}
final vendor = _credentials.read();

final targetId = _targetDeviceId.isNotEmpty
? _targetDeviceId
: await _acquireTargetId();
Expand Down Expand Up @@ -125,7 +152,7 @@ Future<void> _runSession() async {

subscription = notify.onValueReceived.listen((bytes) {
masked.add((elapsed(), List<int>.from(bytes)));
final plaintext = unmaskCbioFrame(bytes);
final plaintext = unmaskCbioFrame(bytes, key: vendor.streamKey);
_emit(
'CBIO-A notify t=${elapsed()} masked=${_hex(bytes)} '
'plaintext=${_hex(plaintext)}',
Expand Down Expand Up @@ -193,7 +220,7 @@ Future<void> _runSession() async {
/// Returns true when the newest unmasked reply is an auth success ACK.
bool authSucceeded() {
for (final (_, bytes) in masked.reversed) {
final plaintext = unmaskCbioFrame(bytes);
final plaintext = unmaskCbioFrame(bytes, key: vendor.streamKey);
if (plaintext.length != 5) continue;
if (plaintext.fold<int>(0, (a, b) => a + b) & 255 != 0) continue;
if (plaintext[1] == 0x01) {
Expand All @@ -215,7 +242,11 @@ Future<void> _runSession() async {
var authenticated = false;
for (final (source, octets) in addressCandidates) {
_emit('CBIO-A auth-attempt source=$source');
final authFrame = buildMaskedCbioAuthentication(octets);
final authFrame = buildMaskedCbioAuthentication(
octets,
key: vendor.streamKey,
material: vendor.authMaterial,
);
await send(authFrame, 'auth-$source');
final deadline = DateTime.now().add(_authWindow);
while (DateTime.now().isBefore(deadline)) {
Expand All @@ -240,14 +271,18 @@ Future<void> _runSession() async {
await send(
buildMaskedCbioClock(
DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000,
key: vendor.streamKey,
),
'clock',
);

final beforeReads = masked.length;
await send(buildMaskedCbioGlucoseQuery(0), 'glucose-0a-index0');
await send(
buildMaskedCbioRawQuery(_rawStartIndex),
buildMaskedCbioGlucoseQuery(0, key: vendor.streamKey),
'glucose-0a-index0',
);
await send(
buildMaskedCbioRawQuery(_rawStartIndex, key: vendor.streamKey),
'raw-08-index$_rawStartIndex',
);

Expand All @@ -261,7 +296,7 @@ Future<void> _runSession() async {
final rawRecords = <CbioRawRecord>[];
final plaintextFrames = <List<int>>[];
for (final (_, bytes) in masked.sublist(beforeReads)) {
final plaintext = unmaskCbioFrame(bytes);
final plaintext = unmaskCbioFrame(bytes, key: vendor.streamKey);
plaintextFrames.add(plaintext);
try {
final batch = parseCbioGlucoseBatch(plaintext);
Expand Down
35 changes: 32 additions & 3 deletions packages/cgm_cbio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ only. The package contains no activation/clock builder or live write path.
`buildCbioGlucoseQuery` and `buildCbioInformationQuery` reproduce the vendor's
recovered V120 read frames (`06 0A LE16(index) 00 00 C` and `03 F0 selector C`).
`parseCbioGlucoseBatch` decodes only the plaintext `0A` batch layout and refuses
the `08` raw-data layout. `cbio_crypto.dart` carries the vendor's static
per-frame stream mask, `cbio_vendor_frames.dart` builds the masked link frames
the sensor actually accepts, and `cbio_history_archive.dart` assembles the `08`
the `08` raw-data layout. `cbio_vendor_frames.dart` builds the masked link
frames the sensor actually accepts, and `cbio_history_archive.dart` assembles the `08`
record stream into an ordered archive with gap and overlap detection.
`CbioGlucoseSyncSession` adds bounded live polling at the newest index and
bounded history paging from the oldest record, with explicit `noRecords`,
Expand All @@ -62,6 +61,36 @@ streams a contiguous raw archive up to the present; the `0A` packed field is
zero throughout, so only `08` carries usable content. See the
[live record](../../docs/testing/cbio-gs1-glucose-live.md).

## Vendor material is injected, never compiled

The vendor link needs three values that this package does **not** carry: the
16-byte RC4 stream key, the 16-byte link credential inside the authentication
frame, and the five-byte authentication prompt. They come from the vendor
artifact described in the evidence record, and storing them in this public
repository is not acceptable.

`cbio_credentials.dart` defines the boundary. `CbioCredentials` validates and
holds the three values and never renders them in `toString`; a
`CbioCredentialSource` supplies them and fails closed with
`CbioCredentialUnavailable` when they are absent or malformed.
`CbioMapCredentialSource` reads them from a supplied string map (a process
environment) and `CbioDefineCredentialSource` from `--dart-define` values.
There is no compiled default.

`cbioRc4Keystream`, `maskCbioFrame`, `unmaskCbioFrame`, and every builder in
`cbio_vendor_frames.dart` take the key and material as required arguments, so a
caller that has not resolved material cannot compile a call. Run a build or a
bench that needs the live link with a git-ignored define file:

```sh
flutter run --dart-define-from-file=cbio_vendor.local.json
```

where the file supplies `CBIO_VENDOR_STREAM_KEY_HEX`,
`CBIO_VENDOR_AUTH_MATERIAL_HEX`, and `CBIO_VENDOR_AUTH_TRIGGER_HEX`. Tests and
fixtures use synthetic material of the same shape; the real values are never
checked in.

Run package checks from this directory with the pinned Dart SDK:

```sh
Expand Down
1 change: 1 addition & 0 deletions packages/cgm_cbio/lib/cgm_cbio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
library;

export 'src/cbio_driver.dart';
export 'src/cbio_credentials.dart';
export 'src/cbio_frames.dart';
export 'src/cbio_history_archive.dart';
export 'src/cbio_vendor_frames.dart';
Expand Down
Loading
Loading
0