Search code examples
androiddelphissl-certificatefiremonkeyindy

Get SSL Certificate expiration date from client-cert.pem file stored locally in Android


If I wanted to check the expiration date of a SSL certificate I can use IdHTTP connected to a IdSSLIOHandlerSocketOpenSSL

IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL1;

And then tapping into OnVerifyPeer

function TForm1.IdSSLIOHandlerSocketOpenSSL1VerifyPeer(Certificate: TIdX509;
  AOk: Boolean; ADepth, AError: Integer): Boolean;
begin
    Showmessage(Certificate.notAfter));
end;

And finally doing a IdHTTP.Get to the server I want to retrieve the server certificate of, and this works.

BUT

I have a client certificate stored locally on my Android device that gets deployed with the App, how would I access the Certificate?

meaning how would I get the certificate loaded into a TidX509 Object? the constructor for TidX509 requires a PX509 which is then assigned to a pointer of a record X509 so I am very lost in there


Solution

  • As Remy pointed out I just had to look how OpenSSL loads the Certificate from memory to get the certificate

    but first the value that you get from reading the certificates notAfter is UTC Time value in String so you will need something like this

    function StringUTCtoDATETIME(UTCString : String) : TDateTime;
    begin
        if UTCString.Length = 13 then
        begin
            Delete(UTCString,UTCString.Length,1);
            Delete(UTCString,0,2);
            Insert(Copy(FormatDateTime('yyyy',Now),0,2),UTCString,0);
        end
        else
        begin
            Delete(UTCString,UTCString.Length,1);
        end;
        Result := EncodeDateTime(StrToInt(Copy(UTCString,1,4)),StrToInt(Copy(UTCString,5,2)),StrToInt(Copy(UTCString,7,2)),
        StrToInt(Copy(UTCString,9,2)),StrToInt(Copy(UTCString,11,2)),StrToInt(Copy(UTCString,13,2)),000);
    end;
    

    The value conforms to the ISO8601 standard which can be YYMMDDHHNNSS or YYYYMMDDHHNNSS so I just made provision for that the value returned will always be YYYYMMDDHHNNSS

    And to get the value we load it into memory and work with it as Indy does.

    function ReturnCertificateExpiryDate(const AFileName: String): TDateTime;
      var
          LM : TMemoryStream;
          LX: PX509;
          LB: PBIO;
    begin
        LM := nil;
        try
            LM := TMemoryStream.Create;
            LM.LoadFromFile(AFileName);
        except
            SSLerr(SSL_F_SSL_LOAD_CLIENT_CA_FILE, ERR_R_SYS_LIB);
            LM.Free;
            Exit;
        end;
    
        if LM = nil then
        begin
            Exit;
        end;
        try
            LB := BIO_new_mem_buf(LM.Memory, LM.Size);
            if Assigned(LB) then begin
                LX := PEM_read_bio_X509(LB, nil, nil, nil);
                if LX<> nil then
                begin
                    RESULT := StringUTCtoDATETIME(String(LX.cert_info.validity.notAfter.data));
                end;
            end;
        finally
            FreeAndNil(LM);
        end;
    end;
    

    And you also need to create a Context variable

    var
        Context : TIdSSLContext;
    begin
      Context := TIdSSLContext.Create;
      ShowMessage(FormatDateTime('yyyy-mm-dd hh:nn:ss',(ReturnCertificateExpiryDate(TPath.Combine(TPath.GetDocumentsPath, 'client-cert.pem')))));
    end;