Skip to content

Commit

Permalink
Better signature algorithm for self-signed certificate.
Browse files Browse the repository at this point in the history
  • Loading branch information
coddingtonbear committed Jan 28, 2022
1 parent 83d188a commit b270735
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 5 deletions.
7 changes: 7 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LocalRestApiSettings } from "./types";

export const CERT_NAME = "obsidian-local-rest-api.crt";

export const DEFAULT_SETTINGS: LocalRestApiSettings = {
port: 27124,
};
85 changes: 80 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import forge, { pki } from "node-forge";
import RequestHandler from "./requestHandler";
import { LocalRestApiSettings } from "./types";

const DEFAULT_SETTINGS: LocalRestApiSettings = {
port: 27124,
};
import { CERT_NAME, DEFAULT_SETTINGS } from "./constants";

export default class LocalRestApi extends Plugin {
settings: LocalRestApiSettings;
Expand All @@ -21,6 +19,12 @@ export default class LocalRestApi extends Plugin {

this.app;

if (this.settings.crypto && this.settings.crypto.resetOnNextLoad) {
delete this.settings.apiKey;
delete this.settings.crypto;
this.saveSettings();
}

if (!this.settings.apiKey) {
this.settings.apiKey = forge.md.sha256
.create()
Expand All @@ -35,11 +39,56 @@ export default class LocalRestApi extends Plugin {
expiry.setDate(today.getDate() + 365);

const keypair = forge.pki.rsa.generateKeyPair(2048);
const attrs = [{ name: "commonName", value: "Obsidian Local REST API" }];
const certificate = forge.pki.createCertificate();
certificate.setIssuer(attrs);
certificate.setSubject(attrs);
certificate.setExtensions([
{
name: "basicConstraints",
cA: true,
},
{
name: "keyUsage",
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true,
},
{
name: "extKeyUsage",
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true,
},
{
name: "nsCertType",
client: true,
server: true,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true,
},
{
name: "subjectAltName",
altNames: [
{
type: 7, // IP
ip: "127.0.0.1",
},
],
},
]);
certificate.serialNumber = "1";
certificate.publicKey = keypair.publicKey;
certificate.validity.notAfter = expiry;
certificate.validity.notBefore = today;
certificate.sign(keypair.privateKey);
certificate.sign(keypair.privateKey, forge.md.sha256.create());

this.settings.crypto = {
cert: pki.certificateToPem(certificate),
Expand Down Expand Up @@ -109,11 +158,24 @@ class SampleSettingTab extends PluginSettingTab {
text: `Authorization: Bearer ${this.plugin.settings.apiKey}`,
});

apiKeyDiv.createEl("a", {
const seeMore = apiKeyDiv.createEl("p");
seeMore.createEl("a", {
href: "https://coddingtonbear.github.io/obsidian-local-rest-api/",
text: "See more information and examples in our interactive OpenAPI documentation.",
});

const importCert = apiKeyDiv.createEl("p");
importCert.createEl("span", {
text: "By default this plugin uses a self-signed certificate for HTTPS; you may want to ",
});
importCert.createEl("a", {
href: `https://127.0.0.1:${this.plugin.settings.port}/${CERT_NAME}`,
text: "download this certificate",
});
importCert.createEl("span", {
text: " to use it for validating your connection's security by adding it as a trusted certificate in the browser or tool you are using for interacting with this API.",
});

new Setting(containerEl).setName("Server Port").addText((cb) =>
cb
.onChange((value) => {
Expand All @@ -123,6 +185,19 @@ class SampleSettingTab extends PluginSettingTab {
})
.setValue(this.plugin.settings.port.toString())
);
new Setting(containerEl)
.setName("Reset Crypto on next Load")
.setDesc(
"Turning this toggle 'on' will cause your certificates and api key to be regenerated when this plugin is next loaded. You can force a reload by running the 'Reload app without saving' command from the command palette, closing and re-opening Obsidian, or turning this plugin off and on again from the community plugins panel in Obsidian's settings."
)
.addToggle((value) => {
value
.onChange((value) => {
this.plugin.settings.crypto.resetOnNextLoad = value;
this.plugin.saveSettings();
})
.setValue(this.plugin.settings.crypto.resetOnNextLoad);
});
new Setting(containerEl).setName("Certificate").addTextArea((cb) =>
cb
.onChange((value) => {
Expand Down
20 changes: 20 additions & 0 deletions src/requestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import bodyParser from "body-parser";

import { LocalRestApiSettings, PeriodicNoteInterface } from "./types";
import { findHeadingBoundary } from "./utils";
import { CERT_NAME } from "./constants";

export default class RequestHandler {
app: App;
Expand Down Expand Up @@ -385,13 +386,30 @@ export default class RequestHandler {
res.sendStatus(202);
}

async certificateGet(
req: express.Request,
res: express.Response
): Promise<void> {
res.set(
"Content-type",
`application/octet-stream; filename="${CERT_NAME}"`
);
res.statusCode = 200;
res.send(this.settings.crypto.cert);
}

async authenticationMiddleware(
req: express.Request,
res: express.Response,
next: express.NextFunction
): Promise<void> {
const authorizationHeader = req.get("Authorization");

if (req.path === `/${CERT_NAME}` || req.path === "/") {
next();
return;
}

if (authorizationHeader !== `Bearer ${this.settings.apiKey}`) {
res.sendStatus(401);
return;
Expand Down Expand Up @@ -422,6 +440,8 @@ export default class RequestHandler {
this.api.route("/commands/").get(this.commandGet.bind(this));
this.api.route("/commands/:commandId/").post(this.commandPost.bind(this));

this.api.get(`/${CERT_NAME}`, this.certificateGet.bind(this));

this.api.get("/", this.root);
}
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface LocalRestApiSettings {
cert: string;
privateKey: string;
publicKey: string;
resetOnNextLoad?: boolean;
};
port: number;
}
Expand Down

0 comments on commit b270735

Please sign in to comment.