EldoS | Feel safer!

Software components for data protection, secure storage and transfer

RSA/X509 message signing using CAPI and eToken

Also by EldoS: CallbackFilter
A component to monitor and control disk activity, track file and directory operations (create, read, write, rename etc.), alter file data, encrypt files, create virtual files.
#884
Posted: 07/28/2006 04:06:35
by Stephane Grobety (Priority Standard support level)
Joined: 04/18/2006
Posts: 170

Hello veryone,

I'm trying to convert some code from CAPICOM to Eldos style signing. Basically, I have messages (string of text) that needs to be signed with a detached signature and sent to the server. The private keys are inside eToken PRO tokens and the application is accessed throough terminal services. This means that I have no choice but use CAPI (I can't do it with PKCS#11).

I'm facing two problems with my code. First, it's several order of magnitude slower than the equivalent CAPICOM code. In fact, CAPICOM is close to instant (there is only a slight pause when I call sign) when Eldos CAPI version of sighn needs more than 30 seconds to even display the token PIN dialog.

Second problem: I can't verify the signature using CAPICOM at the other end of the connection. The "ISignedData.Verify" methode always return false.

I based my code of what I could understand from the MessageSigner demo (which could really use some work to make it easier to follow, BTW). Would it be possible to have a look at my version and, possibly, tell me what I'm doing wrong ?

This is the new code (I commented it to make it easier to follow):
Code
function SignText(Data: String): String;
var
  AMessageSigner: TElMessageSigner;
  ACertificate: TElX509Certificate;
  ATempStorage: TElMemoryCertStorage;
  AWinStorage: TElWinCertStorage;
  SignatureBuffer: TMemoryStream;
  OutBufferSize: Integer;
  Signature: string;
  CertIndex: Integer;
begin
  AMessageSigner := TElMessageSigner.Create(nil);
  try
    // Create a certificate that will be used to "translate" the CAPICOM object into TElX509Certificate
    ACertificate := TElX509Certificate.Create(nil);
    try
      // Select a X509 certificate using CAPICOM's IStore.Select GUI and copy it to a TElX509Certificate object
      X509MSToEldos(PickSignerCertificate(false), ACertificate);
      // Create a winCertStorage to access the certificate
      AWinStorage := TElWinCertStorage.Create(nil);
      try
        // Make sure we can access the "My" store
        AWinStorage.SystemStores.Add('My');
        // Locat the X509 certificate that we intend to use
        CertIndex := AWinStorage.FindByHash(ACertificate.GetHashSHA1);
        if CertIndex > -1 then
        begin
          // Create a temporary memory certificate store
          ATempStorage := TElMemoryCertStorage.Create(nil);
          try
            SignatureBuffer := TMemoryStream.Create;
            try
              OutBufferSize := 0;
              // Add the signer's certificate to the temporary store
              ATempStorage.Add(AWinStorage.Certificates[CertIndex]);
              // Point the message signer to the storage
              AMessageSigner.CertStorage := ATempStorage;
              AMessageSigner.RecipientCerts := ATempStorage;
              // Find out how much memory is needed
              AMessageSigner.Sign(PChar(Data), Length(Data), nil, OutBufferSize, true);
              SignatureBuffer.Size := OutBufferSize;
              SignatureBuffer.Position := 0;
              // Generate the actual signature
              AMessageSigner.Sign(PChar(Data), Length(Data), SignatureBuffer.Memory, OutBufferSize, true);
              SignatureBuffer.Position := 0;
              // Convert the buffer in base64
              Signature := StreamToStringBASE64(SignatureBuffer, false);
              // Display
              result := Signature;
            finally
              SignatureBuffer.Free;
            end;
          finally
            ATempStorage.Free;
          end;
        end;
      finally
        AWinStorage.Free;
      end;
    finally
      ACertificate.Free;
    end;
  finally
    AMessageSigner.Free;
  end;
end;


This is the old code:

Code
function SignData(AData: String; ServerStore, Detached, FullChain: Boolean; SignatureTimeStamp: TDateTime): String;
var
  SignTime: IAttribute;
  SignerData: ISignedData;
  Signer: ISigner2;
  Certificate: ICertificate;
  Signature: String;
begin
  try
    // Create the objects
    SignTime := CoAttribute.Create;
    SignerData := CoSignedData.Create;
    Signer := CoSigner.Create as ISigner2;
    // Pick a certificate for signing
    Certificate := PickSignerCertificate(ServerStore);
    if assigned(Certificate) then
    begin
      // Now, we get a cert. Get the data to sign
      SignerData.Content := AData;
      // Prepare the signature
      // What to include
      if FullChain then
        Signer.Options := CAPICOM_CERTIFICATE_INCLUDE_WHOLE_CHAIN
      else
        Signer.Options := CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY;
      // Fetch the certificat
      Signer.Certificate := Certificate as ICertificate2;
      // TimeStamp
      SignTime.Name := CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME;
      SignTime.Value := Now;
      Signer.AuthenticatedAttributes.Add(SignTime);
      // And sign
      Signature := SignerData.Sign(Signer, Detached, CAPICOM_ENCODE_BASE64);
      result := Signature;
    end
    else
      result := '';
  except
    on e: Exception do
    begin
      result := '';
      raise;
    end;
  end;
end;


Finally, this is the code used to verify the signature (CAPICOM):
Code
function VerifyCertificate(AData: string; ASignature: String; Display: Boolean): WordBool;
var
  I: Integer;
  SignerData: ISignedData;
  Certificates: ICertificates;
  CertificateStatus: ICertificateStatus2;
  AChain: IChain;
  AChainBuilding, NoChainCheck: Boolean;
begin
  try
    // Create the objects
    SignerData := CoSignedData.Create;
    AChain     := CoChain.Create;

    SignerData.Content := AData;
    SignerData.Verify(ASignature, true, CAPICOM_VERIFY_SIGNATURE_AND_CERTIFICATE);
    result := true;
    if Display then
      (IUnknown(SignerData.Certificates.Item[SignerData.Certificates.Count]) as ICertificate2).Display;
    for I := 1 to (SignerData.Certificates.Count) do    // Iterate
    begin
      if AChain.Build(IUnknown(SignerData.Certificates.Item[i]) as ICertificate2) then
      begin
        Certificates := AChain.Certificates; //.Find(CAPICOM_CERTIFICATE_FIND_ROOT_NAME, 'WiseKey Common global root', true);
        if Certificates.Count > 0 then
        begin
          CertificateStatus := (IUnknown(Certificates.Item[1]) as ICertificate2).IsValid;
          CertificateStatus.CheckFlag := CAPICOM_CHECK_SIGNATURE_VALIDITY or CAPICOM_CHECK_TIME_VALIDITY or CAPICOM_CHECK_TRUSTED_ROOT;
          result := CertificateStatus.Result;
        end;
        if not result then
          break;
      end
      else
        Raise exception.Create('Not enough data to create the certificate chain and verify the certification, aborting');
    end;    // for
  except
    result := false;
  end;
end;


In all case, the signing cewrtificate is that same. It works fine.

Thanks in advance,
Stephane
#885
Posted: 07/28/2006 08:05:52
by Ken Ivanov (EldoS Corp.)

Quote
First, it's several order of magnitude slower than the equivalent CAPICOM code. In fact, CAPICOM is close to instant (there is only a slight pause when I call sign) when Eldos CAPI version of sighn needs more than 30 seconds to even display the token PIN dialog.

Would you be so kind to check, what exactly operation causes 30-second time delay?

There can be a lot of different reasons for signature problem. It would be excellent if you sign some arbitrary file using both CAPICOM and ElMessageSigner and sent both signatures to us (either to support@eldos.com, or posting them to our helpdesk system), so that we could find out the differences.
#892
Posted: 07/31/2006 04:42:52
by Stephane Grobety (Priority Standard support level)
Joined: 04/18/2006
Posts: 170

Thank you for your answer.

I gather that my code is correct, then.

I've added some instrumentation in my code and ran the SignText function 4 times. Here is the result (I did a first run without recording the delays):

Code
X509MSToEldos(PickSignerCertificate(false), ACertificate) run count: 4; Total time: 460.41 Average time spent: 115.103 ms; Std dev: 2.052 ms; Min: 112.448 ms; Max: 117.365 ms
AWinStorage.SystemStores.Add('My') run count: 4; Total time: 17435.99 Average time spent: 4358.998 ms; Std dev: 199.484 ms; Min: 4223.233 ms; Max: 4654.522 ms
AMessageSigner.Sign(PChar(Data), Length(Data), nil, OutBufferSize, true); run count: 4; Total time: 54960.12 Average time spent: 13740.031 ms; Std dev: 157.001 ms; Min: 13560.508 ms; Max: 13937.281 ms
AMessageSigner.Sign(PChar(Data), Length(Data), SignatureBuffer.Memory, OutBufferSize, true); run count: 4; Total time: 63498.37 Average time spent: 15874.591 ms; Std dev: 1151.149 ms; Min: 14621.657 ms; Max: 17270.673 ms


After that, I traced into the AMessageSigner.Sign calls. I didn't add instrumentation to that code because it would have required some pretty major modification to carry the context around and I didn't want to mess it up. However, here are what I could find:

1/ The FCertStorage.Certificates[I].SaveKeyToBuffer methode takes about 10 seconds to complete. I could trace the issue down to
the TElX509Certificate.LoadPrivateKeyFromWin32 function. It looks like it has a rather major delay trying to open the Current user's "MY" store. In addition to that, it returned FALSE (which I think is normal since the private key material cannot be extracted from the token).
2/ The TElX509Certificate.GetContextByHash methode also takes a while to run although it's not as slow (about 3 seconds). The problematic call seems to be CertOpenSystemStore (which takes 3 seconds to complete).

I'll create a a sample signature with CAPICOM and with the above code and send them to you.
#893
Posted: 07/31/2006 05:04:17
by Eugene Mayevski (EldoS Corp.)

Can you please check whether SaveKeyToBuffer is called once or several times during single SignText execution?


Sincerely yours
Eugene Mayevski
#895
Posted: 07/31/2006 06:50:40
by Stephane Grobety (Priority Standard support level)
Joined: 04/18/2006
Posts: 170

It's called twice
#897
Posted: 07/31/2006 07:20:28
by Stephane Grobety (Priority Standard support level)
Joined: 04/18/2006
Posts: 170

I just sent you the sample cleartext and signatures
#10334
Posted: 06/09/2009 11:25:36
by Thanh Nguyen Trung (Priority Standard support level)
Joined: 09/12/2008
Posts: 73

Hi,

I am using SBB 7 and I would like use SBB to open and read a certificate in a smartcard. The PIN code will be passed to the SBB to open the card.

As I know that TELWinCertStorage serves as a wrapper of CryptoAPI
http://eldos.com/documentation/sbb/documentation/ref_cl_wincertstorage.html but I see no way to pass the PIN code.
Through MSDN, I know that CryptoAPI two functions to do this, they are:
- CryptAcquireContext
- CryptSetProvParam (with PP_SIGNATURE_PIN flag)

I wonder if you could show me the way to pass the PIN code through the CryptoAPI using SBB.

Thanks
Thanh
#10336
Posted: 06/09/2009 12:21:51
by Eugene Mayevski (EldoS Corp.)

The problem is that PP_SIGNATURE_PIN is not supported by all CSP (cryptoproviders). It's easier to say which providers do support it :).

We suggest using PKCS#11 interface for correct interoperation with the device.


Sincerely yours
Eugene Mayevski
Also by EldoS: Rethync
The cross-platform framework that simplifies synchronizing data between mobile and desktop applications and servers and cloud storages

Reply

Statistics

Topic viewed 5322 times

Number of guests: 1, registered members: 0, in total hidden: 0




|

Back to top

As of July 15, 2016 EldoS Corporation will operate as a division of /n software inc. For more information, please read the announcement.

Got it!