Skip to content

Suggestion: Self-signed certificate #180

@RoLex

Description

@RoLex
class procedure SelfSignCert(const ACert, AKey: String; const AName: TStringList; const ADays: Integer = 365;
  const ASer: UInt64 = 0; const ABits: Integer = 4096); static;

class procedure TSSLTools.SelfSignCert(const ACert, AKey: String; const AName: TStringList;
  const ADays: Integer = 365; const ASer: UInt64 = 0; const ABits: Integer = 4096);
var
  LKey: PEVP_PKEY;
  LCert: PX509;
  LSer: UInt64;
  LName: PX509_NAME;
  LPos: Integer;
  LExt: PX509_EXTENSION;
  LDig: TBytes;
  LData: PASN1_OCTET_STRING;
  LAuth: PAUTHORITY_KEYID;
  LFile: PBIO;
begin
  LKey := EVP_PKEY_Q_keygenRSA(nil, nil, 'RSA', ABits);

  if not Assigned(LKey) then
    raise Exception.Create('Failed: EVP_PKEY_Q_keygenRSA');

  LCert := X509_new;

  if not Assigned(LCert) then begin
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_new');
  end;

  if X509_set_version(LCert, 2) = 0 then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_set_version');
  end;

  LSer := ASer;

  if LSer = 0 then begin
    Randomize;
    LSer := Random($7FFFFFFF) or Random($7FFFFFFF) shl 32;
    {
    Int64Rec(LSer).Words[0] := Random(High(Word));
    Int64Rec(LSer).Words[1] := Random(High(Word));
    Int64Rec(LSer).Words[2] := Random(High(Word));
    Int64Rec(LSer).Words[3] := Random(High(Word));
    }
  end;

  if ASN1_INTEGER_set_int64(X509_get_serialNumber(LCert), LSer) = 0 then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_get_serialNumber');
  end;

  if X509_gmtime_adj(X509_get0_notBefore(LCert), 0) = nil then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_get0_notBefore');
  end;

  if X509_gmtime_adj(X509_get0_notAfter(LCert), 60 * 60 * 24 * ADays) = nil then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_get0_notAfter');
  end;

  if X509_set_pubkey(LCert, LKey) = 0 then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_set_pubkey');
  end;

  LName := X509_get_subject_name(LCert);

  if not Assigned(LName) then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_get_subject_name');
  end;

  for LPos := 0 to AName.Count - 1 do begin
    if AName.Names[LPos].Equals('emailAddress') then begin

      if X509_NAME_add_entry_by_NID(LName, NID_pkcs9_emailAddress, MBSTRING_ASC, PAnsiChar(AnsiString(AName.ValueFromIndex[LPos])), -1, -1, 0) = 0 then begin
        X509_free(LCert);
        EVP_PKEY_free(LKey);
        raise Exception.Create('Failed: X509_NAME_add_entry_by_NID(' + AName.Names[LPos] + '=' + AName.ValueFromIndex[LPos] + ')');
      end;

    end else begin

      if X509_NAME_add_entry_by_txt(LName, PAnsiChar(AnsiString(AName.Names[LPos])), MBSTRING_ASC, PAnsiChar(AnsiString(AName.ValueFromIndex[LPos])), -1, -1, 0) = 0 then begin
        X509_free(LCert);
        EVP_PKEY_free(LKey);
        raise Exception.Create('Failed: X509_NAME_add_entry_by_txt(' + AName.Names[LPos] + '=' + AName.ValueFromIndex[LPos] + ')');
      end;

    end;
  end;

  if X509_set_issuer_name(LCert, LName) = 0 then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_set_issuer_name');
  end;

  // NID_basic_constraints

  LExt := X509V3_EXT_conf_nid(nil, nil, NID_basic_constraints, 'critical,CA:TRUE'); // todo: config

  if not Assigned(LExt) then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509V3_EXT_conf_nid(NID_basic_constraints)');
  end;

  if X509_add_ext(LCert, LExt, -1) = 0 then begin
    X509_EXTENSION_free(LExt);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_add_ext(NID_basic_constraints)');
  end;

  X509_EXTENSION_free(LExt);

  // NID_key_usage

  LExt := X509V3_EXT_conf_nid(nil, nil, NID_key_usage,
    'critical, keyCertSign, digitalSignature, keyCertSign, keyEncipherment, dataEncipherment' // todo: config
  );

  if not Assigned(LExt) then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509V3_EXT_conf_nid(NID_key_usage)');
  end;

  if X509_add_ext(LCert, LExt, -1) = 0 then begin
    X509_EXTENSION_free(LExt);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_add_ext(NID_key_usage)');
  end;

  X509_EXTENSION_free(LExt);

  // NID_ext_key_usage

  LExt := X509V3_EXT_conf_nid(nil, nil, NID_ext_key_usage, 'clientAuth, serverAuth'); // todo: config

  if not Assigned(LExt) then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509V3_EXT_conf_nid(NID_ext_key_usage)');
  end;

  if X509_add_ext(LCert, LExt, -1) = 0 then begin
    X509_EXTENSION_free(LExt);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_add_ext(NID_ext_key_usage)');
  end;

  X509_EXTENSION_free(LExt);

  // prepare

  SetLength(LDig, EVP_MAX_MD_SIZE);
  LPos := 0;

  if X509_pubkey_digest(LCert, EVP_sha256, @LDig[0], @LPos) = 0 then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_pubkey_digest');
  end;

  if LPos = 0 then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_pubkey_digest(len=0)');
  end;

  SetLength(LDig, LPos);
  LData := ASN1_OCTET_STRING_new;

  if not Assigned(LData) then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: ASN1_OCTET_STRING_new)');
  end;

  if ASN1_OCTET_STRING_set(LData, PAnsiChar(LDig), LPos) = 0 then begin
    ASN1_OCTET_STRING_free(LData);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: ASN1_OCTET_STRING_set)');
  end;

  // NID_subject_key_identifier

  LExt := X509V3_EXT_i2d(NID_subject_key_identifier, 0, LData);

  if not Assigned(LExt) then begin
    ASN1_OCTET_STRING_free(LData);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509V3_EXT_i2d(NID_subject_key_identifier)');
  end;

  if X509_add_ext(LCert, LExt, -1) = 0 then begin
    X509_EXTENSION_free(LExt);
    ASN1_OCTET_STRING_free(LData);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_add_ext(NID_subject_key_identifier)');
  end;

  X509_EXTENSION_free(LExt);

  // NID_authority_key_identifier

  LAuth := AUTHORITY_KEYID_new;

  if not Assigned(LAuth) then begin
    ASN1_OCTET_STRING_free(LData);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: AUTHORITY_KEYID_new');
  end;

  LAuth.keyid := LData;
  LExt := X509V3_EXT_i2d(NID_authority_key_identifier, 0, LAuth);

  if not Assigned(LExt) then begin
    AUTHORITY_KEYID_free(LAuth);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509V3_EXT_i2d(NID_authority_key_identifier)');
  end;

  if X509_add_ext(LCert, LExt, -1) = 0 then begin
    X509_EXTENSION_free(LExt);
    AUTHORITY_KEYID_free(LAuth);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_add_ext(NID_authority_key_identifier)');
  end;

  X509_EXTENSION_free(LExt);
  AUTHORITY_KEYID_free(LAuth);

  // sign

  if X509_sign(LCert, LKey, EVP_sha256) = 0 then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: X509_sign');
  end;

  LFile := BIO_new_file(PAnsiChar(AnsiString(AKey)), PAnsiChar('w'));

  if not Assigned(LFile) then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: BIO_new_file(Key)');
  end;

  if PEM_write_bio_PKCS8PrivateKey(LFile, LKey, nil, nil, 0, nil, nil) = 0 then begin
    BIO_free(LFile);
    DeleteFile(AKey);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: PEM_write_bio_PKCS8PrivateKey');
  end;

  BIO_free(LFile);
  LFile := BIO_new_file(PAnsiChar(AnsiString(ACert)), PAnsiChar('w'));

  if not Assigned(LFile) then begin
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: BIO_new_file(Cert)');
  end;

  if PEM_write_bio_X509(LFile, LCert) = 0 then begin
    BIO_free(LFile);
    DeleteFile(AKey);
    DeleteFile(ACert);
    X509_free(LCert);
    EVP_PKEY_free(LKey);
    raise Exception.Create('Failed: PEM_write_bio_X509');
  end;

  BIO_free(LFile);
  X509_free(LCert);
  EVP_PKEY_free(LKey);
end;

This requires missing OpenSSL API declarations. They can easily be found in manual.

Example:

var
  LName: TStringList;
begin
  LName := TStringList.Create;
  LName.AddPair('CN', 'CommonName');
  LName.AddPair('O', 'Organization');
  LName.AddPair('OU', 'Unit');
  LName.AddPair('C', 'CountryCode');
  LName.AddPair('ST', 'State');
  LName.AddPair('L', 'Locality');
  LName.AddPair('emailAddress', 'Email');

  TSSLTools.SelfSignCert('server.crt', 'server.key', LName, 1825, DoRandom64);
  LName.Free;
end;

Good random number generator:

uses
  System.SysUtils;

function DoRandom64: UInt64;
begin
  Randomize;
  Assert(SizeOf(Int64Rec)=SizeOf(Result));
  Int64Rec(Result).Hi := Random($7FFFFFFF);
  Int64Rec(Result).Lo := Random($7FFFFFFF);
end;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions