Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add helpful error in case private key is in OpenSSH format #74

Merged
merged 3 commits into from
Jul 8, 2023
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
40 changes: 16 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,40 @@
[![@latest](https://img.shields.io/npm/vuniversal-github-app-jwt.svg)](https://www.npmjs.com/packageuniversal-github-app-jwt)
[![Build Status](https://github.com/gr2m/universal-github-app-jwt/workflows/Test/badge.svg)](https://github.com/gr2m/universal-github-app-jwt/actions?query=workflow%3ATest+branch%3Amaster)

⚠ The private keys provide by GitHub are in `PKCS#1` format, but the WebCrypto API only supports `PKCS#8`. You can see the difference in the first line, `PKCS#1` format starts with `-----BEGIN RSA PRIVATE KEY-----` while `PKCS#8` starts with `-----BEGIN PRIVATE KEY-----`. You can convert one format to the other using `oppenssl`:
⚠ The private keys provide by GitHub are in `PKCS#1` format, but the WebCrypto API only supports `PKCS#8`. And neither Node nor the WEbCrypto API supports private keys in the `OpenSSH` format. You can see the difference in the first line, `PKCS#1` format starts with `-----BEGIN RSA PRIVATE KEY-----` while `PKCS#8` starts with `-----BEGIN PRIVATE KEY-----`, and `OpenSSH` starts with `-----BEGIN OPENSSH PRIVATE KEY-----`.

You can convert `PKCS#1` to `PKCS#8` using `oppenssl`:

```
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private-key.pem -out private-key-pkcs8.key
```

It's also possible to convert the formats with JavaScript, e.g. using [node-rsa](https://github.com/rzcoder/node-rsa), but it turns a 4kb to a 200kb+ built. I'm looking for help to create a minimal `PKCS#1` to `PKCS#8` convert library that I can recommend people to use before passing the private key to `githubAppJwt`. Please create an issue if you'd like to help.

The way it works with `node-rsa` is this

```js
const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----`;

const key = new NodeRSA(PRIVATE_KEY);
const privateKeyPkcs8 = key.exportKey("pkcs8-private-pem");
You can convert `OpenSSH` to `PKCS#8` using `ssh-keygen`:

// privateKeyPkcs8 is now
// -----BEGIN PRIVATE KEY-----
// ...
// -----END PRIVATE KEY-----
```
cp private-key.pem private-key-pkcs8.key && ssh-keygen -m PKCS8 -N "" -f private-key-pkcs8.key
```

When using a node, a conversion is not necessary, the implementation is agnostic to either format.

However, if you got the error `Private Key is in PKCS#1 format, but only PKCS#8 is supported.` inside Node.js, it is possible that your bundler or your app framework incorrectly bundled the web version instead of the node version ([example](https://github.com/backstage/backstage/issues/9959)).
It's also possible to convert the formats with JavaScript, e.g. using [node-rsa](https://github.com/rzcoder/node-rsa), but it turns a 4kb to a 200kb+ built. I'm looking for help to create a minimal `PKCS#1` to `PKCS#8` convert library that I can recommend people to use before passing the private key to `githubAppJwt`. Please create an issue if you'd like to help. The same to convert `OpenSSH` to `PKCS#8`.

You can also convert `PKCS#1` to `PKCS#8` in Node.js using the built-in `crypto` module:
You can convert `PKCS#1` to `PKCS#8` in Node.js using the built-in `crypto` module:

```js
const crypto = require('crypto');
const crypto = require("crypto");
const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----`;

const privateKeyPkcs8 = crypto.createPrivateKey(PRIVATE_KEY).export({
type: 'pkcs8',
format: 'pem',
})
type: "pkcs8",
format: "pem",
});
```

When using a node, a conversion is not necessary, the implementation is agnostic to either `PKCS` format.

However, if you got the error `Private Key is in PKCS#1 format, but only PKCS#8 is supported.` inside Node.js, it is possible that your bundler or your app framework incorrectly bundled the web version instead of the node version ([example](https://github.com/backstage/backstage/issues/9959)).

## Usage

<table>
Expand Down
6 changes: 6 additions & 0 deletions lib/get-token-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import jsonwebtoken from "jsonwebtoken";
* @returns {Promise<string>}
*/
export async function getToken({ privateKey, payload }) {
if (privateKey.includes("-----BEGIN OPENSSH PRIVATE KEY-----")) {
throw new Error(
"[universal-github-app-jwt] Private Key is in OpenSSH format, but only PKCS is supported. See https://github.com/gr2m/universal-github-app-jwt#readme"
);
}

return jsonwebtoken.sign(payload, privateKey, {
algorithm: "RS256",
});
Expand Down
11 changes: 9 additions & 2 deletions lib/get-token.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ import {
*/
export async function getToken({ privateKey, payload }) {
// WebCrypto only supports PKCS#8, unfortunately
if (/BEGIN RSA PRIVATE KEY/.test(privateKey)) {
if (privateKey.includes("-----BEGIN RSA PRIVATE KEY-----")) {
throw new Error(
"[universal-github-app-jwt] Private Key is in PKCS#1 format, but only PKCS#8 is supported. See https://github.com/gr2m/universal-github-app-jwt#readme"
"[universal-github-app-jwt] Private Key is in PKCS#1 format, but only PKCS#8 is supported by WebCrypto. See https://github.com/gr2m/universal-github-app-jwt#readme"
);
}

// WebCrypto does not support OpenSSH, unfortunately
if (privateKey.includes("-----BEGIN OPENSSH PRIVATE KEY-----")) {
throw new Error(
"[universal-github-app-jwt] Private Key is in OpenSSH format, but only PKCS#8 is supported by WebCrypto. See https://github.com/gr2m/universal-github-app-jwt#readme"
);
}

Expand Down
48 changes: 48 additions & 0 deletions test/node.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,37 @@ SrOlxDpE+88rX/uDQ0ieaM8x5s/Qrp5I/HT3/j0npZMP74RqM3sWGS1RJjG6R1Zo
KhwBAlMM1g/Qrzwbiu0LYhjLkkWf1JbPmiHH//S77N39H9BFTMs7Dg3vb8e75Qbo
w23mINkUK1qlFoq3o69IHDLz
-----END PRIVATE KEY-----`;

// openssh version of the key above
// see https://github.com/gr2m/universal-github-app-jwt/issues/72
const PRIVATEY_KEY_OPENSSH = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEA1c7+9z5Pad7OejecsQ0bu3aozN3tihPmljnnudb9G3HECdnHlWu2
/a1gB9JW5TBQ+AVpum9Okx7KfqkfBKL9mcHgSL0yWMdjMfNOqNtrQqKlN4kEp6RD++7sGb
zbfZ9arwrlD/HSDAWGdGGJTSOBM6pHehyLmSC3DJoR/CTu0vTGTWXQrO64Z8tyXQPtVPb/
YXrcUhbBp8i72b9Xky0fD6PkEebOy0Ip58XVAn2UPNlNOSPSye+Qjtius0Md4Nie4+X8kw
VI2Qjk3dSm0sw/720KJkdVDmrayeljtKBx6AtNQsSXgzQbeMmiqFFkwrG1+zx6E7H7jqIQ
9B6bvWKXGwAAA7hJqTyJSak8iQAAAAdzc2gtcnNhAAABAQDVzv73Pk9p3s56N5yxDRu7dq
jM3e2KE+aWOee51v0bccQJ2ceVa7b9rWAH0lblMFD4BWm6b06THsp+qR8Eov2ZweBIvTJY
x2Mx806o22tCoqU3iQSnpEP77uwZvNt9n1qvCuUP8dIMBYZ0YYlNI4Ezqkd6HIuZILcMmh
H8JO7S9MZNZdCs7rhny3JdA+1U9v9hetxSFsGnyLvZv1eTLR8Po+QR5s7LQinnxdUCfZQ8
2U05I9LJ75CO2K6zQx3g2J7j5fyTBUjZCOTd1KbSzD/vbQomR1UOatrJ6WO0oHHoC01CxJ
eDNBt4yaKoUWTCsbX7PHoTsfuOohD0Hpu9YpcbAAAAAwEAAQAAAQA/JAQTy+jz4QKlASta
9f4MnHw5FAkET+A22R7fuOfCZRR5vEC+lbLSs5K6fJmA0YX32NFiZ2CS/fLa+OLaIbA26h
RsJsNpiGh45duC7ll8sj+MaQBg6ZfSHLrI1wcS0C6dpuACl21qc29Di1Ja33NWaKD3JNrs
+8ZTCGTrS5Tj4y89g7no1XtuCXMMB860FijnwNdUf/XudA1bHoZ0h/8OOkO+t8uCNBNwSK
FDLKJRCs4dsnQAMs5IGmxkAVLmpmaLTX7yaA13gotSw/MjQlsk5WKSWSQw16KAVORALAr4
TdbEkaGl1q5TEgwe6/xPmkMirmuCjjar5qCbgttb0inhAAAAgQDOYPZ2JGmhibqKjjLFm0
qzpcQ6RPvPK1/7g0NInmjPMebP0K6eSPx09/49J6WTD++EajN7FhktUSYxukdWaCocAQJT
DNYP0K88G4rtC2IYy5JFn9SWz5ohx//0u+zd/R/QRUzLOw4N72/Hu+UG6MNt5iDZFCtapR
aKt6OvSBwy8wAAAIEA7yW6wBkVoNhgXnk8XSZv3W+Q0xtdVpidJeNGBWnczlZrummt4xw3
xs6zV+rGUDy59yDkKwBKjMMa42Mni7T9Fx8+EKUuhVK3PVQyajoyQqFwT1GORJNzc/eYQ6
VYOCSC8OyZmsBM2p+0D4FF2/abwSPMmy0NgyFLCUFVc3OECpkAAACBAOTgJiN8LebPnJYN
fKkuwSkdg7jhVq8zVXB2F4Y8fLzklldN9zst8r3VAQIbvQNS1KqcMjGvrpyIMZr5lDEBHc
h0DHIAbFbNdHUf4NsRozC3MI+pJ/sa9yVSGZ20U+dGLj4X/p7NUnHYT+rqaPSNP7EbCLjf
ehm+uSScfeIEOJPTAAAAAAEC
-----END OPENSSH PRIVATE KEY-----`;

// see https://runkit.com/gr2m/reproducable-jwt
const BEARER =
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOi0zMCwiZXhwIjo1NzAsImlzcyI6MX0.q3foRa78U3WegM5PrWLEh5N0bH1SD62OqW66ZYzArp95JBNiCbo8KAlGtiRENCIfBZT9ibDUWy82cI4g3F09mdTq3bD1xLavIfmTksIQCz5EymTWR5v6gL14LSmQdWY9lSqkgUG0XCFljWUglEP39H4yeHbFgdjvAYg3ifDS12z9oQz2ACdSpvxPiTuCC804HkPVw8Qoy0OSXvCkFU70l7VXCVUxnuhHnk8-oCGcKUspmeP6UdDnXk-Aus-eGwDfJbU2WritxxaXw6B4a3flTPojkYLSkPBr6Pi0H2-mBsW_Nvs0aLPVLKobQd4gqTkosX3967DoAG8luUMhrnxe8Q";
Expand Down Expand Up @@ -96,6 +127,23 @@ test("README example for app auth with private key in PKCS#8 format", async (t)
});
});

test("Throw error if key is OpenSSH", async (t) => {
MockDate.set(0);

try {
await githubAppJwt({
id: APP_ID,
privateKey: PRIVATEY_KEY_OPENSSH,
});
t.fail("should throw");
} catch (error) {
t.is(
error.message,
"[universal-github-app-jwt] Private Key is in OpenSSH format, but only PKCS is supported. See https://github.com/gr2m/universal-github-app-jwt#readme"
);
}
});

test("Include the time difference in the expiration and issued_at field", async (t) => {
MockDate.set(0);

Expand Down