You are not logged in.
Hello,
I'm currently working on a complete PAdES signature implementation using mORMot 2 and OpenSSL, including support for visible digital signatures on PDFs, multiple sequential signatures (PAdES).
Certificates from PFX and Windows Store
But I'm facing a critical problem I couldn't solve or debug further:
No matter if I try to load the certificate from a .pfx file or from the Windows Certificate Store, I receive a runtime Abstract Error at the moment I call:
Cert := TCryptCert.Create;
if not Cert.LoadFromFile(CertOrigem, cccCertWithPrivateKey, CertSenha) then
Exit;
or when using the Windows Store PEM:
tmpPEM := GetOneSystemStoreAsPem(scsMY);
if not Cert.Load(tmpPEM, cccCertWithPrivateKey, '') then
Exit;
In both cases, I receive no stack trace, no explanation — just "Abstract Error".
Here’s the "almost" code:
type
TCertSource = (csPFX, csWindowsStore);
function AssinarPDF_PAdES_Completo(
const ArquivoOrigem, ArquivoDestino: string;
const CertOrigem, CertSenha: string;
const Razao, Local, Contato, TSA_URL: string;
const PosX, PosY, Largura, Altura: Integer;
FonteCert: TCertSource
): Boolean;
implementation
function GerarByteRangePlaceholder(const TamanhoPDF, AssinaturaMax: Integer): RawUTF8;
begin
Result := FormatUTF8('[0 % 0 %]', [100000, 100000 + AssinaturaMax]);
end;
function CriarPlaceholderAssinatura(AssinaturaMax: Integer): RawUTF8;
begin
SetLength(Result, AssinaturaMax * 2);
FillChar(Pointer(Result)^, Length(Result), Ord('0'));
end;
// Função auxiliar para contar ocorrências em RawByteString
function AnsiStringReplaceCount(const Source: RawByteString; const Pattern: RawUtf8): Integer;
var
P, Start: PAnsiChar;
PatLen: Integer;
begin
Result := 0;
if (Source = '') or (Pattern = '') then Exit;
PatLen := Length(Pattern);
Start := Pointer(Source);
P := AnsiStrPos(Start, Pointer(Pattern));
while P <> nil do
begin
Inc(Result);
Start := P + PatLen;
P := AnsiStrPos(Start, Pointer(Pattern));
end;
end;
function AssinarPDF_PAdES_Completo(
const ArquivoOrigem, ArquivoDestino: string;
const CertOrigem, CertSenha: string;
const Razao, Local, Contato, TSA_URL: string;
const PosX, PosY, Largura, Altura: Integer;
FonteCert: TCertSource
): Boolean;
var
OriginalPDF, FinalPDF: RawByteString;
ByteRangeStr, PlaceholderAssinatura: RawUtf8;
AssinaturaMax, PosicaoAssinatura: Integer;
Cert: TCryptCert;
Assinatura: RawByteString;
SHA: TSHA256Digest;
StreamOut: TFileStream;
AssinaturaHex: RawUTF8;
SigCount: Integer;
tmpPEM: RawUtf8;
begin
Result := False;
RegisterOpenSsl;
RegisterX509;
if not FileExists(ArquivoOrigem) then
Exit;
OriginalPDF := StringFromFile(ArquivoOrigem);
AssinaturaMax := 8192;
ByteRangeStr := GerarByteRangePlaceholder(Length(OriginalPDF), AssinaturaMax);
PlaceholderAssinatura := CriarPlaceholderAssinatura(AssinaturaMax);
FinalPDF := OriginalPDF +
Format(#13#10 +
'1 0 obj << /Type /Sig /Filter /Adobe.PPKLite /SubFilter /adbe.pkcs7.detached ' +
'/ByteRange % /Contents <%> /Reason (%)/M (D:%s)/ContactInfo (%)/Location (%) ' +
'/Rect [%d %d %d %d] >> endobj'#13#10 + '%%EOF',
[ByteRangeStr, PlaceholderAssinatura, Razao,
FormatDateTime('yyyymmddhhmmss', Now), Contato, Local,
PosX, PosY, PosX + Largura, PosY + Altura]);
SHA := SHA256Digest(FinalPDF);
Cert := TCryptCert.Create;
try
case FonteCert of
csPFX:
begin
if not Cert.LoadFromFile(CertOrigem, cccCertOnly, CertSenha) then
begin
ShowMessage('Erro ao carregar o certificado PFX com senha');
Exit;
end;
end;
csWindowsStore:
begin
tmpPEM := GetOneSystemStoreAsPem(scsMY);
if tmpPEM = '' then
begin
ShowMessage('Não foi possível obter certificados da Windows Store');
Exit;
end;
if not Cert.Load(tmpPEM, cccCertWithPrivateKey, '') then
begin
ShowMessage('Erro ao carregar certificado da store');
Exit;
end;
end;
end;
// Se houver TSA, aplicar carimbo de tempo (exemplo básico)
if TSA_URL <> '' then
// Cert.TsaUrl := TSA_URL; // Não implementado em mORMot2
Assinatura := Cert.Sign(@SHA, SizeOf(SHA), cuDigitalSignature);
if Assinatura = '' then
begin
ShowMessage('Erro ao gerar assinatura.');
Exit;
end;
AssinaturaHex := BinToHex(Pointer(Assinatura), Length(Assinatura));
PosicaoAssinatura := Pos('<' + PlaceholderAssinatura + '>', UTF8ToString(FinalPDF));
if PosicaoAssinatura <= 0 then Exit;
Delete(FinalPDF, PosicaoAssinatura + 1, Length(PlaceholderAssinatura));
Insert(AssinaturaHex, FinalPDF, PosicaoAssinatura + 1);
StreamOut := TFileStream.Create(ArquivoDestino, fmCreate);
try
StreamOut.WriteBuffer(Pointer(FinalPDF)^, Length(FinalPDF));
Result := True;
finally
StreamOut.Free;
end;
// Contar quantas assinaturas já existem (assina em sequência)
SigCount := 0;
SigCount := AnsiStringReplaceCount(OriginalPDF, '/SubFilter /adbe.pkcs7.detached');
Inc(SigCount);
ShowMessage(Format('Assinatura #%d aplicada com sucesso: %s', [SigCount, ArquivoDestino]));
finally
Cert.Free;
end;
end;
What is the proper way to instantiate a concrete implementation of TCryptCert when I want to:
Load .pfx with private key
Or load from PEM (from Windows Store or external file)
Am I supposed to call some factory or registration function explicitly before calling TCryptCert.Create?
Any insight or sample would be appreciated.
* Environment:
mORMot 2 (latest GitHub build)
Delphi 11.3
Windows 11 x64
Cert tested: standard ICP-Brasil .pfx (with password), and one from Windows Store (Personal store)
Thank you for your amazing framework and dedication!
Cheers
Last edited by Linces (2025-04-13 11:46:39)
Offline
A good habit with mORMot is to look at the documentation, when you have a doubt.
That is, at the comments above each class or method.
/// abstract parent class to implement ICryptCert, as returned by Cert() factory
// - you should never use this class, but the ICryptCert instances
// - type is only defined here to be inherited with the actual provider units
TCryptCert = class(TCryptInstance, ICryptCert)
So you should never call TCryptCert.Create, but the Cert() function factory.
Offline
Hi
Thank you for your reply, unfortunately I don't have enough knowledge in mormot, I'm doing my best to understand everything. I'm reading the documentation and looking at examples, but due to the tight time, everything is still very confusing. What I've managed so far is:
case FonteCert of
csPFX:
begin
Certificado := Cert('x509-rs256').Instance;
if not(Certificado.LoadFromFile(CertOrigem, cccCertOnly, CertSenha)) or not(Assigned(Certificado)) then
begin
ShowMessage('Erro ao carregar certificado PFX: ' + '');
Exit;
end;
Assinatura := Certificado.Sign(@SHA, SizeOf(SHA), cuDigitalSignature);
if Assinatura = '' then
begin
ShowMessage('Erro ao gerar assinatura.');
Exit;
end;
end;
csWindowsStore:
begin
Armazem := Store('x509-store');
if Armazem = nil then
begin
ShowMessage('Erro ao obter a store de certificados');
Exit;
end;
Certificado := Armazem.GetBySerial(CertOrigem); // Espera serial como hex string
if not Assigned(Certificado) then
begin
ShowMessage('Erro ao localizar certificado pela serial na store');
Exit;
end;
end;
end;
But I still can't open the WindowsStore or sign the document, I didn't see any examples of this in the documentation, the automated tests I found in the test folder don't have this.
Offline
The problem is that you try to sign with a certificate.
Digital signature is not done with a certificate, but with a private key.
The certificate is to verify the certificate, not signing it. The certificate is a public key with some validity parameters (like a signing chain of trust).
Offline