delphissl-certificateindyidhttpdelphi-12-athens

EIdOSSLLoadingCertError "[...] PEM_read_bio:no start line" when trying to post with TIdHTTP and SSLIOHandler


I'm trying to do a POST request in Delphi, using the Indy TIdHTTP component. The target host requires a certificate and key files. Testing the requests with Postman or Insomnia, it works fine, returns 200, all right. But, in Delphi with the procedures I will show, it returns an error:

Project ... raised exception class EIdOSSLLoadingCertError with message 'Could not load certificate. error:0906D06C:PEM routines:PEM_read_bio:no start line'.

I load the certificates from a constant, and they are in the correct format:

-----BEGIN PRIVATE KEY-----                  
...   
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----  
...   
-----END CERTIFICATE-----

The functions in question are these, the error occurs when trying to run the IdHTTP.Post():

function TAPI.LoadCertificate : TIdSSLIOHandlerSocketOpenSSL;
var
  fileCrt, fileKey : TStringList;
  var_path_system: string;
begin
  if assigned(SSLIOHandler) then SSLIOHandler:= nil;

  SSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);

  try
    fileCrt:= TStringList.Create;
    fileKey:= TStringList.Create;

    fileCrt.Add(const_cert);
    fileKey.Add(const_private_key);

    var_path_system:= ExtractFilePath(Application.ExeName);

    fileCrt.SaveToFile(var_path_system+'Certificate.crt', Tencoding.UTF8);
    fileKey.SaveToFile(var_path_system+'PrivateKeyFile.key', Tencoding.UTF8);

    SSLIOHandler.Port:= 443;
    SSLIOHandler.DefaultPort:= 443;

    SSLIOHandler.SSLOptions.CertFile:= var_path_system+'Certificate.crt';
    SSLIOHandler.SSLOptions.KeyFile:= var_path_system+'PrivateKeyFile.key';

    SSLIOHandler.SSLOptions.Method:= sslvTLSv1_2;
    SSLIOHandler.SSLOptions.SSLVersions:= [sslvTLSv1_2];
    SSLIOHandler.SSLOptions.Mode:= sslmClient;
    SSLIOHandler.SSLOptions.VerifyMode:= [] ;
    SSLIOHandler.SSLOptions.VerifyDepth:= 0;
    SSLIOHandler.Host := 'place.holder.com';

  finally
    fileCrt.Free;
    fileKey.Free;
  end;

  result:= SSLIOHandler;

end;

function TAPI.GetToken: TStringBuilder;
  function RemoveSurroundingQuotes(const S: string): string; begin
    Result := S;
    if (Length(S) >= 2) and (S[1] = '"') and (S[Length(S)] = '"') then
      Result := Trim(Copy(S, 2, Length(S) - 2));
  end;
var
  IdHTTP: TIdHTTP;
  SSL: TIdSSLIOHandlerSocketOpenSSL;
  Params: TStringList;
  JsonStreamReturn: TStringStream;
  var_Result: TJSONObject;
  StringBuilder: TStringBuilder;

begin
  IdHTTP := TIdHTTP.Create(nil);
  try
    StringBuilder := TStringBuilder.Create;
    SSL := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
    IdHTTP.IOHandler := SSL;
    SSL.SSLOptions.Method := sslvTLSv1_2;

    JsonStreamReturn := TStringStream.Create('', TEncoding.UTF8);
    try
      Params := TStringList.Create;
      try
        Params.Add('client_id='+ FAAuthorization);
        Params.Add('client_secret=' + FAAuthorizationSecret);
        Params.Add('grant_type=client_credentials');

        IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';

        IdHTTP.IOHandler := LoadCertificate;

        try
          IdHTTP.Post(URLBasic, Params, JsonStreamReturn);

        except
          on E: EIdHTTPProtocolException do
            LogHttpClientError(E, IdHTTP.ResponseCode, false);
          on E: Exception do
            raise;
//           LogHttpClientError();
        end;
[... rest of the  code]

I can't find much information on this specific case. But I know the certificates are valid and with the correct syntax.

I've tried Google'ing it, but found no answer or clarification. I even tried a long talk with ChatGPT, but I can't find more information about this error. The certificates are correct, they load in an archive just fine, and the SSLIOHandler loads the certificate without error.

The certificate was generated and signed by the host, it's still valid. I've also tried using PFX but it returns a [...] ASN1_CHECK_TLEN:wrong tag error. I also tried different SSL options to load the certificate, to no avail. I tested the same again with Postman, and it works just fine with the same host, port, IDs, etc.


Solution

  • The error means that the SSLIOHandler could not load the file specified in its CertFile property. The no start line error is a good indication that there is a problem with the formatting of the certificate file.

    One thing I notice is you are saving the certificate files using TEncoding.UTF8, which defines a BOM. By default, TStringList.SaveTo...() will write out that BOM. But OpenSSL may have trouble processing a UTF-8 encoded certificate file that has a BOM. So, try disabling the TStringList.WriteBOM option when calling TStringList.SaveToFile(), eg:

    fileCrt.WriteBOM := False;
    fileKey.WriteBOM := False;
    

    Alternatively, you can use TEncoding.ASCII instead, which does not define a BOM. PEM-encoded certificate files do not use non-ASCII characters, so TEncoding.ASCII will produce the same textual content as TEncoding.UTF8, but without a BOM.