diff --git a/README.md b/README.md
index 5558cfd..5f95f62 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@ Finds a private key through various user-(un)specified methods. Order of precede
3. `PRIVATE_KEY_PATH` environment variable or explicit `env.PRIVATE_KEY_PATH` option
4. Any file w/ `.pem` extension in current working dir
+Supports both PKCS1 (i.e `-----BEGIN RSA PRIVATE KEY-----`) and PKCS8 (i.e `-----BEGIN PRIVATE KEY-----`).
+
## Usage
diff --git a/src/index.ts b/src/index.ts
index 80aa419..6536103 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,5 @@
import { resolve } from "path";
import { existsSync, readdirSync, readFileSync } from "fs";
-
import { VERSION } from "./version";
type Options = {
@@ -13,8 +12,19 @@ type Options = {
cwd?: string;
};
-const begin = "-----BEGIN RSA PRIVATE KEY-----";
-const end = "-----END RSA PRIVATE KEY-----";
+const pkcs1Begin = "-----BEGIN RSA PRIVATE KEY-----";
+const pkcs1End = "-----END RSA PRIVATE KEY-----";
+
+const pkcs8Begin = "-----BEGIN PRIVATE KEY-----";
+const pkcs8End = "-----END PRIVATE KEY-----";
+
+function isPKCS1(privateKey: string): boolean {
+ return privateKey.includes(pkcs1Begin) && privateKey.includes(pkcs1End);
+}
+
+function isPKCS8(privateKey: string): boolean {
+ return privateKey.includes(pkcs8Begin) && privateKey.includes(pkcs8End);
+}
export function getPrivateKey(options: Options = {}): string | null {
const env = options.env || process.env;
@@ -32,15 +42,31 @@ export function getPrivateKey(options: Options = {}): string | null {
privateKey = Buffer.from(privateKey, "base64").toString();
}
- if (privateKey.includes(begin) && privateKey.includes(end)) {
- // newlines are escaped
- if (privateKey.indexOf("\\n") !== -1) {
- privateKey = privateKey.replace(/\\n/g, "\n");
+ // newlines are potentially escaped
+ if (privateKey.indexOf("\\n") !== -1) {
+ privateKey = privateKey.replace(/\\n/g, "\n");
+ }
+
+ if (isPKCS1(privateKey)) {
+ // newlines are missing
+ if (privateKey.indexOf("\n") === -1) {
+ privateKey = addNewlines({
+ privateKey,
+ begin: pkcs1Begin,
+ end: pkcs1End,
+ });
}
+ return privateKey;
+ }
+ if (isPKCS8(privateKey)) {
// newlines are missing
if (privateKey.indexOf("\n") === -1) {
- privateKey = addNewlines(privateKey);
+ privateKey = addNewlines({
+ privateKey,
+ begin: pkcs8Begin,
+ end: pkcs8End,
+ });
}
return privateKey;
}
@@ -76,7 +102,15 @@ function isBase64(str: string): boolean {
return Buffer.from(str, "base64").toString("base64") === str;
}
-function addNewlines(privateKey: string): string {
+function addNewlines({
+ privateKey,
+ begin,
+ end,
+}: {
+ privateKey: string;
+ begin: string;
+ end: string;
+}): string {
const middleLength = privateKey.length - begin.length - end.length - 2;
const middle = privateKey.substr(begin.length + 1, middleLength);
return `${begin}\n${middle.trim().replace(/\s+/g, "\n")}\n${end}`;
diff --git a/test/get-private-key.test.ts b/test/get-private-key.test.ts
index d882acb..997e0be 100644
--- a/test/get-private-key.test.ts
+++ b/test/get-private-key.test.ts
@@ -9,18 +9,30 @@ const existsSync = fs.existsSync as jest.Mock;
const readdirSync = fs.readdirSync as jest.Mock;
const readFileSync = fs.readFileSync as jest.Mock;
-const PRIVATE_KEY =
+const PRIVATE_KEY_PKCS1 =
"-----BEGIN RSA PRIVATE KEY-----\n7HjkPK\nKLm395\nAIBII\n-----END RSA PRIVATE KEY-----";
-const PRIVATE_KEY_NO_NEWLINES =
+const PRIVATE_KEY_PKCS1_NO_NEWLINES =
"-----BEGIN RSA PRIVATE KEY----- 7HjkPK KLm395 AIBII -----END RSA PRIVATE KEY-----";
-const PRIVATE_KEY_NO_NEWLINES_MULTIPLE_SPACES =
+const PRIVATE_KEY_PKCS1_NO_NEWLINES_MULTIPLE_SPACES =
"-----BEGIN RSA PRIVATE KEY----- 7HjkPK KLm395 AIBII -----END RSA PRIVATE KEY-----";
-const PRIVATE_KEY_ESCAPED_NEWLINES =
+const PRIVATE_KEY_PKCS1_ESCAPED_NEWLINES =
"-----BEGIN RSA PRIVATE KEY-----\\n7HjkPK\\nKLm395\\nAIBII\\n-----END RSA PRIVATE KEY-----";
+const PRIVATE_KEY_PKCS8 =
+ "-----BEGIN PRIVATE KEY-----\n7HjkPK\nKLm395\nAIBII\n-----END PRIVATE KEY-----";
+
+const PRIVATE_KEY_PKCS8_NO_NEWLINES =
+ "-----BEGIN PRIVATE KEY----- 7HjkPK KLm395 AIBII -----END PRIVATE KEY-----";
+
+const PRIVATE_KEY_PKCS8_NO_NEWLINES_MULTIPLE_SPACES =
+ "-----BEGIN PRIVATE KEY----- 7HjkPK KLm395 AIBII -----END PRIVATE KEY-----";
+
+const PRIVATE_KEY_PKCS8_ESCAPED_NEWLINES =
+ "-----BEGIN PRIVATE KEY-----\\n7HjkPK\\nKLm395\\nAIBII\\n-----END PRIVATE KEY-----";
+
describe("getPrivateKey", () => {
beforeEach(() => {
jest.resetAllMocks();
@@ -34,90 +46,267 @@ describe("getPrivateKey", () => {
});
describe("no environment variables", () => {
- it("returns null if called without arguments", () => {
- const result = getPrivateKey();
- expect(result).toBeNull();
- });
+ describe("PKCS1 format", () => {
+ const PRIVATE_KEY = PRIVATE_KEY_PKCS1;
- it("{ filepath } option", () => {
- const result = getPrivateKey({ filepath: "test.pem" });
- expect(readFileSync).toHaveBeenCalledTimes(1);
- expect(readFileSync).toHaveBeenCalledWith(
- resolve(process.cwd(), "test.pem"),
- "utf-8"
- );
- expect(result).toEqual("test.pem content");
- });
+ it("returns null if called without arguments", () => {
+ const result = getPrivateKey();
+ expect(result).toBeNull();
+ });
- it("{ env: { PRIVATE_KEY } }", () => {
- const result = getPrivateKey({
- env: {
- PRIVATE_KEY,
- },
+ it("{ filepath } option", () => {
+ const result = getPrivateKey({ filepath: "test.pem" });
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
});
- expect(result).toEqual(PRIVATE_KEY);
- });
- it("{ env: { PRIVATE_KEY_PATH } }", () => {
- const result = getPrivateKey({
- env: {
- PRIVATE_KEY_PATH: "test.pem",
- },
+ it("{ env: { PRIVATE_KEY } }", () => {
+ const result = getPrivateKey({
+ env: {
+ PRIVATE_KEY,
+ },
+ });
+ expect(result).toEqual(PRIVATE_KEY);
});
- expect(existsSync).toHaveBeenCalledTimes(1);
- expect(existsSync).toHaveBeenCalledWith(
- resolve(process.cwd(), "test.pem")
- );
- expect(readFileSync).toHaveBeenCalledTimes(1);
- expect(readFileSync).toHaveBeenCalledWith(
- resolve(process.cwd(), "test.pem"),
- "utf-8"
- );
- expect(result).toEqual("test.pem content");
- });
+ it("{ env: { PRIVATE_KEY_PATH } }", () => {
+ const result = getPrivateKey({
+ env: {
+ PRIVATE_KEY_PATH: "test.pem",
+ },
+ });
+ expect(existsSync).toHaveBeenCalledTimes(1);
+ expect(existsSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem")
+ );
- it("{ filepath, env }", () => {
- const result = getPrivateKey({
- filepath: "test.pem",
- env: { PRIVATE_KEY },
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
});
- expect(readFileSync).toHaveBeenCalledTimes(1);
- expect(readFileSync).toHaveBeenCalledWith(
- resolve(process.cwd(), "test.pem"),
- "utf-8"
- );
- expect(result).toEqual("test.pem content");
- });
- it("single test.pem file in current working directory", () => {
- readdirSync.mockReturnValue(["test.pem"]);
- const result = getPrivateKey();
- expect(readFileSync).toHaveBeenCalledTimes(1);
- expect(readFileSync).toHaveBeenCalledWith(
- resolve(process.cwd(), "test.pem"),
- "utf-8"
- );
- expect(result).toEqual("test.pem content");
- });
+ it("{ filepath, env }", () => {
+ const result = getPrivateKey({
+ filepath: "test.pem",
+ env: { PRIVATE_KEY },
+ });
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
+ });
- it("two *.pem files in current working directory", () => {
- readdirSync.mockReturnValue(["test1.pem", "test2.pem"]);
- expect(() => getPrivateKey()).toThrow(
- `[@probot/get-private-key] More than one file found: \"test1.pem, test2.pem\". Set { filepath } option or set one of the environment variables: PRIVATE_KEY, PRIVATE_KEY_PATH`
- );
+ it("single test.pem file in current working directory", () => {
+ readdirSync.mockReturnValue(["test.pem"]);
+ const result = getPrivateKey();
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
+ });
+
+ it("two *.pem files in current working directory", () => {
+ readdirSync.mockReturnValue(["test1.pem", "test2.pem"]);
+ expect(() => getPrivateKey()).toThrow(
+ `[@probot/get-private-key] More than one file found: \"test1.pem, test2.pem\". Set { filepath } option or set one of the environment variables: PRIVATE_KEY, PRIVATE_KEY_PATH`
+ );
+ });
+
+ it("{ cwd }", () => {
+ const result = getPrivateKey({
+ cwd: "/app/current",
+ });
+ expect(result).toEqual(null);
+ expect(readdirSync).toHaveBeenCalledWith("/app/current");
+ });
});
- it("{ cwd }", () => {
- const result = getPrivateKey({
- cwd: "/app/current",
+ describe("PKCS8 format", () => {
+ const PRIVATE_KEY = PRIVATE_KEY_PKCS8;
+
+ it("returns null if called without arguments", () => {
+ const result = getPrivateKey();
+ expect(result).toBeNull();
+ });
+
+ it("{ filepath } option", () => {
+ const result = getPrivateKey({ filepath: "test.pem" });
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
+ });
+
+ it("{ env: { PRIVATE_KEY } }", () => {
+ const result = getPrivateKey({
+ env: {
+ PRIVATE_KEY,
+ },
+ });
+ expect(result).toEqual(PRIVATE_KEY);
+ });
+
+ it("{ env: { PRIVATE_KEY_PATH } }", () => {
+ const result = getPrivateKey({
+ env: {
+ PRIVATE_KEY_PATH: "test.pem",
+ },
+ });
+ expect(existsSync).toHaveBeenCalledTimes(1);
+ expect(existsSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem")
+ );
+
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
+ });
+
+ it("{ filepath, env }", () => {
+ const result = getPrivateKey({
+ filepath: "test.pem",
+ env: { PRIVATE_KEY },
+ });
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
+ });
+
+ it("single test.pem file in current working directory", () => {
+ readdirSync.mockReturnValue(["test.pem"]);
+ const result = getPrivateKey();
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
+ });
+
+ it("two *.pem files in current working directory", () => {
+ readdirSync.mockReturnValue(["test1.pem", "test2.pem"]);
+ expect(() => getPrivateKey()).toThrow(
+ `[@probot/get-private-key] More than one file found: \"test1.pem, test2.pem\". Set { filepath } option or set one of the environment variables: PRIVATE_KEY, PRIVATE_KEY_PATH`
+ );
+ });
+
+ it("{ cwd }", () => {
+ const result = getPrivateKey({
+ cwd: "/app/current",
+ });
+ expect(result).toEqual(null);
+ expect(readdirSync).toHaveBeenCalledWith("/app/current");
});
- expect(result).toEqual(null);
- expect(readdirSync).toHaveBeenCalledWith("/app/current");
});
});
describe("with environment variables", () => {
+ describe("PKCS1 format", () => {
+ const PRIVATE_KEY = PRIVATE_KEY_PKCS1;
+ const PRIVATE_KEY_NO_NEWLINES = PRIVATE_KEY_PKCS1_NO_NEWLINES;
+ const PRIVATE_KEY_NO_NEWLINES_MULTIPLE_SPACES =
+ PRIVATE_KEY_PKCS1_NO_NEWLINES_MULTIPLE_SPACES;
+ const PRIVATE_KEY_ESCAPED_NEWLINES = PRIVATE_KEY_PKCS1_ESCAPED_NEWLINES;
+
+ it("PRIVATE_KEY", () => {
+ process.env.PRIVATE_KEY = PRIVATE_KEY;
+ const result = getPrivateKey();
+ expect(result).toEqual(PRIVATE_KEY);
+ });
+
+ it("PRIVATE_KEY is base64 encoded", () => {
+ process.env.PRIVATE_KEY = Buffer.from(PRIVATE_KEY, "utf-8").toString(
+ "base64"
+ );
+ const result = getPrivateKey();
+ expect(result).toEqual(PRIVATE_KEY);
+ });
+
+ it("PRIVATE_KEY contains no newlines", () => {
+ process.env.PRIVATE_KEY = PRIVATE_KEY_NO_NEWLINES;
+ const result = getPrivateKey();
+ expect(result).toEqual(PRIVATE_KEY);
+ });
+
+ it("PRIVATE_KEY contains consecutive spaces", () => {
+ process.env.PRIVATE_KEY = PRIVATE_KEY_NO_NEWLINES_MULTIPLE_SPACES;
+ const result = getPrivateKey();
+ expect(result).toEqual(PRIVATE_KEY);
+ });
+
+ it("PRIVATE_KEY contains escaped newlines", () => {
+ process.env.PRIVATE_KEY = PRIVATE_KEY_ESCAPED_NEWLINES;
+ const result = getPrivateKey();
+ expect(result).toEqual(PRIVATE_KEY);
+ });
+
+ it("PRIVATE_KEY invalid", () => {
+ process.env.PRIVATE_KEY = "invalid";
+ expect(() => getPrivateKey()).toThrow(
+ `[@probot/get-private-key] The contents of "env.PRIVATE_KEY" could not be validated. Please check to ensure you have copied the contents of the .pem file correctly.`
+ );
+ });
+
+ it("PRIVATE_KEY_PATH", () => {
+ process.env.PRIVATE_KEY_PATH = "test.pem";
+ const result = getPrivateKey();
+ expect(existsSync).toHaveBeenCalledTimes(1);
+ expect(existsSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem")
+ );
+
+ expect(readFileSync).toHaveBeenCalledTimes(1);
+ expect(readFileSync).toHaveBeenCalledWith(
+ resolve(process.cwd(), "test.pem"),
+ "utf-8"
+ );
+ expect(result).toEqual("test.pem content");
+ });
+
+ it("PRIVATE_KEY_PATH does not exist", () => {
+ process.env.PRIVATE_KEY_PATH = "test.pem";
+ existsSync.mockReturnValue(false);
+ expect(() => getPrivateKey()).toThrow(
+ `[@probot/get-private-key] Private key does not exists at path: "test.pem". Please check to ensure that "env.PRIVATE_KEY_PATH" is correct.`
+ );
+ });
+
+ it("both PRIVATE_KEY and PRIVATE_KEY_PATH", () => {
+ process.env.PRIVATE_KEY = PRIVATE_KEY;
+ process.env.PRIVATE_KEY_PATH = "test.pem";
+
+ const result = getPrivateKey();
+ expect(result).toEqual(PRIVATE_KEY);
+ });
+ });
+ });
+
+ describe("PKCS8 format", () => {
+ const PRIVATE_KEY = PRIVATE_KEY_PKCS8;
+ const PRIVATE_KEY_NO_NEWLINES = PRIVATE_KEY_PKCS8_NO_NEWLINES;
+ const PRIVATE_KEY_NO_NEWLINES_MULTIPLE_SPACES =
+ PRIVATE_KEY_PKCS8_NO_NEWLINES_MULTIPLE_SPACES;
+ const PRIVATE_KEY_ESCAPED_NEWLINES = PRIVATE_KEY_PKCS8_ESCAPED_NEWLINES;
+
it("PRIVATE_KEY", () => {
process.env.PRIVATE_KEY = PRIVATE_KEY;
const result = getPrivateKey();