sendEnvelope method

Future<void> sendEnvelope(
  1. KeyPackage to,
  2. String appNamespace,
  3. Map<String, dynamic> payload
)

Encrypts payload to to's key package and stores it for delivery, addressed through appNamespace.

Both this client's and the recipient's enrollments must be authorized for appNamespace — the atServer enforces this on write (here) and on read/sync (recipient side) respectively.

Throws StateError if to advertises no key with a mutually-supported algorithm.

Implementation

Future<void> sendEnvelope(
  KeyPackage to,
  String appNamespace,
  Map<String, dynamic> payload,
) async {
  final PackageKey? recipientKey = to.bestKeyFor(SecretSharingAlgos.keyAlgos);
  if (recipientKey == null) {
    throw StateError(
        'Key package ${to.enrollmentId}/${to.apkamId} advertises no key '
        'with a supported algorithm (supported: '
        '${SecretSharingAlgos.keyAlgos})');
  }

  // X-Wing HPKE: pqSeal encapsulates to the recipient's published key and
  // wraps the payload (AEAD over an HKDF key schedule) into one envelope —
  // nothing secret travels except that sealed envelope.
  final Uint8List sealed = await pqSeal(
    XWingPureDartAlgo.instance,
    base64Decode(recipientKey.pub),
    Uint8List.fromList(utf8.encode(jsonEncode(payload))),
    info: _sealInfo,
  );

  final envelope = SecretEnvelope(
    fromKpid: kpid,
    fromEnrollmentId: enrollmentId,
    toKpid: recipientKey.kid,
    suite: SecretSharingAlgos.xWingHpke,
    kid: recipientKey.kid,
    sealed: base64Encode(sealed),
  );
  // pqSeal's AEAD authenticates the payload; the APKAM signature over the
  // whole envelope additionally authenticates the SENDER (receivers still
  // verify before decrypting).
  final String signedJson = await wrapAndSignAndJsonEncode(envelope.toJson());

  final atKey = AtKey()
    ..key = '${Uuid().v4()}.${recipientKey.kid}.$envelopeKeyMarker'
    ..namespace = appNamespace
    ..sharedBy = atClient.getCurrentAtSign()
    ..metadata.ttl = envelopeTtl.inMilliseconds;
  // shouldEncrypt=false: the value is already end-to-end encrypted to the
  // recipient; self-key encryption would only obscure that the payload is
  // our own ciphertext. The value is raw JSON (never whole-value base64) so
  // that pre-fix readers' legacy decrypt fallback also returns it untouched.
  await atClient.put(
    atKey,
    signedJson,
    putRequestOptions: PutRequestOptions()..shouldEncrypt = false,
  );
  logger.info('Stored secret envelope $atKey for kpid ${recipientKey.kid}');

  if (sendWakeUpNotification) {
    await _sendWakeUp(atKey, appNamespace);
  }
}