StreamSec

 

 
Articles

This paper describes a feature of StreamSec ToolsCreating 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.

  1. 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.
  2. 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.
  3. 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.
  4. After ComposeCertificate the TX509CertificateAuthority component will generate a private key, the corresponding public key and sign the new certificate.
  5. 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.

  1. The TX509CertificateAuthority.NewServerCertDlg method invokes the BeforeCreateNewCertDlg event. This time the method will pass the value nccCert as the ACreateType parameter of the event.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

  1. The TSimpleTLSInternalServer.NewClientCertReqDlg method invokes the BeforeCreateNewCertDlg event. The method will pass the value nccCertReq as the ACreateType parameter of the event.
  2. 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.
  3. 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.
  4. The TSimpleTLSInternalServer component will generate a private key, the corresponding public key and self-sign the certification request using its own private key.
  5. 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.
  6. 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;

 

 

 

About I Contact us I Site map
Terms & Conditions
I Privacy Policy

© 2000-2004 StreamSec™ All rights reserved.