Jump to content
mitzi

TX509Base.LoadFromP12Buffer

Recommended Posts

Hi,

can you please add this function to your components? It is tested and works and allows to load PFX/P12 certificates from memory. It is based on your LoadFromP12File function.

procedure TX509Base.LoadFromP12Buffer(ABuffer: Pointer; ABufferSize: Cardinal; IncludePKey,
  IncludeInters: TCertReadOpt; const Password: String);
var
    FileBio : PBIO;
    P12 : PPKCS12;
    Cert : PX509;
    PKey : PEVP_PKEY;
    Ca: PSTACK_OF_X509;
    PW: PAnsiChar;
    I: integer;
begin
    InitializeSsl;

    FileBio := f_BIO_new_mem_buf(ABuffer,ABufferSize);
    if not Assigned(FileBio) then
      RaiseLastOpenSslError(EX509Exception, TRUE,
                              'Error reading PKCS12 certificates from buffer');
    try
        P12 := f_d2i_PKCS12_bio(FileBio, Nil);
        if not Assigned(P12) then
            RaiseLastOpenSslError(EX509Exception, TRUE,
                              'Error reading PKCS12 certificates from buffer');
        try
            Cert := Nil;
            Pkey := Nil;
            Ca := Nil;
            PW := Nil;
            if Length(Password) > 0 then PW := PAnsiChar(PasswordConvert(Password));   { V8.55 }
            if f_PKCS12_parse(P12, PW, Pkey, Cert, Ca) = 0 then begin
                if Ics_Ssl_ERR_GET_REASON(f_ERR_peek_error) = 113 then  { PKCS12_R_MAC_VERIFY_FAILURE }
                    raise EX509Exception.Create('Error PKCS12 Certificate password invalid for buffer')
                else
                    RaiseLastOpenSslError(EX509Exception, TRUE,
                              'Error parsing PKCS12 certificates from buffer');
            end;
            if (IncludePKey > croNo) then begin   { V8.50 don't ignore croYes }
                if Assigned(PKey) then begin
                    if (f_X509_check_private_key(Cert, PKey) < 1) then
                        raise EX509Exception.Create('Certificate and private key do not match');
                     SetX509(Cert);
                     SetPrivateKey(PKey);
                     f_EVP_PKEY_free(PKey);
                end
                else begin
                    if IncludePKey = croYes then  { V8.50 require  private key so error }
                        raise EX509Exception.Create('Error reading private key from buffer');
                end;
            end
            else
                SetX509(Cert);
            f_X509_free(Cert);
            FreeAndNilX509Inters;
          { intermediate certificates are optional, no error if none found }
            if (IncludeInters > croNo) and Assigned(Ca) then begin   { V8.50 don't ignore croYes }
                FX509Inters := f_OPENSSL_sk_new_null;
                for I := 0 to f_OPENSSL_sk_num(Ca) - 1 do
                    f_OPENSSL_sk_insert(FX509Inters, PAnsiChar(f_X509_dup
                                    (PX509(f_OPENSSL_sk_value(Ca, I)))), I);
                f_OPENSSL_sk_free(Ca);
            end;
        finally
            f_PKCS12_free(p12);
        end;
    finally
        f_bio_free(FileBio);
    end;
end;

 

Edited by mitzi

Share this post


Link to post

Thanks, added to my master, will be in SVN in a couple of days with other minor SSL improvements. 

 

Are you working with the Windows certificate store by any chance, by pending list includes putting certificates (Let's Encrypt) into the store. 

 

Angus

 

Share this post


Link to post

ICS has code to read certificates from the Windows certificate store, but not to add them.  Not needed for ICS applications generally, but for servers using SChannel.

 

Angus

Share this post


Link to post

What kind of certificates? PEM,DER,PFX? If you are able to get certificate context then adding to windows store is simple: (it uses WinCrypt API functions)

 

function AddCertContextToStore(ACertContext: PCCERT_CONTEXT; const AStoreName: string = 'MY'): boolean;  // other store names are e.g CA or ROOT
var
  cs: HCERTSTORE;
begin
  cs:=CertOpenStore(CERT_STORE_PROV_SYSTEM,0,0,CERT_SYSTEM_STORE_CURRENT_USER,PChar(AStoreName));
  try
    Result:=Assigned(cs) and CertAddCertificateContextToStore(cs,ACertContext,CERT_STORE_ADD_REPLACE_EXISTING,nil);
  finally
    CertCloseStore(cs,0);
  end;
end;

 

But obtaining the certificate contexts from different file(certificate) types is more complicated. Following function gets first certificate context from PEM,P7C,P7B,CER,CRT,DER certificate files, PFX/P12 and combined PEM files are more difficult.

 

function GetCertContext(const AFilename: string; out ACertContext: PCCERT_CONTEXT): boolean;
var
  cs: HCERTSTORE;
begin
  Result:=False;
  ACertContext:=nil;
  cs:=CertOpenStore(CERT_STORE_PROV_FILENAME,X509_ASN_ENCODING or PKCS_7_ASN_ENCODING,0,CERT_STORE_OPEN_EXISTING_FLAG or CERT_STORE_READONLY_FLAG,PChar(AFilename));
  if Assigned(cs) then begin
    ACertContext:=CertEnumCertificatesInStore(cs,nil);
    Result:=Assigned(ACertContext);
    CertCloseStore(cs,0);
  end;
end;

 

Edited by mitzi

Share this post


Link to post

Only concerned about internal DER ASN_ENCODING format which ICS already supports, not file formats. 

 

OverbyteIcsPemtool1.pas already has code for opening a store, getting certificate contexts and converting them to X509 and PEM, need to move that into one of the library units, create a PCCERT_CONTEXT and add it to the correct store. 

 

Angus

 

Share this post


Link to post

Another question: How to convert TX509Base.SubjectOneLine to readable normalized form?

I get e.g. /C=CZ/postalCode=00000/ST=\x00L\x00i\x00b\x00e\x00r\x00e\x00c\x00k\x00\xFD/L=SomeCity/street=SomeStreet/O=SomeName/OU=IT/CN=SomeName

but need

C=CZ, PostalCode=00000, S=Liberecký, L=SomeCity, STREET=SomeStreet, O=SomeName, OU=IT, CN=SomeName

Edited by mitzi

Share this post


Link to post

All the common subject and issuer fields are available as properties, ie SubjectCName, SubjectOName, IssuerCName, SubjectCOName, SubAltNameDNS, etc, or you can use GetNameEntryByNid with the NID literal for obscure ones like STREET, not even sure that exists...

 

Look at the function ListCertDetail in OverbyteIcsPemtool1.pas which creates a string detailing most certificate fields.

 

Angus

 

Edited by Angus Robertson

Share this post


Link to post

Yes but when i want to compare Subject of one certificate with subject of another one, then I need whole Subject line in right order and filled fields. I don't know what fields are filled in certificate so I can't successfully compose whole subject line for comparison.

So it would be nice to have SubjectOneLine (and IssuerOneLine) in mentioned form. 

I get this info in correct form from another certificate with no problem via WinCrypt API and need to compare it with data returned by ICS in TX509Base.

Share this post


Link to post

OpenSSL does have newer functions to format certificate information, but most users want single fields, not a list of cryptic fields.

 

Angus

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×