Creating
Certificates in Code
StreamSec Tools Enterprise
2.1.8.165
By Henrick Hellström
This paper will give you some
quick insights to getting started creating X.509 Certificates in code. The
paper will expect you are using version 2.1.8.165 of StreamSec Tools
Enterprise.
The focus of the paper is on the
StreamSec NewCertDlg Class Wizard. By combining Automatic Code Generation
with the components of StreamSec Tools this wizard will allow you to set
up an application specific Certificate Hierarchy with minimum effort.
To get started, create a new
Project in Delphi and select File|New from the Delphi IDE main menu. Go to
the StreamSec tab:

Select StreamSec NewCertDlg Class
and click OK. The default settings of the Wizard are displayed by the
picture below:

The control on top of the dialog
is a radio group that will let you select between four different types of
Certificate Creating Classes:
- A
class that creates a Root CA Certificate,
- A
class that creates Certificates signed by a local CA,
- A
class that creates a Certification Request and
- A
class that creates and signs a Certificate from a Certification
Request.
Root CA Certificate Creator
Normally there is only one Root CA
Certificate in each system. The code you are about to generate should only
be executed once during the lifetime of a production system.
1.
Select Create Root CA Certificate as the purpose of the generated
class,
2.
Uncheck each of the four Subject Alternative Name fields,
3.
Change the Public Key Size to 4096,
4.
Change the Validity Period to 3660 days,
5.
Change the path of the generated certificate to Root.cer,
6.
Change the Class name to TRootCertificateCreator,
7.
Click OK.
The generated unit contains one
interface IRootCertificateCreator and one class TRootCertificateCreator.
The interface declares 11
properties corresponding to the Subject Name fields you left checked, and
inherits from the INewCertDlg interface declared in unit StreamSecII.pas.
The class implements the
IRootCertificateCreator interface and inherits from the TNewCertDlgObject
class declared in unit StreamSecII.pas. The class overrides the
ComposeCertificate and AfterExecute methods of the base class. Put
additional code in ComposeCertificate e.g. if you want to add more
Certificate Extensions to the Root CA Certificate. The AfterExecute method
will be called after the private key of the Root CA Certificate has been
generated and the certificate has been signed. This is the first place you
might save the new certificate and its private key.
Using the generated class
Drop a TButton and a
TX509CertificateAuthority on the main form.
There are two event handlers to be
implemented. Firstly, the OnClick event of the button:
procedure TForm1.Button1Click(Sender: TObject);
begin
if not X509CertificateAuthority1.NewCACertDlg(nil) then
raise Exception.Create('Unable to create root certificate');
end;
Secondly, the
BeforeCreateNewCertDlg of the TX509CertificateAuthority:
procedure TForm1.X509CertificateAuthority1BeforeCreateNewCertDlg(
Sender: TObject; ACreateType: TNewCertCreateEnum;
var ANewCertDlg: INewCertDlg);
var
RootDlg: IRootCertificateCreator;
begin
case ACreateType of
nccRoot:
begin
RootDlg := TRootCertificateCreator.Create;
//Some Example values:
RootDlg.CommonName := 'Demo Root CA';
RootDlg.OrganizationName := 'TEST TEST TEST';
ANewCertDlg := RootDlg;
end;
end;
end;
Note: Make
sure that StreamSecII is in the interface section uses clause and that the
generated unit is in the implementation section uses clause.
So, what does this code do? Run
the project and click on the button.
- The
TX509CertificateAuthority.NewCACertDlg method invokes the
BeforeCreateNewCertDlg event. Since this is the first time you run the
code the method will pass the value nccRoot as the ACreateType
parameter of the event.
- The
X509CertificateAuthority1BeforeCreateNewCertDlg method creates an
instance of the generated TRootCertificateCreator class and assigns
values to the CommonName and OrganizationName properties of the
instance. The instance is returned as the value of the ANewCertDlg
parameter.
- The
ComposeCertificate method of the generated class is called. This
method assigns the values of the properties to the corresponding
fields of the Subject Name of the certificate. The method initializes
the new certificate as a Root CA Certificate.
- After
ComposeCertificate the TX509CertificateAuthority component will
generate a private key, the corresponding public key and sign the new
certificate.
- AfterExecute
is called. The generated code (see below) will save the new root
certificate as Root.cer, since this is what we specified in the wizard
dialog. AfterExecute will also save the private key ring to the file
User.pkr and the certificate store to the file User.scl.
procedure TRootCertificateCreator.AfterExecute(ATLSServer: ITLSServer);
begin
Cert.SaveToFile('Root.cer');
ATLSServer.SavePrivateKeyRing('User.pkr');
ATLSServer.SaveMyCertsToSCLFile('User.scl');
// inherited will save Cert to ExportData
inherited;
end;
The Root.cer file contains the
generated public key and the information assigned by ComposeCertificate.
This file is typically loaded at design time into each application in the
system and used for verifying the authenticity of every other certificate
of the system.
The User.pkr and User.scl files
contain the information the CA application needs for issuing other
certificates. These files are typically loaded at application start up
from now on:
procedure TForm1.FormCreate(Sender: TObject);
begin
if FileExists('User.pkr') then begin
X509CertificateAuthority1.LoadPrivateKeyRingFromFile('User.pkr',nil);
X509CertificateAuthority1.LoadMyCertsFromFile('User.scl');
end;
end;
Advanced:
The *.pkr files are password protected and contain information that
prevents your applications from accidentally overwriting them. Both of
these features have been added to ensure the security of production
systems, but they might complicate development and testing if you are not
aware of the details. To prevent the password dialogs from popping up
during development and testing you might add a TSsPrivateKeyRingComponent
to your application. Assign this component to the PrivateKeyRing property
of X509CertificateAuthority1 and set its property as follows:
Set
CacheKeyInterface to True. This property setting is required for
compatibility with the methods of the TX509CertificateAuthority component.
Next, select the Events tab and
assign event handlers to the OnPassword and OnAdminPassword events. The
implementation below could be used in a testing only environment. It is
recommended that you use password dialogs in a production environment. The
StreamSec Tools default dialogs can be accessed through the routines in
unit PasswordMain.pas.
StreamSec Tools uses the
ISecretKey interface for passing passwords and keys between different
parts of the framework. The ISecretKey interface is declared in unit
SecUtils.pas. You must add SecUtils to the interface section uses clause.
procedure TForm1.SsPrivateKeyRingComponent1AdminPassword(Sender: TObject;
Password: ISecretKey);
begin
Password.SetKeyStr('abc');
end;
procedure TForm1.SsPrivateKeyRingComponent1Password(Sender: TObject;
Password: ISecretKey);
begin
Password.SetKeyStr('def');
end;
Certificate Creator
In many systems the entity acting
as CA might need more than one certificate. In small systems the CA is
identical to the entity operating the server and will be the subject of
both the Root Certificate and the Server Certificate. In larger systems
the CA might need additional certificates for authenticating itself as the
Registration Authority that receives Certification Requests from the
clients. The purpose of the classes that creates Certificates signed by a
local CA is to create such certificates.
1.
Select Create a Certificate signed by a
local CA Certificate as the purpose of the generated class,
2.
Change the path of the generated
certificate to ServerCert.pfx,
3.
Click Open and select the generated Root
Certificate as the Certificate Template,
4.
Change the Class name to
TServerCertificateCreator,
5.
Click OK.
The generated unit contains one
interface IServerCertificateCreator and one class
TServerCertificateCreator.
The interface declares 15
properties corresponding to the Subject Name and Subject Alternative Name
fields you left checked, and inherits from the INewCertDlg interface
declared in unit StreamSecII.pas.
The class differs from
TRootCertificateCreator generated above in the implementation of the
ComposeCertificate method and the AfterExecute method.
Using the generated class
Drop a second button on the main
form. There are two event handlers to be implemented. Firstly, the OnClick
event of the button:
procedure TForm1.Button2Click(Sender: TObject);
begin
if not X509CertificateAuthority1.NewServerCertDlg(nil) then
raise Exception.Create('Unable to create server certificate');
end;
Secondly, extend the
BeforeCreateNewCertDlg of the TX509CertificateAuthority as follows:
procedure TForm1.X509CertificateAuthority1BeforeCreateNewCertDlg(
Sender: TObject; ACreateType: TNewCertCreateEnum;
var ANewCertDlg: INewCertDlg);
var
RootDlg: IRootCertificateCreator;
ServerDlg: IServerCertificateCreator;
begin
case ACreateType of
nccRoot:
begin
RootDlg := TRootCertificateCreator.Create;
//Some Example values:
RootDlg.CommonName := 'Demo Root CA';
RootDlg.OrganizationName := 'TEST TEST TEST';
ANewCertDlg := RootDlg;
end;
nccCert:
begin
ServerDlg := TServerCertificateCreator.Create;
//Some Example values:
ServerDlg.CommonName := 'Demo Server';
ServerDlg.DnsName := 'localhost';
ServerDlg.IPAddress := '127.0.0.1';
ANewCertDlg := ServerDlg;
end;
end;
end;
Note: Make
sure that both generated units are in the implementation section uses
clause.
So, what does this code do? Run
the project and click on the button.
- The
TX509CertificateAuthority.NewServerCertDlg method invokes the
BeforeCreateNewCertDlg event. This time the method will pass the value
nccCert as the ACreateType parameter of the event.
- The
X509CertificateAuthority1BeforeCreateNewCertDlg method creates an
instance of the generated TServerCertificateCreator class and assigns
values to the CommonName, DnsName and IPAddress properties of the
instance. The instance is returned as the value of the ANewCertDlg
parameter.
- The
ComposeCertificate method of the generated class is called. This
method assigns the values of the properties to the corresponding
fields of the Subject Name and Subject Alternative Name of the
certificate. The string ‘TEST TEST TEST’ from the OrganizationName
of the template certificate Root.cer will be assigned to the
OrganizationName field. The method initializes the new certificate as
a Server Certificate.
- The
TX509CertificateAuthority component will generate a private key, the
corresponding public key and sign the new certificate using the
private key of the Root Certificate.
- AfterExecute
is called. The generated code (see below) will save the new server
certificate together with its private key and the Root Certificate as
ServerCert.pfx, since this is what we specified in the wizard dialog.
procedure TRootCertificateCreator.AfterExecute(ATLSServer: ITLSServer);
var
WS: WideString;
PW: ISecretKey;
begin
WS := 'somepassword';
PW := TSecretKey.CreateBMPStr(PWideChar(WS));
ATLSServer.ExportToPFX('ServerCert.pfx',PW);
// inherited will save Cert to ExportData
inherited;
end;
The ServerCert.pfx file is
typically loaded at run time into the server application of the system.
Note:
Optionally, you might also copy the calls to SavePrivateKeyRing and
SaveMyCertsToSCLFile into AfterExecute. If you do, the private key of the
new certificate will be saved together with the Root Certificate private
key to User.pkr and both certificates will be saved to User.scl.
Certification Request Creator
Clients that need a Client
Certificate for connecting to a secure server typically generate
Certification Requests and send them to the server as part of the
registration process. A Client Certification Request contains the
certificate public key, the Subject Name of the client and a registration
token that authenticates the client to the CA that issues the Client
Certificate.
There is no point in using
Certification Requests within a single instance of an application. Save
your CA project, select File|New and create a new project. Select File|New
the StreamSec tab and the wizard:

1.
Delete the path of the generated
certificate,
2.
Change the Class name to TClientCertificationRequestCreator,
3.
Click OK.
The generated unit contains one
interface IClientCertificationRequestCreator and one class
TClientCertificationRequestCreator.
The interface declares 15
properties corresponding to the Subject Name and Subject Alternative Name
fields you left checked, and inherits from the INewCertDlg interface
declared in unit StreamSecII.pas.
The class implementation of the
ComposeCertificate method and the AfterExecute method is adopted for the
purpose of generating Certification Requests.
Using the generated class
The Client Certification Request
is typically generated as part of the client software registration
process. If it helps, think of this process as an Online Activation
Scheme. The client user enters his name and organization name together
with a “license key” (the Registration Token), connects to the
registration server, and obtains the “activation key” (the Client
Certificate) the client software will need for connecting to the
application server.
Drop a TButton and a
TSimpleTLSInternalServer (and optionally a TSsPrivateKeyRingComponent) on
the Client CA main form. Optionally, you should import the Root.cer file
or the Root CA User.scl file into the TSimpleTLSInternalServer component.
Right-click on the component, select Open File, select the Root
Certificate filter and open the file.
Implement the event handlers as
follows:
procedure TForm1.Button1Click(Sender: TObject);
var
ExportData: TMemoryStream;
ImportData: TMemoryStream;
Cert: TASN1Struct;
Status: TCertStatusCode;
begin
ExportData := TMemoryStream.Create;
try
if not SimpleTLSInternalServer1.NewClientCertReqDlg(ExportData) then
raise Exception.Create('Unable to create client cert req');
ImportData := TMemoryStream.Create;
try
//Here you should add code to send the ExportData stream
//and receive the signed certificate as the ImportData stream.
...
ImportData.Position := 0;
Cert := TASN1Struct.Create;
try
Cert.LoadFromStream(ImportData,fmtDER);
SimpleTLSInternalServer1.AddCertificate(Cert,True,Status);
if Status <> crcOK then
raise Exception.Create('Unable to add client certificate');
finally
Cert.Free;
end;
SimpleTLSInternalServer1.ExportToPFX('ClientCert.pfx',
TSecretKey.CreateBMPStr('somepassword'));
finally
ImportData.Free;
end;
finally
ExportData.Free;
end;
end;
Extend the BeforeCreateNewCertDlg
of the TSimpleTLSInternalServer as follows:
procedure TForm1.SimpleTLSInternalServer1BeforeCreateNewCertDlg(
Sender: TObject; ACreateType: TNewCertCreateEnum;
var ANewCertDlg: INewCertDlg);
var
ClientDlg: IClientCertificationRequestCreator;
begin
case ACreateType of
nccCertReq:
begin
ClientDlg := TClientCertificationRequestCreator.Create;
//Some Example values:
ClientDlg.CommonName := 'Demo Client';
ClientDlg.DnsName := 'localhost';
ClientDlg.IPAddress := '127.0.0.1';
ClientDlg.RegToken := 'someregtoken';
ANewCertDlg := ClientDlg;
end;
end;
end;
So, what does this code do? Run
the project and click on the button.
- The
TSimpleTLSInternalServer.NewClientCertReqDlg method invokes the
BeforeCreateNewCertDlg event. The method will pass the value
nccCertReq as the ACreateType parameter of the event.
- The
SimpleTLSInternalServer1BeforeCreateNewCertDlg method creates an
instance of the generated TClientCertificationRequestCreator class and
assigns values to the CommonName, DnsName and IPAddress properties of
the instance. The instance is returned as the value of the ANewCertDlg
parameter.
- The
ComposeCertificate method of the generated class is called. This
method assigns the values of the properties to the corresponding
fields of the Subject Name and Subject Alternative Name of the
certification request.
- The
TSimpleTLSInternalServer component will generate a private key, the
corresponding public key and self-sign the certification request using
its own private key.
- AfterExecute
is called. The generated code will save the private key ring to
User.pkr. This file contains the private key corresponding to the
public key in the certification request sent to the server. The
Certification Request will be saved to the ExportData stream.
- The
NewClientCertReqDlg method returns. The Button1Click event handler
sends the Certification Request to the server and obtains a
Certificate signed by the server CA. The Button1Click event handler
saves the certificate together with the private key to a
ClientCert.pfx file.
Note:
It is very important that the transmission of the Certification Request
from the client to the server is protected by a protocol that provides
both secrecy and server authentication, such as SSL or
PKCS#7-EnvelopedData. The RegToken appears as plain text in the
Certification Request, so the entire Certification Request has to be kept
secret from anyone but the CA server.
Certificate From Request Creator
As implied by the discussion in
the previous section, the client sends a Client Certification Request to
the server, and the server needs some way of authenticating and signing
this request. This is achieved by using a class that Creates and Signs a
Certificate From a Certification Request:

1.
Select the fourth purpose.
2.
Uncheck
the Subject Name and Subject Alternative Name fields that the client uses
for identifying itself. Any field that you leave checked will not be
copied from the imported Certification Request, but will be set by the
certificate creating class. You should leave a field checked if you either
want to set it to a system wide value (such as Organization) or if you
want to prohibit the client from setting it.
3.
Remove the file path of the generated certificate. Leaving the file
path as A:\Cert.cer might be useful if the Certificates are authenticated,
signed and exported manually on an off-line computer.
4.
Change the Class name to TClientCertificateCreator.
5.
Click OK.
The generated unit contains one
interface IClientCertificateCreator and one class
TClientCertificateCreator.
The main differences between this
class and the TServerCertificateCreator class described above, is:
- That
the TClientCertificateCreator class includes code for verifying and
copying the import data,
- That
it doesn’t create a public and private key pair, and
- That
it, by default, only generates Client Certificates.
Using the generated class
Using this class is the most
delicate part of creating a Certificate using system. If anything goes
wrong here, the security of the entire system might be jeopardized.
Typically, the
TClientCertificateCreator class is called by a service of a secure server.
It is assumed that you use some secure transportation of the Certification
Request, e.g. SSL authenticated by the previously generated Root CA
Certificate and Server Certificate.
Add a TSsEventUserList component
to the main form of the CA project. Assign this component to the UserList
property of the TX509CertificateAuthority component. Assign an event
handler to the OnVerifyUser event of the TSsEventUserList component:
procedure TForm1.SsEventUserList1VerifyUser(ASubjectName: TRdnsequence;
ASubjectAltName: TGeneralNames; const ARegToken: WideString;
var AVerified: Boolean);
begin
AVerified := False;
if (ASubjectName.CommonName = 'Demo Client') and
(ASubjectAltName.DnsName = 'localhost') and
(ASubjectAltName.IPAddress = '127.0.0.1') then
AVerified := ARegToken = 'someregtoken';
end;
As you can see, the code above
verifies the name and registration token entered by the client into the
Certification Request. This is of course only an example; you wouldn’t
use hard coded strings like this in production software.
The method that is called by the
server component when the client has sent the Certification Request could
look like this:
procedure TForm1.SignRequest(ImportData, ExportData: TMemoryStream);
begin
if not X509CertificateAuthority1.NewCertFromReqDlg(ImportData,ExportData) then
raise Exception.Create('Unable to create certificate');
ExportData.Position := 0;
end;
Finally, extend the
BeforeCreateNewCertDlg of the TX509CertificateAuthority as follows:
procedure TForm1.X509CertificateAuthority1BeforeCreateNewCertDlg(
Sender: TObject; ACreateType: TNewCertCreateEnum;
var ANewCertDlg: INewCertDlg);
var
RootDlg: IRootCertificateCreator;
ServerDlg: IServerCertificateCreator;
ClientDlg: IclientCertificateCreator;
begin
case ACreateType of
nccRoot:
begin
RootDlg := TRootCertificateCreator.Create;
//Some Example values:
RootDlg.CommonName := 'Demo Root CA';
RootDlg.OrganizationName := 'TEST TEST TEST';
ANewCertDlg := RootDlg;
end;
nccCert:
begin
ServerDlg := TServerCertificateCreator.Create;
//Some Example values:
ServerDlg.CommonName := 'Demo Server';
ServerDlg.DnsName := 'localhost';
ServerDlg.IPAddress := '127.0.0.1';
ANewCertDlg := ServerDlg;
end;
nccCertFromReq:
begin
ClientDlg := TClientCertificateCreator.Create;
//Some Example values:
ClientDlg.OrganizationName := 'TEST TEST TEST';
ANewCertDlg := ClientDlg;
end;
end;
end;