generateSelfSignedCertificate static method

String generateSelfSignedCertificate(
  1. PrivateKey privateKey,
  2. String csr,
  3. int days, {
  4. List<String>? sans,
  5. List<KeyUsage>? keyUsage,
  6. List<ExtendedKeyUsage>? extKeyUsage,
  7. String serialNumber = '1',
  8. Map<String, String>? issuer,
  9. DateTime? notBefore,
})

Generates a self signed certificate

  • privateKey = The private key used for signing
  • csr = The CSR containing the DN and public key
  • days = The validity in days
  • sans = Subject alternative names to place within the certificate
  • keyUsage = The key usage definition extension
  • extKeyUsage = The extended key usage definition
  • serialNumber = The serialnumber. If not set the default will be 1.
  • issuer = The issuer. If null, the issuer will be the subject of the given csr.
  • notBefore = The Timestamp after when the certificate is valid. If null, this will be DateTime.now. The "Not After" property of the certificate will have the days added to notBefore.

Implementation

static String generateSelfSignedCertificate(
  PrivateKey privateKey,
  String csr,
  int days, {
  List<String>? sans,
  List<KeyUsage>? keyUsage,
  List<ExtendedKeyUsage>? extKeyUsage,
  String serialNumber = '1',
  Map<String, String>? issuer,
  DateTime? notBefore,
}) {
  var csrData = csrFromPem(csr);

  var data = ASN1Sequence();

  // Add version
  var version = ASN1Object(tag: 0xA0);
  version.valueBytes = ASN1Integer.fromtInt(2).encode();
  data.add(version);

  // Add serial number
  data.add(ASN1Integer(BigInt.parse(serialNumber)));

  // Add protocol
  var blockProtocol = ASN1Sequence();
  blockProtocol.add(
      ASN1ObjectIdentifier.fromIdentifierString(csrData.signatureAlgorithm));
  blockProtocol.add(ASN1Null());
  data.add(blockProtocol);

  issuer ??= csrData.certificationRequestInfo!.subject!;

  // Add Issuer
  var issuerSeq = ASN1Sequence();
  for (var k in issuer.keys) {
    var value = issuer[k];
    var pString;
    if (StringUtils.isAscii(value!)) {
      pString = ASN1PrintableString(stringValue: value);
    } else {
      pString = ASN1UTF8String(utf8StringValue: value);
    }
    var oIdentifier;
    try {
      oIdentifier = ASN1ObjectIdentifier.fromName(k);
    } on UnsupportedObjectIdentifierException catch (e) {
      oIdentifier = ASN1ObjectIdentifier.fromIdentifierString(k);
    }
    var innerSequence = ASN1Sequence(elements: [oIdentifier, pString]);
    var s = ASN1Set(elements: [innerSequence]);
    issuerSeq.add(s);
  }
  data.add(issuerSeq);

  // Add Validity
  var validitySeq = ASN1Sequence();
  final DateTime from = notBefore ?? DateTime.now();
  validitySeq.add(ASN1UtcTime(from));
  validitySeq.add(ASN1UtcTime(from.add(Duration(days: days))));
  data.add(validitySeq);

  // Add Subject
  var subjectSeq = ASN1Sequence();
  for (var k in csrData.certificationRequestInfo!.subject!.keys) {
    var value = csrData.certificationRequestInfo!.subject![k];
    var pString;
    if (StringUtils.isAscii(value!)) {
      pString = ASN1PrintableString(stringValue: value);
    } else {
      pString = ASN1UTF8String(utf8StringValue: value);
    }
    var oIdentifier = ASN1ObjectIdentifier.fromIdentifierString(k);
    var innerSequence = ASN1Sequence(elements: [oIdentifier, pString]);
    var s = ASN1Set(elements: [innerSequence]);
    subjectSeq.add(s);
  }
  data.add(subjectSeq);

  // Add Public Key
  if (privateKey.runtimeType == RSAPrivateKey) {
    data.add(
      _makePublicKeyBlock(
        CryptoUtils.rsaPublicKeyFromDERBytes(
          _stringAsBytes(
              csrData.certificationRequestInfo!.publicKeyInfo!.bytes!),
        ),
      ),
    );
  } else {
    data.add(
      _makeEccPublicKeyBlock(
        CryptoUtils.ecPublicKeyFromDerBytes(
          _stringAsBytes(
              csrData.certificationRequestInfo!.publicKeyInfo!.bytes!),
        ),
      ),
    );
  }

  // Add Extensions
  if (IterableUtils.isNotNullOrEmpty(sans) ||
      IterableUtils.isNotNullOrEmpty(keyUsage) ||
      IterableUtils.isNotNullOrEmpty(extKeyUsage)) {
    var extensionTopSequence = ASN1Sequence();

    if (IterableUtils.isNotNullOrEmpty(keyUsage)) {
      int valueBytes = 1; // the last bit of the 2 bytes is always set
      for (KeyUsage keyUsage in keyUsage!) {
        final int shiftedBit = int.parse("8000", radix: 16) >> keyUsage.index;
        valueBytes |=
            shiftedBit; // bit shift from the first bit of the 2 bytes depending on which flag is set
      }

      final int firstValueByte =
          (valueBytes & int.parse("ff00", radix: 16)) >> 8;
      final int secondValueByte = (valueBytes & int.parse("00ff", radix: 16));

      final Uint8List keyUsageBytes = Uint8List.fromList(<int>[
        // BitString identifier
        3,
        // Length
        3,
        // Unused bytes at the end
        1,
        firstValueByte,
        secondValueByte
      ]);

      var octetString = ASN1OctetString(
          octets: ASN1BitString.fromBytes(keyUsageBytes).encode());

      var keyUsageSequence = ASN1Sequence();
      keyUsageSequence
          .add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.15'));
      keyUsageSequence.add(octetString);
      extensionTopSequence.add(keyUsageSequence);
    }

    if (IterableUtils.isNotNullOrEmpty(extKeyUsage)) {
      var extKeyUsageList = ASN1Sequence();
      for (var s in extKeyUsage!) {
        var oi = <int>[];
        switch (s) {
          case ExtendedKeyUsage.SERVER_AUTH:
            oi = [1, 3, 6, 1, 5, 5, 7, 3, 1];
            break;
          case ExtendedKeyUsage.CLIENT_AUTH:
            oi = [1, 3, 6, 1, 5, 5, 7, 3, 2];
            break;
          case ExtendedKeyUsage.CODE_SIGNING:
            oi = [1, 3, 6, 1, 5, 5, 7, 3, 3];
            break;
          case ExtendedKeyUsage.EMAIL_PROTECTION:
            oi = [1, 3, 6, 1, 5, 5, 7, 3, 4];
            break;
          case ExtendedKeyUsage.TIME_STAMPING:
            oi = [1, 3, 6, 1, 5, 5, 7, 3, 8];
            break;
          case ExtendedKeyUsage.OCSP_SIGNING:
            oi = [1, 3, 6, 1, 5, 5, 7, 3, 9];
            break;
          case ExtendedKeyUsage.BIMI:
            oi = [1, 3, 6, 1, 5, 5, 7, 3, 31];
            break;
        }
        extKeyUsageList.add(ASN1ObjectIdentifier(oi));
      }
      var octetString = ASN1OctetString(octets: extKeyUsageList.encode());

      var extKeyUsageSequence = ASN1Sequence();
      extKeyUsageSequence
          .add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.37'));
      extKeyUsageSequence.add(octetString);
      extensionTopSequence.add(extKeyUsageSequence);
    }

    if (IterableUtils.isNotNullOrEmpty(sans)) {
      var sanList = ASN1Sequence();
      for (var s in sans!) {
        sanList.add(ASN1PrintableString(stringValue: s, tag: 0x82));
      }
      var octetString = ASN1OctetString(octets: sanList.encode());

      var sanSequence = ASN1Sequence();
      sanSequence.add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.17'));
      sanSequence.add(octetString);
      extensionTopSequence.add(sanSequence);
    }

    var extObj = ASN1Object(tag: 0xA3);
    extObj.valueBytes = extensionTopSequence.encode();

    data.add(extObj);
  }

  var outer = ASN1Sequence();
  outer.add(data);
  outer.add(blockProtocol);
  if (privateKey.runtimeType == RSAPrivateKey) {
    outer.add(ASN1BitString(
        stringValues: _rsaSign(data.encode(), privateKey as RSAPrivateKey,
            _getDigestFromOi(csrData.signatureAlgorithm!))));
  } else {
    var ecSignature = eccSign(data.encode(), privateKey as ECPrivateKey,
        _getDigestFromOi(csrData.signatureAlgorithm!));
    var bitStringSequence = ASN1Sequence();
    bitStringSequence.add(ASN1Integer(ecSignature.r));
    bitStringSequence.add(ASN1Integer(ecSignature.s));
    var blockSignatureValue =
        ASN1BitString(stringValues: bitStringSequence.encode());
    outer.add(blockSignatureValue);
  }

  var chunks = StringUtils.chunk(base64.encode(outer.encode()), 64);
  return '$BEGIN_CERT\n${chunks.join('\r\n')}\n$END_CERT';
}