0

I've written an electron application on my linux machine, and I'm encountering an error when trying to run it on my windows machine.

The error is:

[16100:0412/130215.483:ERROR:cert_issuer_source_aia.cc(34)] Error parsing cert retrieved from AIA (as DER): ERROR: Couldn't read tbsCertificate as SEQUENCE ERROR: Failed parsing Certificate

I am launching two windows, one auth window, then one for the main process, to handle authentication via OAuth I followed this article: https://auth0.com/blog/securing-electron-applications-with-openid-connect-and-oauth-2, adapting it to my use-case with Keycloak.

When I run on my Linux machine (debian) everything works fine. When I run using my windows machine, I'm getting the error.

The issue I'm having, is I cannot find where the error is coming from. I'm not getting any logs anywhere, except for what I've already provided.

I've tried adding app.commandLine.appendSwitch('ignore-certificate-errors') per this answer: Selenium Issue : ERROR: Couldn't read tbsCertificate (originally intended for selenium).

I can post code as necessary, but will be unable to provide access to the full repo as it is private.

Main Process:

import {createMainWindow} from "./windows/main.window";
import {app, BrowserWindow, protocol} from "electron";
import {createAuthWindow} from "./windows/auth.window";
import * as authService from './services/auth.service'
import {hasConfig} from "./services/config.service";
import {registerConfigAPIHandlers} from "./apis/config/config-api.main";
import {registerAuthAPIHandlers} from "./apis/auth/auth-api.main";

if (require('electron-squirrel-startup')) {
  app.quit();
}

export async function showWindow() {
  if(hasConfig()) {
    try {
      await authService.refreshTokens();
      createMainWindow();
    } catch (err) {
      createAuthWindow();
    }
  } else {
    createMainWindow();
  }
}


app.on('ready', () => {
  registerAuthAPIHandlers();
  registerConfigAPIHandlers();
  showWindow();
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    showWindow()
  }
});

protocol.registerSchemesAsPrivileged([
  { scheme: 'http', privileges: { standard: true, bypassCSP: true, corsEnabled: true} },
  { scheme: 'https', privileges: { standard: true, bypassCSP: true, corsEnabled: true} },
  { scheme: 'mailto', privileges: { standard: true } },
]);

Preload Process:

import {exposeAuthAPI} from "./apis/auth/auth-api.renderer";
import {exposeConfigAPI} from "./apis/config/config-api.renderer";



// Register the API with the contextBridge
process.once("loaded", () => {
    exposeAuthAPI();
    exposeConfigAPI();
});

main.window.ts

import {BrowserWindow} from "electron";
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;

let mainWindow: BrowserWindow;
export const createMainWindow = (): void => {
    destroyMainWindow();

    const width = 1000;
    const height = 700;

    mainWindow = new BrowserWindow({
        height,
        width,
        webPreferences: {
            preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
        },
    });

    mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);

    mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
        (details, callback) => {
            callback({ requestHeaders: { Origin: '*', ...details.requestHeaders } });
        },
    );

    mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
        callback({
            responseHeaders: {
                'Access-Control-Allow-Origin': ['*'],
                'Access-Control-Allow-Headers': ["Authorization", "realm"],
                ...details.responseHeaders,
            },
        });
    });

    if (process.env.NODE_ENV === "development") {
        mainWindow.webContents.openDevTools();
    }
};

export const destroyMainWindow = (): void => {
    if(mainWindow != null) {
        mainWindow.close();
    }
    mainWindow = null;
}

auth.window.ts

import {BrowserWindow} from "electron";
import * as authService from '../services/auth.service'
import {createMainWindow, destroyMainWindow} from "./main.window";
import {getConfig} from "../services/config.service";

let authWindow: BrowserWindow
export function createAuthWindow() {
    destroyAuthWin();

   authWindow = new BrowserWindow({
        width: 1000,
        height: 600,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true
        }
    });

    authWindow.loadURL(authService.getAuthenticationURL());

    const {session: {webRequest}} = authWindow.webContents;

    const filter = {
        urls: [
            `${getConfig().redirectUri}*`
        ]
    };

    webRequest.onBeforeRequest(filter, async ({url}) => {
        await authService.loadTokens(url);
        createMainWindow();
        return destroyAuthWin();
    });

    authWindow.on("close", () => {
        authWindow = null
    })
}

export function destroyAuthWin() {
    if (!authWindow) return;
    authWindow.close();
    authWindow = null;
}

export async function logoutAndLaunchAuthWindow() {
    await authService.logout();
    destroyMainWindow();
    createAuthWindow();
}

auth.service.ts


import jwtDecode from "jwt-decode";
import axios from "axios"
import url from 'url'
import keytar from 'keytar'
import os from 'os'
import {getConfig} from "./config.service";

const keytarService = 'electron-openid-oauth';
const keytarAccount = os.userInfo().username;

let accessToken: string = null;
let identity: string = null;
let refreshToken: string = null;

export function getAccessToken() {
    return accessToken;
}

export function getIdentity() {
    return identity;
}

export function getBaseUrl() {
    const {authHost, tenant} = getConfig();

    return `${authHost}/realms/${tenant}/protocol/openid-connect`
}

export function getAuthenticationURL() {
    const {redirectUri, authClient} = getConfig();
    return (
        `${getBaseUrl()}/auth?client_id=${authClient}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=openid`
    );
}

export async function refreshTokens() {
    const refreshToken = await keytar.getPassword(keytarService, keytarAccount);

    if (refreshToken) {
        const refreshOptions = {
            method: 'POST',
            url: `${getBaseUrl()}/token`,
            headers: {
                'content-type': 'application/x-www-form-urlencoded'
            },
            data: {
                grant_type: 'refresh_token',
                client_id: getConfig().authClient,
                refresh_token: refreshToken,
            }
        };

        try {
            const response = await axios(refreshOptions);
            accessToken = response.data.access_token;
            console.log("before decode")
            identity = jwtDecode(response.data.id_token);
            console.log("after decode")
        } catch (error) {
            await logout();
            throw error;
        }
    } else {
        throw new Error("No available refresh token.");
    }
}

export async function loadTokens(callbackURL: string) {
    const urlParts = url.parse(callbackURL, true);
    const query = urlParts.query;

    const {redirectUri, authClient} = getConfig();


    const exchangeOptions = {
        'grant_type': 'authorization_code',
        'client_id': authClient,
        'code': query.code,
        'redirect_uri': redirectUri,
    };

    const options = {
        method: 'POST',
        url: `${getBaseUrl()}/token`,
        headers: {
            'content-type': 'application/x-www-form-urlencoded'
        },
        data: exchangeOptions,
    };

    try {
        const response = await axios(options);
        accessToken = response.data.access_token;
        console.log("beforeDecode")
        identity = jwtDecode(response.data.id_token);
        console.log("afterDecode")
        refreshToken = response.data.refresh_token;

        console.log(accessToken)

        if (refreshToken) {
            console.log("before keytar")
            await keytar.setPassword(keytarService, keytarAccount, refreshToken);
            console.log("after keytar")
        }

    } catch (error) {
        console.log("before logout")
        await logout();
        console.log("inside catch");
        console.error(error)
        throw error;
    }
}

export async function logout() {
    const {authClient} = getConfig();
    console.log("logout")

    if (refreshToken) {
        const refreshOptions = {
            method: 'POST',
            url: getLogOutUrl(),
            headers: {
                'content-type': 'application/x-www-form-urlencoded'
            },
            data: {
                grant_type: 'refresh_token',
                client_id: authClient,
                refresh_token: refreshToken,
            }
        };

        try {
            await axios(refreshOptions);
        } catch (error) {
            console.error(error)
        }
    }
    await keytar.deletePassword(keytarService, keytarAccount);
    accessToken = null;
    identity = null;
    refreshToken = null;
}

export function getLogOutUrl() {
    return `${getBaseUrl()}/logout`;
}

Any help would be massively appreciated!

protob
  • 3,317
  • 1
  • 8
  • 19

0 Answers0