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.
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;
uses
System.SysUtils;
function DoRandom64: UInt64;
begin
Randomize;
Assert(SizeOf(Int64Rec)=SizeOf(Result));
Int64Rec(Result).Hi := Random($7FFFFFFF);
Int64Rec(Result).Lo := Random($7FFFFFFF);
end;
This requires missing OpenSSL API declarations. They can easily be found in manual.
Example:
Good random number generator: