DidcommEncryptedMessage.fromPlaintext constructor

DidcommEncryptedMessage.fromPlaintext({
  1. KeyWrapAlgorithm keyWrapAlgorithm = KeyWrapAlgorithm.ecdh1PU,
  2. EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.a256cbc,
  3. required Map<String, dynamic> senderPrivateKeyJwk,
  4. required List<Map<String, dynamic>> recipientPublicKeyJwk,
  5. required DidcommMessage plaintext,
})

Implementation

DidcommEncryptedMessage.fromPlaintext(
    {KeyWrapAlgorithm keyWrapAlgorithm = KeyWrapAlgorithm.ecdh1PU,
    EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.a256cbc,
    required Map<String, dynamic> senderPrivateKeyJwk,
    required List<Map<String, dynamic>> recipientPublicKeyJwk,
    required DidcommMessage plaintext}) {
  if (keyWrapAlgorithm == KeyWrapAlgorithm.ecdh1PU &&
      plaintext is DidcommPlaintextMessage &&
      plaintext.from == null) {
    throw Exception(
        'For authcrypted messages the from-header of the plaintext message must not be null');
  }
  Map<String, dynamic> jweHeader = {};
  jweHeader['enc'] = encryptionAlgorithm.value;
  jweHeader['alg'] = keyWrapAlgorithm.value;
  if (keyWrapAlgorithm == KeyWrapAlgorithm.ecdh1PU) {
    jweHeader['apu'] = removePaddingFromBase64(
        base64UrlEncode(utf8.encode(senderPrivateKeyJwk['kid'])));
  }
  jweHeader['skid'] = senderPrivateKeyJwk['kid'];
  String curve = senderPrivateKeyJwk['crv']!;
  String keyType = senderPrivateKeyJwk['kty']!;

  List<String> receiverKeyIds = [];
  for (Map<String, dynamic> key in recipientPublicKeyJwk) {
    if (key['crv'] == curve) {
      receiverKeyIds.add(key['kid']);
    }
  }
  receiverKeyIds.sort();
  String keyIdString = '';
  for (var keyId in receiverKeyIds) {
    keyIdString += '$keyId.';
  }
  if (keyIdString.isEmpty) {
    throw Exception('Cant find keys with matching crv parameter');
  }
  keyIdString = keyIdString.substring(0, keyIdString.length - 1);
  var apv = removePaddingFromBase64(
      base64UrlEncode(sha256.convert(utf8.encode(keyIdString)).bytes));
  jweHeader['apv'] = apv;

  //1) Resolve dids to get public keys

  //important: KeyAgreement section in diddoc
  //apu = key-id of sender (first entry in keyAgreementArray) -> entry istsef (if did) or id of key-Object

  //apv: get all key-ids in KeyAgreement _> search which match curve of sender key -> sort alphanumerical -> concat with . -> sha256 -> base64URL

  //2) look for Key-Type and generate Ephermal Key

  elliptic.Curve? c;
  Object epkPrivate;
  List<int> epkPublic = [];
  if (curve.startsWith('P') || curve.startsWith('secp256k1')) {
    if (curve == 'P-256') {
      c = elliptic.getP256();
    } else if (curve == 'P-384') {
      c = elliptic.getP384();
    } else if (curve == 'P-521') {
      c = elliptic.getP521();
    } else if (curve == 'secp256k1') {
      c = elliptic.getSecp256k1();
    } else {
      throw UnimplementedError();
    }

    epkPrivate = c.generatePrivateKey();
  } else if (curve.startsWith('X')) {
    var eKeyPair = x25519.generateKeyPair();
    epkPrivate = eKeyPair.privateKey;
    epkPublic = eKeyPair.publicKey;
  } else {
    throw UnimplementedError();
  }

  Map<String, dynamic> epkJwk = {'kty': keyType, 'crv': curve};
  if (epkPrivate is elliptic.PrivateKey) {
    epkJwk['x'] = removePaddingFromBase64(
        base64UrlEncode(intToBytes(epkPrivate.publicKey.X)));
    epkJwk['y'] = removePaddingFromBase64(
        base64UrlEncode(intToBytes(epkPrivate.publicKey.Y)));
  } else if (epkPrivate is List<int>) {
    epkJwk['x'] = removePaddingFromBase64(base64UrlEncode(epkPublic));
  } else {
    throw Exception('Unknown Key type');
  }
  jweHeader['epk'] = epkJwk;

  //3) generate symmetric CEK
  SymmetricKey cek;
  if (encryptionAlgorithm == EncryptionAlgorithm.a256cbc) {
    cek = SymmetricKey.generate(512);
  } else {
    cek = SymmetricKey.generate(256);
  }
  Encrypter e;
  if (encryptionAlgorithm == EncryptionAlgorithm.a256cbc) {
    e = cek.createEncrypter(algorithms.encryption.aes.cbcWithHmac.sha512);
  } else if (encryptionAlgorithm == EncryptionAlgorithm.a256gcm) {
    e = cek.createEncrypter(algorithms.encryption.aes.gcm);
  } else {
    throw UnimplementedError();
  }

  //4) Generate IV

  //5) build aad ( ASCII(BASE64URL(UTF8(JWE Protected Header))) )
  var aad = ascii.encode(removePaddingFromBase64(
      base64UrlEncode(utf8.encode(jsonEncode(jweHeader)))));
  //6) encrypt and get tag
  var encrypted = e.encrypt(
      Uint8List.fromList(utf8.encode(plaintext.toString())),
      additionalAuthenticatedData: aad);

  // 7) Encrypt cek for all recipients
  List<Map<String, dynamic>> recipients = [];
  for (var key in recipientPublicKeyJwk) {
    if (key['crv'] == curve) {
      Map<String, dynamic> r = {};
      r['header'] = {'kid': key['kid']};
      var encryptedCek = _encryptSymmetricKey(
          cek, keyWrapAlgorithm.value, curve, key, epkPrivate, apv,
          c: c,
          senderPrivateKeyJwk: senderPrivateKeyJwk,
          tag: encrypted.authenticationTag);
      r['encrypted_key'] =
          removePaddingFromBase64(base64UrlEncode(encryptedCek.data));
      recipients.add(r);
    }
  }

  //9) put everything together

  protectedHeader = ascii.decode(aad);
  tag =
      removePaddingFromBase64(base64UrlEncode(encrypted.authenticationTag!));
  iv = removePaddingFromBase64(
      base64UrlEncode(encrypted.initializationVector!));
  ciphertext = removePaddingFromBase64(base64UrlEncode(encrypted.data));
  this.recipients = recipients;
}