cmsAssembleSignedData function

Uint8List cmsAssembleSignedData({
  1. required Uint8List signedAttributes,
  2. required Uint8List signature,
  3. required List<Uint8List> certificates,
  4. Hash hash = crypto.sha256,
  5. List<Uint8List> unsignedAttributes = const [],
  6. String eContentType = _Oid.data,
  7. Uint8List? eContent,
  8. Uint8List? signatureAlgorithm,
})

Assembles a detached CMS SignedData from a signedAttributes blob (the SET OF from cmsSignedAttributes) and the RSA signature over it. certificates is the DER chain, signer first. unsignedAttributes (e.g. a signature-time-stamp) are carried in the SignerInfo's 1 field. hash is the digest algorithm named in digestAlgorithms and the SignerInfo (it must match what produced the message digest / signature).

Implementation

Uint8List cmsAssembleSignedData({
  required Uint8List signedAttributes,
  required Uint8List signature,
  required List<Uint8List> certificates,
  crypto.Hash hash = crypto.sha256,
  List<Uint8List> unsignedAttributes = const [],
  String eContentType = _Oid.data,
  Uint8List? eContent,
  Uint8List? signatureAlgorithm,
}) {
  if (certificates.isEmpty) {
    throw ArgumentError('at least the signer certificate is required');
  }
  final signerCert = X509Certificate.parse(certificates.first);
  final digestOid = _digestOidFor(hash);
  if (digestOid == null) {
    throw ArgumentError('unsupported digest algorithm');
  }
  final digestAlgorithm = derSequence([derOid(digestOid), derNull()]);
  final signerInfo = derSequence([
    derInteger(BigInt.one),
    derSequence([signerCert.issuerDer, derInteger(signerCert.serial)]),
    digestAlgorithm,
    // re-tag the SET of signed attributes as IMPLICIT [0]
    Uint8List.fromList(signedAttributes)..[0] = DerTag.context(0),
    // the signature AlgorithmIdentifier: rsaEncryption by default, or the
    // ecdsa-with-SHAx identifier an EC signer passes in
    signatureAlgorithm ?? derSequence([derOid(_Oid.rsaEncryption), derNull()]),
    derOctetString(signature),
    if (unsignedAttributes.isNotEmpty)
      derContext(1, [for (final a in unsignedAttributes) ...a]),
  ]);

  // RFC 5652 ยง5.1: SignedData is version 3 when the encapsulated content
  // type is not id-data (e.g. an RFC 3161 TSTInfo), version 1 otherwise.
  final version = eContentType == _Oid.data ? 1 : 3;
  final signedData = derSequence([
    derInteger(BigInt.from(version)),
    derSet([digestAlgorithm]),
    // encapContentInfo: detached carries only the type; encapsulated wraps
    // the content in an EXPLICIT [0] OCTET STRING
    derSequence([
      derOid(eContentType),
      if (eContent != null) derContext(0, derOctetString(eContent)),
    ]),
    derContext(0, [for (final cert in certificates) ...cert]),
    derSet([signerInfo]),
  ]);

  return derSequence([
    derOid(_Oid.signedData),
    derContext(0, signedData),
  ]);
}