I want to get a token from a desktop app that calls an OAuth2 authentication server that requires an HTTPS connection for the callback.
For this, I created a local Indy web server and use a self-signed SSL certificate created with Indy and OpenSSL after many discussions with ChatGPT - I have already been informed :) That's a great way to write bad code!
The error is:
raised exception class EIdOSSLLoadingCertError with message 'Could not load certificate. error:0D07209B:asn1 encoding routines:ASN1_get_object:too long'.
I assume that the certificate was not generated correctly
The app code is as follows:
type TForm2 = class(TForm) ... Memo1: TMemo; btnLoginANAF: TButton; RESTClient1: TRESTClient; RESTRequest1: TRESTRequest; RESTResponse1: TRESTResponse; Label1: TLabel; procedure Label1DblClick(Sender: TObject); procedure btnLoginANAFClick(Sender: TObject); procedure Label1DblClick(Sender: TObject); procedure btnLoginANAFClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private FHTTPServer: TIdHTTPServer; FIOHandler: TIdServerIOHandlerSSLOpenSSL; FAuthCode: string; procedure StartLocalServer; procedure StopLocalServer; procedure HandleRequest(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); function RequestAccessToken(AuthCode: string): Boolean; public { Public declarations } end;var Form2: TForm2;const ...implementation{$R *.dfm}...procedure TForm2.FormCreate(Sender: TObject);begin FHTTPServer := TIdHTTPServer.Create(nil); if IdSSLOpenSSL.LoadOpenSSLLibrary then begin Memo1.Lines.Add('OpenSSL version: '+ IdSSLOpenSSL.OpenSSLVersion); Memo1.Lines.Add(''); FIOHandler := TIdServerIOHandlerSSLOpenSSL.Create(FHTTPServer); FHTTPServer.IOHandler := FIOHandler; // Configurarea SSL FIOHandler.SSLOptions.CertFile := '.\CertificatTest.pem'; FIOHandler.SSLOptions.KeyFile := '.\CheiePrivata.crt'; // Același fișier ca CertFile FIOHandler.SSLOptions.RootCertFile := ''; // Nu este necesar pentru certificat auto-semnat FIOHandler.SSLOptions.Mode := sslmServer; FIOHandler.SSLOptions.VerifyMode := []; FIOHandler.SSLOptions.VerifyDepth := 0; end else raise Exception.Create(IdSSLOpenSSLHeaders.WhichFailedToLoad()); StartLocalServer;end;procedure TForm2.StartLocalServer;begin FHTTPServer.DefaultPort := 8080; FHTTPServer.OnCommandGet := HandleRequest; FHTTPServer.Active := True; // <-- Error Hereend;procedure TForm2.StopLocalServer;begin FHTTPServer.Active := False; FHTTPServer.Free;end;procedure TForm2.FormDestroy(Sender: TObject);begin StopLocalServer;end;procedure TForm2.HandleRequest(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);var Code: string;begin if ARequestInfo.Document = '/callback' then begin Code := ARequestInfo.Params.Values['code']; if Code <> '' then begin FAuthCode := Code; AResponseInfo.ContentText :='Successful authorization. You can close this window.'; if RequestAccessToken(Code) then begin // Successful authorization. You can close this window.) Memo1.Lines.Add('Successful authorization. AuthCode='+ Code) end; end else begin AResponseInfo.ContentText := 'Error: The authorization code was not received.'; end; end else begin AResponseInfo.ResponseNo := 404; AResponseInfo.ContentText := 'Page not found'; end;end;function TForm2.RequestAccessToken(AuthCode: string): Boolean;var Response: TJSONObject; AccessToken: string;begin Result := False; RESTClient1.BaseURL := TOKEN_ENDPOINT; RESTRequest1.Method := rmPOST; RESTRequest1.Params.Clear; RESTRequest1.Params.AddItem('grant_type', 'authorization_code'); RESTRequest1.Params.AddItem('code', AuthCode); RESTRequest1.Params.AddItem('redirect_uri', REDIRECT_URI); RESTRequest1.Params.AddItem('client_id', CLIENT_ID); RESTRequest1.Params.AddItem('client_secret', CLIENT_SECRET); try RESTRequest1.Execute; if RESTResponse1.StatusCode = 200 then begin Response := RESTResponse1.JSONValue as TJSONObject; AccessToken := Response.GetValue('access_token').Value; // Save or use AccessToken as needed Memo1.Lines.Add('Acces token:'); Memo1.Lines.Add(AccessToken); Result := True; end; except on E: Exception do begin ShowMessage('Error obtaining the access token: '+ E.Message); end; end;end;procedure TForm2.Label1DblClick(Sender: TObject); procedure GenerateSelfSignedCertificate(const AFileName: string); procedure GenerateRandomSerialNumber(SerialNumber: PASN1_INTEGER); var RandomValue: Integer; I: Integer; begin RandomValue := 0; for I := 1 to 4 do begin RandomValue := (RandomValue shl 8) or Random(256); end; // We make sure that the number is positive RandomValue := RandomValue and $7FFFFFFF; // We set the serial number if ASN1_INTEGER_set(SerialNumber, RandomValue) = 0 then raise Exception.Create('Error setting the serial number'); end; function DateToASN1_TIME(const ADate: TDateTime): PASN1_TIME; var AStruct: TSystemTime; ASN1_Time: PASN1_TIME; begin DateTimeToSystemTime(ADate, AStruct); ASN1_Time := ASN1_TIME_set(nil, SystemTimeToDateTime(AStruct)); Result := ASN1_Time; end; var Cert: PX509; PrivateKey: PEVP_PKEY; SerialNumber: PASN1_INTEGER; Name: PX509_NAME; BioCert, BioKey: PBIO; NotBefore, NotAfter: TDateTime; LoadOpenSSLLibraryErr: string; begin // Create new certificate try Cert := X509_new; except raise Exception.Create('Error creating certificate X509') end; try // New RSA Private Key Generation PrivateKey := EVP_PKEY_new; try if EVP_PKEY_assign(PrivateKey, EVP_PKEY_RSA, PByte(RSA_generate_key(2048, RSA_F4, nil, nil))) = 0 then raise Exception.Create('Error generating private key'); // Certificate Version Setting (X509 v3) X509_set_version(Cert, 2); // Random Serial Number Generation SerialNumber := ASN1_INTEGER_new; if SerialNumber = nil then raise Exception.Create('Error creating ASN1_INTEGER'); try GenerateRandomSerialNumber(SerialNumber); if X509_set_serialNumber(Cert, SerialNumber) = 0 then raise Exception.Create ('Error setting the serial number for the certificate'); finally ASN1_INTEGER_free(SerialNumber); end; // Set the start and end date of the certificate NotBefore := Now - 1; // The certificate is valid from yesterday NotAfter := NotBefore + 364; // The certificate is valid for one year X509_set_notBefore(Cert, DateToASN1_TIME(NotBefore)); X509_set_notAfter(Cert, DateToASN1_TIME(NotAfter)); // Public Key Setting X509_set_pubkey(Cert, PrivateKey); // Setting Issuer and Subject Name (localhost) Name := X509_get_subject_name(Cert); X509_NAME_add_entry_by_txt(Name, 'CN', MBSTRING_ASC, PAnsiChar('localhost'), -1, -1, 0); X509_set_issuer_name(Cert, Name); // Certificate Signing if X509_sign(Cert, PrivateKey, EVP_sha256) = 0 then raise Exception.Create('Error signing the certificate'); // Save certificate to PEM file BioCert := BIO_new_file(PAnsiChar(AnsiString(AFileNamePem)), 'w+'); try if PEM_write_bio_X509(BioCert, Cert) = 0 then raise Exception.Create ('Error saving certificate to PEM file'); finally BIO_free(BioCert); end; // Save Private Key to CRT File BioKey := BIO_new_file(PAnsiChar(AnsiString(AFileNameCrt)), 'w+'); try if PEM_write_bio_PrivateKey(BioKey, PrivateKey, nil, nil, 0, nil, nil) = 0 then raise Exception.Create ('Error saving private key to CRT file'); finally BIO_free(BioKey); end; finally EVP_PKEY_free(PrivateKey); end; finally X509_free(Cert); end; end;begin GenerateSelfSignedCertificate('.\CertificatTest.pem', '.\CheiePrivata.crt'); ShowMessage('Certificate and private key successfully generated!');end;procedure TForm2.btnLoginANAFClick(Sender: TObject);var URL: string;begin URL := AUTH_ENDPOINT +'?response_type=code'+'&client_id='+ TNetEncoding.URL.Encode(CLIENT_ID) +'&redirect_uri='+ TNetEncoding.URL.Encode(REDIRECT_URI) +'&scope='+ TNetEncoding.URL.Encode(SCOPE); ShellExecute(0, 'open', PChar(URL), nil, nil, SW_SHOWNORMAL);end;...InitializationRandomize;IdOpenSSLSetLibPath(ExtractFilePath(Application.ExeName));end.