Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020-2024 Estonian Information System Authority
Copyright (c) Estonian Information System Authority

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ The Web eID extension for Safari is built as a [Safari web extension](https://de
TOKEN_SIGNING_BACKWARDS_COMPATIBILITY=true npm run clean build package
```

During development, for additional logging, set the `DEBUG` environment variable to `true`.
```bash
DEBUG=true npm run clean build package
```

5. Load in Firefox as a Temporary Extension
1. Open `about:debugging#/runtime/this-firefox`
2. Click "Load temporary Add-on..." and open `/web-eid-webextension/dist/manifest.json`
Expand All @@ -62,3 +57,33 @@ The Web eID extension for Safari is built as a [Safari web extension](https://de
Make sure the `NATIVE_APP_NAME` value in `src/config.ts` matches the one in
the Web-eID native application manifest file.

### Developer tools

The Web eID DevTools tab can be useful while integrating Web eID on a website.

#### Features
- Event history between the website, extension and native application.
- Extension's internal log messages.
- Option to override extension settings, without needing to compile the extension yourself.
- Option to allow the http://localhost origin when authenticating and signing

#### Enable or disable the Web eID developer tools tab

**Firefox**
1. Open [Menu -> Settings -> Extensions and themes]
or navigate to the address `about:addons`
2. Open the Web eID extension details
3. Open the "Permissions" tab
4. Toggle "Extend developer tools to access your data in open tabs"

**Chrome**
1. Open [Settings -> Extensions]
or navigate to the address `chrome://extensions/`
2. Open the Web eID extension details
3. Open "Extension options"
4. Toggle "Enable developer tools"

**Safari**
1. Open [Settings -> Extensions]
2. Open the Web eID Settings
3. Toggle "Enable developer tools"
7 changes: 2 additions & 5 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,26 @@ import license from "rollup-plugin-license";
import polyfill from "rollup-plugin-polyfill";
import resolve from "@rollup/plugin-node-resolve";

const projectRootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)));

// List of browsers to build for.
const browsers = ["chrome", "firefox", "safari"];

const processEnvConf = {
DEBUG: process.env.DEBUG,
TOKEN_SIGNING_BACKWARDS_COMPATIBILITY: process.env.TOKEN_SIGNING_BACKWARDS_COMPATIBILITY,
}

const pluginsConf = (environment) => [
alias({
entries: [{
find: "@web-eid.js",
replacement: path.resolve(projectRootDir, "dist/lib/web-eid.js/src"),
replacement: path.resolve(import.meta.dirname, "dist/lib/web-eid.js/src"),
}],
}),
resolve({ rootDir: "./dist" }),
cleanup({ comments: ["jsdoc"] }), // Keep jsdoc comments
license({
banner: {
content: {
file: path.join(projectRootDir, "LICENSE"),
file: path.join(import.meta.dirname, "LICENSE"),
encoding: "utf-8",
},
},
Expand Down
19 changes: 19 additions & 0 deletions scripts/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,25 @@ const targets = {
await cp("./static/icons", "./dist/firefox/icons");
await cp("./static/icons", "./dist/safari");

rem(
"Copying static pages to Firefox dist directory"
);
await cp("./static/views", "./dist/firefox/views");
await rm("./dist/firefox/views/options.*");
await cp("./node_modules/webextension-polyfill/dist/browser-polyfill.min.js", "./dist/firefox/views/browser-polyfill.min.js");

rem(
"Copying static pages to Chrome dist directory"
);
await cp("./static/views", "./dist/chrome/views");
await cp("./node_modules/webextension-polyfill/dist/browser-polyfill.min.js", "./dist/chrome/views/browser-polyfill.min.js");

rem(
"Copying static pages to Safari dist directory"
);
await cp("./static/views", "./dist/safari/views");
await cp("./node_modules/webextension-polyfill/dist/browser-polyfill.min.js", "./dist/safari/views/browser-polyfill.min.js");

rem(
"Setting up the Firefox manifest"
);
Expand Down
37 changes: 33 additions & 4 deletions src/background-safari/services/NativeAppService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import libraryConfig from "@web-eid.js/config";

import Mutex from "../../shared/Mutex";
import calculateJsonSize from "../../shared/utils/calculateJsonSize";
import config from "../../config";
import { config } from "../../shared/configManager";

import Logger from "../../shared/Logger";
import isLoopbackAddress from "../../shared/utils/isLoopbackAddress";

const logger = new Logger("NativeAppService.ts");

type NativeAppPendingRequest =
| { resolve?: (value: PromiseLike<any>) => void; reject?: (reason?: any) => void }
Expand All @@ -28,7 +33,13 @@ export default class NativeAppService {

private pending: NativeAppPendingRequest = null;

constructor(tabId?: number) {
logger.tabId = tabId;
}

async connect(): Promise<{ version: string }> {
logger.log("Connecting to the native application " + config.NATIVE_APP_NAME);

this.state = NativeAppState.CONNECTING;

try {
Expand All @@ -52,6 +63,8 @@ export default class NativeAppService {
if (message.version) {
this.state = NativeAppState.CONNECTED;

logger.info("Connected to the native application", message);

return message;
}

Expand All @@ -72,7 +85,8 @@ export default class NativeAppService {
}

close(error?: any): void {
config.DEBUG && console.log("Disconnecting from native app");
logger.info("Closing connection to native application");

this.state = NativeAppState.DISCONNECTED;

this.pending?.reject?.(error);
Expand All @@ -92,6 +106,8 @@ export default class NativeAppService {
setTimeout(() => { reject(throwAfterTimeout); }, timeout );

const onResponse = (message: any): void => {
logger.log("Response from native application", message);

this.pending = null;

if (message.error) {
Expand All @@ -101,14 +117,27 @@ export default class NativeAppService {
}
};

config.DEBUG && console.log("Sending message to native app", JSON.stringify(message));

const messageSize = calculateJsonSize(message);

logger.info("Calculated message size", messageSize);

if (messageSize > config.NATIVE_MESSAGE_MAX_BYTES) {
throw new Error(`native application message exceeded ${config.NATIVE_MESSAGE_MAX_BYTES} bytes`);
}

if (config.ALLOW_HTTP_LOCALHOST && message.arguments?.origin) {
const url = new URL(message.arguments.origin);

if (url.protocol === "http:" && isLoopbackAddress(url.hostname)) {
url.protocol = "https:";

message.arguments.origin = url.origin;
logger.warn("Setting ALLOW_HTTP_LOCALHOST enabled, replaced origin with " + message.arguments.origin);
}
}

logger.log("Sending message to native application", message);

browser.runtime.sendNativeMessage("application.id", message, onResponse);
});

Expand Down
31 changes: 25 additions & 6 deletions src/background/actions/TokenSigning/getCertificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,42 @@ import {
} from "../../../models/TokenSigning/TokenSigningResponse";

import ByteArray from "../../../shared/ByteArray";
import { MessageSender } from "../../../models/Browser/Runtime";
import NativeAppService from "../../services/NativeAppService";
import config from "../../../config";
import { config } from "../../../shared/configManager";
import errorToResponse from "./errorToResponse";
import threeLetterLanguageCodes from "./threeLetterLanguageCodes";
import tokenSigningResponse from "../../../shared/tokenSigningResponse";

import Logger from "../../../shared/Logger";

const logger = new Logger("TokenSigning/getCertificate.ts");

export default async function getCertificate(
sender: MessageSender,
nonce: string,
sourceUrl: string,
lang?: string,
filter: "AUTH" | "SIGN" = "SIGN",
): Promise<TokenSigningCertResponse | TokenSigningErrorResponse> {
logger.tabId = sender.tab?.id;

logger.log("Certificate requested");

if (lang && Object.keys(threeLetterLanguageCodes).includes(lang)) {
lang = threeLetterLanguageCodes[lang];
logger.log("Language code converted to three-letter code", lang);
}

const nativeAppService = new NativeAppService();
const nativeAppService = new NativeAppService(sender.tab?.id);

logger.info("Checking 'filter', should be 'SIGN'");

if (filter !== "SIGN") {
const { message, name, stack } = new Error("Web-eID only allows signing with a signing certificate");

logger.error({ message, name, stack });

return tokenSigningResponse<TokenSigningErrorResponse>("not_allowed", nonce, {
message,
name,
Expand All @@ -40,9 +55,7 @@ export default async function getCertificate(
}

try {
const nativeAppStatus = await nativeAppService.connect();

config.DEBUG && console.log("Get certificate: connected to native", nativeAppStatus);
await nativeAppService.connect();

const message: NativeGetSigningCertificateRequest = {
command: "get-signing-certificate",
Expand All @@ -61,14 +74,20 @@ export default async function getCertificate(
);

if (!response?.certificate) {
logger.info("Certificate request failed. Expected 'certificate' was not found in the response");

return tokenSigningResponse<TokenSigningErrorResponse>("no_certificates", nonce);
} else {
logger.info("Returning success response");

return tokenSigningResponse<TokenSigningCertResponse>("ok", nonce, {
cert: new ByteArray().fromBase64(response.certificate).toHex(),
});
}
} catch (error) {
console.error(error);
logger.info("Certificate request failed");
logger.error(error);

return errorToResponse(nonce, error);
} finally {
nativeAppService.close();
Expand Down
30 changes: 25 additions & 5 deletions src/background/actions/TokenSigning/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ import {
} from "../../../models/TokenSigning/TokenSigningResponse";

import ByteArray from "../../../shared/ByteArray";
import { MessageSender } from "../../../models/Browser/Runtime";
import NativeAppService from "../../services/NativeAppService";
import config from "../../../config";
import { config } from "../../../shared/configManager";
import errorToResponse from "./errorToResponse";
import threeLetterLanguageCodes from "./threeLetterLanguageCodes";
import tokenSigningResponse from "../../../shared/tokenSigningResponse";

import Logger from "../../../shared/Logger";

const logger = new Logger("TokenSigning/sign.ts");


const digestCommandToHashFunction = {
"sha224": "SHA-224",
Expand All @@ -41,37 +46,46 @@ const hashFunctionToLength = {
} as Record<string, number>;

export default async function sign(
sender: MessageSender,
nonce: string,
sourceUrl: string,
certificate: string,
hash: string,
algorithm: string,
lang?: string,
): Promise<TokenSigningSignResponse | TokenSigningErrorResponse> {
logger.tabId = sender.tab?.id;

logger.log("Signing requested");

if (lang && Object.keys(threeLetterLanguageCodes).includes(lang)) {
lang = threeLetterLanguageCodes[lang];
logger.info("Language code converted to three-letter code", lang);
}

const nativeAppService = new NativeAppService();
const nativeAppService = new NativeAppService(sender.tab?.id);

try {
const warnings: Array<string> = [];
const nativeAppStatus = await nativeAppService.connect();

config.DEBUG && console.log("Sign: connected to native", nativeAppStatus);
await nativeAppService.connect();

let hashFunction = (
Object.keys(digestCommandToHashFunction).includes(algorithm)
? digestCommandToHashFunction[algorithm]
: algorithm
);

logger.debug("Selected hashing function", hashFunction);

const expectedHashByteLength = (
Object.keys(hashFunctionToLength).includes(hashFunction)
? hashFunctionToLength[hashFunction]
: undefined
);

logger.debug("Hash byte length", expectedHashByteLength);

const hashByteArray = new ByteArray().fromHex(hash);

if (hashByteArray.length !== expectedHashByteLength) {
Expand All @@ -94,6 +108,8 @@ export default async function sign(
}
}

warnings.forEach((message) => logger.warn(message));

const message: NativeSignRequest = {
command: "sign",

Expand All @@ -115,16 +131,20 @@ export default async function sign(
);

if (!response?.signature) {
logger.info("Signing failed. Expected 'signature' was not found in the response");
return tokenSigningResponse<TokenSigningErrorResponse>("technical_error", nonce);
} else {
logger.info("Returning success response");
return tokenSigningResponse<TokenSigningSignResponse>("ok", nonce, {
signature: new ByteArray().fromBase64(response.signature).toHex(),

warnings,
});
}
} catch (error) {
console.error(error);
logger.info("Signing failed");
logger.error(error);

return errorToResponse(nonce, error);
} finally {
nativeAppService.close();
Expand Down
Loading