diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 00000000..d4dc1e2d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: There's a bug in @panva/jose I want to report +title: 'bug: ' +labels: bug +--- + +**Describe the bug** + + + +**To Reproduce** + +Steps to reproduce the behaviour: + +1. +2. +3. + +**Expected behaviour** +A clear and concise description of what you expected to happen. + +**Environment:** + - @panva/jose version: [e.g. v1.0.0] + - node version: [e.g. v11.9.0] + +**Additional context** +Add any other context about the problem here. + + - [ ] the bug is happening on latest @panva/jose too. + - [ ] i have tried DEBUG (see readme.md) and can see the issue is with the provider and not my code. + - [ ] i have searched the issues tracker on github for similar issues and couldn't find anything related. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 00000000..536323b1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,21 @@ +--- +name: Feature proposal +about: I have an idea for a new @panva/jose feature +title: 'proposal: ' +labels: enhancement +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context about the problem here. + + - [ ] i have searched the configuration section for this feature and couldn't find it + - [ ] i have searched the issues tracker on github for similar requests and couldn't find anything related. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000..3504b5e1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,6 @@ +--- +name: Question +about: I have a question about using @panva/jose +title: 'question: ' +labels: question +--- diff --git a/.github/ISSUE_TEMPLATE/security-vulnerability.md b/.github/ISSUE_TEMPLATE/security-vulnerability.md new file mode 100644 index 00000000..4744b7c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/security-vulnerability.md @@ -0,0 +1,20 @@ +--- +name: Security Vulnerability +about: I want to disclose a vulnerability +--- + + + + diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..dfda0737 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +access = public diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..f50fb737 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: stable +script: npm run coverage +after_script: npx codecov +jobs: + include: + - stage: Lint + script: npm run lint + after_script: skip diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..9bf6722e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,62 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers +pledge to making participation in our project and our community a harassment-free experience for +everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level +of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behaviour that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behaviour by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit + permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behaviour and are +expected to take appropriate and fair corrective action in response to any instances of unacceptable +behaviour. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, +code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or +to ban temporarily or permanently any contributor for other behaviours that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is +representing the project or its community. Examples of representing a project or community include +using an official project e-mail address, posting via an official social media account, or acting as +an appointed representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported by contacting +the project team at panva.ip@gmail.com. The project team will review and investigate all complaints, +and will respond in a way that it deems appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at +[http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6b21ff9e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing to @panva/jose + +Please note we have a [code of conduct][coc], please follow it in all your interactions with the +project. + +When contributing to this project, please first discuss the change you wish to make via issue, +email, or any other method with the owners of this project before proposing a change via a Pull +Request. Use (and follow!) the appropriate [Issue Template][new-issue] to do so. A contribution that +implements something non-standard will most likely be dismissed. + +## Rules of the discussions + +Remember to be very clear and transparent when discussing any issue in the discussions boards. We +ask that you keep the language to English and keep on track with the issue at hand. Lastly, please +be respectful of our fellow contributors and keep an exemplary level of professionalism at all +times. + +## Pull Request Checklist + +- No additional runtime dependencies unless previously agreed upon +- `npm run lint` passes +- File names must be snake_case.js +- Add tests covering 100% of the library code you are adding or modifying +- Unless previously agreed upon (i.e. fixing a bug) all contributions must be backwards compatible +- Update the documentation +- Do not commit unnecessary whitespace + +[coc]: https://github.com/panva/jose/blob/master/CODE_OF_CONDUCT.md +[new-issue]: https://github.com/panva/jose/issues/new/choose +[standard-version]: https://github.com/conventional-changelog/standard-version diff --git a/README.md b/README.md index 5f1ec37c..a84d8edf 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,7 @@ "JSON Web Almost Everything" - JWA, JWS, JWE, JWK, JWKS for Node.js with minimal dependencies -See the [`@panva/jwt`](https://github.com/panva/jwt) package for JWT. - -**Table of Contents** - -- [Implemented specs & features](#implemented-specs--features) -- [Usage](#usage) +See the [`@panva/jwt`](https://github.com/panva/jwt) (coming soon™) for JWT convenience abstraction. ## Implemented specs & features @@ -30,7 +25,8 @@ implementation is correct. Legend: - **✓** Implemented -- **✕** Missing node crypto support / won't implement / not planned / PR welcome +- **✕** Missing node crypto support / won't implement +- **◯** not planned (yet?) / PR / Use-Case first welcome | JWK Key Types | Supported || | -- | -- | -- | @@ -68,327 +64,199 @@ Legend: --- -Remaining tasks: -- ✓ JWKS abstraction -- ✓ `crit` JWE/JWS Header parameter handling -- ✓ `b64` JWS crit support -- ✓ JWE `zip` handling -- ✓ JWE/JWS decrypt/verify algorithm whitelisting -- ◯ JWE/JWS reference (true/false for `kid`, name of the field for other fields) -- ◯ whitelist additional JWK reference fields (`kid`, `jku`, `x5c`, `x5t`, `x5t#S256`, `x5u`) -- ◯ README and documentation -- ◯ .d.ts types -- ◯ .github files (templates, CoC, Contributing) -- ◯ `@panva/jwt` - - `compact` only with convenience methods and options - - `@panva/jose` as a dependency +Pending Node.js Support 🤞: +- [RFC8037][spec-cfrg] (EdDSA, OKP kty, etc) + - `crypto.getCurves().includes('Curve25519')` // => 😢 + - `crypto.getCurves().includes('Curve448')` // => 😢 + - `openssl ecparam -list_curves` // => 😢 Won't implement: -- ✕ JWS embedded key / referenced verification - won't implement, who needs it can decode the header - and pass the (`x5c`, `jwk`) to `JWK.importKey` and validate with that key, similarly the - application can handle fetching the referenced `x5u` or `jku` -- ✕ JWS detached content - won't implement, who needs it can remove/attach the payload after/before - the respective operation -- ✕ "none" alg support, no crypto, no use, don't bother +- ✕ JWS embedded key / referenced verification + - one can decode the header and pass the (`x5c`, `jwk`) to `JWK.importKey` and validate with that + key, similarly the application can handle fetching and then instantiating the referenced `x5u` + or `jku` in its own code. This way you opt-in to these behaviours and for `x5c` specifically + the recipient is responsible for validating the certificate chain is trusted +- ✕ JWS detached content + - one can remove/attach the payload after/before the respective operation +- ✕ "none" alg support + - no crypto, no use + +Not Planned / PR | Use-Case | Discussion Welcome: +- ◯ automatically adding `kid` reference to JWS / JWE Headers +- ◯ `x5c`, `x5t`, `x5t#S256`, `x5u` etc `JWK.Key` fields -Missing a feature? - If it wasn't already discussed before, [ask for it][suggest-feature]. -Found a bug? - [report it][bug]. +
-

Support

+Have a question about using @panva/jose? - [ask][ask]. +Found a bug? - [report it][bug]. +Missing a feature? - If it wasn't already discussed before, [ask for it][suggest-feature]. +Found a vulnerability? - Reach out to us via email first, see [security vulnerability disclosure][security-vulnerability]. + +## Support [][support-patreon] If you or your business use @panva/jose, please consider becoming a [Patron][support-patreon] so I can continue maintaining it and adding new features carefree. You may also donate one-time via [PayPal][support-paypal]. [][support-paypal] +## Documentation + +- [@panva/jose API Documentation][documentation] + - [JWK (JSON Web Key)][documentation-jwk] + - [JWKS (JSON Web Key Set)][documentation-jwks] + - [JWS (JSON Web Signature)][documentation-jws] + - [JWE (JSON Web Encryption)][documentation-jwe] + ## Usage -The minimal Node.js version supported is v11.8.0 +The minimal Node.js version required is v11.8.0 +Installing @panva/jose + +```sh +$ npm install @panva/jose +``` + +Usage ```js +const jose = require('@panva/jose') const { JWE, // JSON Web Encryption (JWE) JWK, // JSON Web Key (JWK) JWKS, // JSON Web Key Set (JWKS) JWS, // JSON Web Signature (JWS) errors // errors utilized by @panva/jose -} = require('@panva/jose') - +} = jose ``` -## JWK (JSON Web Key) +#### Keys and KeyStores -All @panva/jose operations require `JWK.Key` or `JWKS.KeyStore` as arguments. Here's -how to get a `JWK.Key`. - -#### Class: JWK `` | `` | `` - -``, `` and `` represent a key usable for JWS and JWE operations. The -`JWK.importKey()` method is used to retrieve a key representation of an existing key or secret. -`JWK.generate()` method is used to generate a new random key. - -#### JWK `#importKey(key[, options])` asymmetric key import - -Imports an asymmetric private or public key. Supports importing JWK formatted keys (private, public, -secrets), `pem` and `der` formatted private and public keys, `pem` formatted X.509 certificates. -Private keys may also be passphrase protected. - -
- API (Click to expand) - -- `key`: `` | `` | `` | `` - - `key`: `` | `` - - `format`: `` Must be 'pem' or 'der'. Default: 'pem'. - - `type`: `` Must be 'pkcs1', 'pkcs8' or 'sec1'. This option is required only if the format is 'der' and ignored if it is 'pem'. - - `passphrase`: `` | `` The passphrase to use for decryption. -- `options`: `` - - `alg`: `` option identifies the algorithm intended for use with the key. - - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. - - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] -- Returns: `` | `` - -See the underlying Node.js API for details on importing private and public keys in the different formats - -- [crypto.createPrivateKey(key)](https://nodejs.org/api/crypto.html#crypto_crypto_createprivatekey_key) -- [crypto.createPublicKey(key)](https://nodejs.org/api/crypto.html#crypto_crypto_createpublickey_key) -- [crypto.createSecretKey(key)](https://nodejs.org/api/crypto.html#crypto_crypto_createsecretkey_key) - - - -
- Example (Click to expand) +Prepare your Keys and KeyStores. See the [documentation][documentation-jwk] for more. ```js -const { readFileSync } = require('fs') -const { JWK: { importKey } } = require('@panva/jose') +const key = jose.JWK.importKey(fs.readFileSync('path/to/key/file')) -const key = importKey(readFileSync('path/to/key/file')) -// ECKey { -// kty: 'EC', -// public: true, -// kid: [Getter], -// crv: [Getter], -// x: [Getter], -// y: [Getter] } +const jwk = { kty: 'EC', + kid: 'dl4M_fcI7XoFCsQ22PYrQBkuxZ2pDcbDimcdFmmXM98', + crv: 'P-256', + x: 'v37avifcL-xgh8cy6IFzcINqqmFLc2JF20XUpn4Y2uQ', + y: 'QTwy27XgP7ZMOdGOSopAHB-FU1JMQn3J9GEWGtUXreQ' } +const anotherKey = jose.JWK.importKey(jwk) + +const keystore = new jose.JWK.KeyStore(key, key2) ``` -
+#### Signing -#### JWK `#importKey(secret[, options])` secret key import - -Imports a symmetric key. - -
- API (Click to expand) - -- `secret`: `` | `` | `` -- `options`: `` - - `alg`: `` option identifies the algorithm intended for use with the key. - - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. - - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] -- Returns: `` - - - -
- Example (Click to expand) +Sign with a private or symmetric key using compact serialization. See the [documentation][documentation-jws] for more. ```js -const { JWK: { importKey } } = require('@panva/jose') - -const key = importKey(Buffer.from('8yHym6h5CG5FylbzrCn8fhxEbp3kOaTsgLaawaaJ')) -// OctKey { -// kty: 'oct', -// kid: [Getter], -// k: [Getter] } +jose.JWS.sign( + { sub: 'johndoe' }, + privateKey +) ``` -
+#### Verifying -#### JWK `#importKey(jwk)` JWK-formatted key import - -Imports a JWK formatted key. This supports JWK formatted EC, RSA and oct keys. Asymmetrical keys -may be both private and public. - -
- API (Click to expand) - -- `jwk`: `` - - `kty`: `` Key type. Must be 'RSA', 'EC' or 'oct'. - - `alg`: `` option identifies the algorithm intended for use with the key. - - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. - - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] - - `e`, `n` properties as `` for RSA public keys - - `e`, `n`, `d`, `p`, `q`, `dp`, `dq`, `qi` properties as `` for RSA private keys - - `crv`, `x`, `y` properties as `` for EC public keys - - `crv`, `x`, `y`, `d` properties as `` for EC private keys - - `k` properties as `` for secret oct keys -- Returns: `` | `` | `` - - - -
-Example (Click to expand) +Verify with a public or symmetric key. See the [documentation][documentation-jws] for more. ```js -const { JWK: { importKey } } = require('@panva/jose') -const jwk = { - kty: 'RSA', - kid: 'r1LkbBo3925Rb2ZFFrKyU3MVex9T2817Kx0vbi6i_Kc', - use: 'sig', - e: 'AQAB', - n: 'xwQ72P9z9OYshiQ-ntDYaPnnfwG6u9JAdLMZ5o0dmjlcyrvwQRdoFIKPnO65Q8mh6F_LDSxjxa2Yzo_wdjhbPZLjfUJXgCzm54cClXzT5twzo7lzoAfaJlkTsoZc2HFWqmcri0BuzmTFLZx2Q7wYBm0pXHmQKF0V-C1O6NWfd4mfBhbM-I1tHYSpAMgarSm22WDMDx-WWI7TEzy2QhaBVaENW9BKaKkJklocAZCxk18WhR0fckIGiWiSM5FcU1PY2jfGsTmX505Ub7P5Dz75Ygqrutd5tFrcqyPAtPTFDk8X1InxkkUwpP3nFU5o50DGhwQolGYKPGtQ-ZtmbOfcWQ' -} - -const key = importKey(jwk) -// RSAKey { -// kty: 'RSA', -// public: true, -// use: 'sig', -// kid: 'r1LkbBo3925Rb2ZFFrKyU3MVex9T2817Kx0vbi6i_Kc', -// e: [Getter], -// n: [Getter] } +jose.JWS.verify( + 'eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJqb2huZG9lIn0.T_SYLQV3A5_kFDDVNuoadoURSEtuSOR-dG2CMmrP-ULK9xbIf2vYeiHOkvTrnqGlWEGBGxYtsP1VkXmNsi1uOw', + publicKey +) ``` -
+#### Encrypting -#### JWK `#generate(kty[, crvOrSize[, options[, private]]])` generating new keys +Encrypt using the recipient's public key or a shared symmetrical secret. See the [documentation][documentation-jwe] for more. -Securely generates a new RSA, EC or oct key. +```js +jose.JWE.encrypt( + 'eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJqb2huZG9lIn0.T_SYLQV3A5_kFDDVNuoadoURSEtuSOR-dG2CMmrP-ULK9xbIf2vYeiHOkvTrnqGlWEGBGxYtsP1VkXmNsi1uOw', + publicKey +) +``` -
- API (Click to expand) +#### Verifying -- `kty`: `` Key type. Must be 'RSA', 'EC' or 'oct'. -- `crvOrSize`: `` | `` key's bit size or in case of EC keys the curve -- `options`: `` - - `alg`: `` option identifies the algorithm intended for use with the key. - - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. - - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] -- `private`: `` **Default** 'true'. Is the resulting key private or public (when asymmetrical) -- Returns: `Promise` | `Promise` | `Promise` +Decrypt using the private key or a shared symmetrical secret. See the [documentation][documentation-jwe] for more. - +```js +jose.JWE.decrypt( + 'eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiRUNESC1FUyIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IkVsUGhsN1ljTVZsWkhHM0daSkRoOVJhemNYYlN2VFNheUF6aTBINFFtRUEiLCJ5IjoiM0hDREJTRy12emd6cGtLWmJqMU05UzVuUEJrTDBBdFM4U29ORUxMWE1SayJ9fQ..FhmidRo0twvFA7jcfKFNJw.o112vgiG_qUL1JR5WHpsErcxxgaK_FAa7vCWJ--WulndLpdwdRXHd9k3aL_k8K67xoAThrt10d7dSY2TlPpHdYkw979u0V-C4TNrpzNkv5jpBjU6hHyKpoGZfEsiTD1ivHaFy3ZLCTS69kN_eVKsZGLVf_dkq6Sz6bWE4-ln_fuwukPyMvjTyaTreLjPLBZW.ocKwptCm4Zn437L5hWFnHg', + privateKey +) +``` -#### JWK `#generateSync(kty[, crvOrSize[, options[, private]]])` +## FAQ -Synchronous version of JWK `#generate` +#### Semver? -
- API (Click to expand) +**Yes.** Everything that's either exported in the TypeScript definitions file or [documented][documentation] +is subject to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). The rest is to be +considered private API and is subject to change between any versions. -- `kty`: `` Key type. Must be 'RSA', 'EC' or 'oct'. -- `crvOrSize`: `` | `` key's bit size or in case of EC keys the curve. **Default** 2048 for RSA, 'P-256' for EC and 256 for oct. -- `options`: `` - - `alg`: `` option identifies the algorithm intended for use with the key. - - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. - - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] -- `private`: `` **Default** 'true'. Is the resulting key private or public (when asymmetrical) -- Returns: `` | `` | `` +#### How do I use it outside of Node.js - +It is **only built for Node.js** environment - it builds on top of the `crypto` module and requires +the KeyObject API that was added in Node.js v11.6.0. -## JWKS (JSON Web Key Set) +#### How is it different from [`node-jose`][node-jose] -- [Class: KeyStore](#class-keystore) -- [new KeyStore([keys])](#new-keystorekeys) -- [keystore.all([parameters])](#keystoreallparameters) -- [keystore.get([parameters])](#keystoregetparameters) -- [keystore.add(key)](#keystoreaddkey) -- [keystore.remove(key)](#keystoreremovekey) -- [keystore.generate(...)](#keystoregenerate) -- [keystore.generateSync(...)](#keystoregeneratesync) +`node-jose` is built to work in any javascript runtime, to be able to do that it packs a lot of +backfill and javascript implementation code in the form of +[`node-forge`](https://github.com/digitalbazaar/forge), this significantly increases the footprint +of the module with dependencies that either aren't ever used or have native implementation available +in Node.js already, those are often times faster and more reliable. -#### Class: `KeyStore` +#### How is it different from [`node-jws`](https://github.com/brianloveswords/node-jws) or [`node-jwa`](https://github.com/brianloveswords/node-jwa)? -`KeyStore` is an abstraction representing a set of JWKs +- it is providing Key and KeyStore abstractions +- there is JSON Web Encryption support +- there is no asynchronous API since node crypto is ultimately entirely synchronous +- it supports all JWS / JWE Serialization Syntaxes -#### `new KeyStore([keys])` +#### What is the ultimate goal? -Creates a new KeyStore, either empty or populated. +- **No dependencies**, the moment JWK formatted keys are supported by node's `crypto` the direct +dependency count will go down from 1 to 0. 🚀 +- Just the API one needs, having used other jose modules for 3+ years I only include what's useful -
- API (Click to expand) +#### Why? Just, why? -- `keys`: `` Array of key keys instantiated by `JWK.importKey` -- Returns: `` +I was / (still am) using [`node-jose`][node-jose] for [`openid-client`](https://github.com/panva/node-openid-client) +and [`oidc-provider`](https://github.com/panva/node-oidc-provider) and came to realize its shortcomings +in terms of performance and API (not having well defined errors). When Node.js v12 lands in April +2019 I will be releasing new major versions of both those libraries using @panva/jose. -
- -#### `keystore.all([parameters])` - -Retrieves an array of keys matching the provider parameters, returns all if none are provided. The -returned array is sorted by relevance based on the parameters. Keys with the exact algorithm or use -specified by the parameters are first. - -
- API (Click to expand) - -- `parameters`: `` - - `kty`: `` Key Type to filter for. - - `alg`: `` Key supported algorithm to filter for. - - `use`: `` Key use to filter for. - - `kid`: `` Key ID to filter for. -- Returns: `` Array of key instances or an empty array when none are matching the parameters. - - - -#### `keystore.get([parameters])` - -Retrieves a single key matching the provider parameters. The most relevant Key based on the -parameters is returned. - -
- API (Click to expand) - -- `parameters`: `` - - `kty`: `` Key Type to filter for. - - `alg`: `` Key supported algorithm to filter for. - - `use`: `` Key use to filter for. - - `kid`: `` Key ID to filter for. -- Returns: `` | `` | `` | `` - - - -#### `keystore.add(key)` - -Adds a key instance to the store unless it is already included. - -
- API (Click to expand) - -- `key`: `` | `` | `` - -
- -#### `keystore.remove(key)` - -Ensures a key is removed from a store. - -
- API (Click to expand) - -- `key`: `` | `` | `` - -
- -#### `keystore.generate(...)` - -Asynchronously generates new random key and automatically adds it to the store. See `JWK.generate` for the API. - -#### `keystore.generateSync(...)` - -Synchronous version of `keystore.generate`. ++ this was an amazing opportunity to learn JOSE as a whole +#### Where's the performance coming from? +No endless stream of yielded promises, uses KeyObject instances for crypto operations, once a +KeyObject is instantiated the keys do not need to be "prepped" and validated any more in neither +the Node runtime nor the underlying OpenSSL implementation. In some cases this yields 2x throughput +for the actual crypto operation. +[node-jose]: https://github.com/cisco/node-jose +[documentation]: https://github.com/panva/jose/blob/master/docs/README.md +[documentation-jws]: https://github.com/panva/jose/blob/master/docs/README.md#jws-json-web-signature +[documentation-jwe]: https://github.com/panva/jose/blob/master/docs/README.md#jwe-json-web-encryption +[documentation-jwk]: https://github.com/panva/jose/blob/master/docs/README.md#jwk-json-web-key +[documentation-jwks]: https://github.com/panva/jose/blob/master/docs/README.md#jwks-json-web-key-set +[documentation]: https://github.com/panva/jose/blob/master/docs/README.md +[documentation]: https://github.com/panva/jose/blob/master/docs/README.md [travis-image]: https://api.travis-ci.com/panva/jose.svg?branch=master [travis-url]: https://travis-ci.com/panva/jose [codecov-image]: https://img.shields.io/codecov/c/github/panva/jose/master.svg [codecov-url]: https://codecov.io/gh/panva/jose -[suggest-feature]: https://github.com/panva/jose/issues/new?template=feature-request.md -[bug]: https://github.com/panva/jose/issues/new?template=bug-report.md +[suggest-feature]: https://github.com/panva/jose/issues/new?labels=enhancement&template=feature-request.md&title=proposal%3A+ +[bug]: https://github.com/panva/jose/issues/new?labels=bug&template=bug-report.md&title=bug%3A+ +[ask]: https://github.com/panva/jose/issues/new?labels=question&template=question.md&title=question%3A+ +[security-vulnerability]: https://github.com/panva/jose/issues/new?template=security-vulnerability.md [support-patreon]: https://www.patreon.com/panva [support-paypal]: https://www.paypal.me/panva [spec-jwa]: https://tools.ietf.org/html/rfc7518 @@ -396,5 +264,6 @@ Synchronous version of `keystore.generate`. [spec-jwe]: https://tools.ietf.org/html/rfc7516 [spec-b64]: https://tools.ietf.org/html/rfc7797 [spec-jwk]: https://tools.ietf.org/html/rfc7517 +[spec-cfrg]: https://tools.ietf.org/html/rfc8037 [spec-thumbprint]: https://tools.ietf.org/html/rfc7638 [spec-cookbook]: https://tools.ietf.org/html/rfc7520 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..b7c9ac3a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,922 @@ +# @panva/jose API Documentation + +**Table of Contents** + +- [JWK (JSON Web Key)](#jwk-json-web-key) +- [JWKS (JSON Web Key Set)](#jwks-json-web-key-set) +- [JWS (JSON Web Signature)](#jws-json-web-signature) +- [JWE (JSON Web Encryption)](#jwe-json-web-encryption) + +## Support + +[][support-patreon] +If you or your business use @panva/jose, please consider becoming a [Patron][support-patreon] so I can continue maintaining it and adding new features carefree. You may also donate one-time via [PayPal][support-paypal]. +[][support-paypal] + +
+ +--- + +## JWK (JSON Web Key) + + +- [Class: <JWK.Key> and <JWK.RSAKey> | <JWK.ECKey> | <JWK.OctKey>](#class-jwkkey-and-jwkrsakey--jwkeckey--jwkoctkey) + - [key.kty](#keykty) + - [key.alg](#keyalg) + - [key.use](#keyuse) + - [key.kid](#keykid) + - [key.public](#keypublic) + - [key.private](#keyprivate) + - [key.algorithms([operation])](#keyalgorithmsoperation) + - [key.toJWK([private])](#keytojwkprivate) +- JWK.importKey + - [JWK.importKey(key[, options]) asymmetric key import](#jwkimportkeykey-options-asymmetric-key-import) + - [JWK.importKey(secret[, options]) secret key import](#jwkimportkeysecret-options-secret-key-import) + - [JWK.importKey(jwk) JWK-formatted key import](#jwkimportkeyjwk-jwk-formatted-key-import) +- [JWK.generate(kty[, crvOrSize[, options[, private]]]) generating new keys](#jwkgeneratekty-crvorsize-options-private-generating-new-keys) +- [JWK.generateSync(kty[, crvOrSize[, options[, private]]])](#jwkgeneratesynckty-crvorsize-options-private) +- [JWK.isKey(object)](#jwkiskeyobject) + + +All @panva/jose operations require `` or `` as arguments. Here's +how to get a `` instances generated or instantiated from existing key material. + + +```js +const { JWK } = require('@panva/jose') +// { importKey: [Function: importKey], +// generate: [AsyncFunction: generate], +// generateSync: [Function: generateSync] } +``` + +--- + +#### Class: `` and `` | `` | `` + +``, `` and `` represent a key usable for JWS and JWE operations. The +`JWK.importKey()` method is used to retrieve a key representation of an existing key or secret. +`JWK.generate()` method is used to generate a new random key. + +``, `` and `` inherit methods from `` and in addition +to the properties documented below have the respective key component properties exported as `` +in their format defined by the specifications. + +- `e, n` for Public RSA Keys +- `e, n, d, p, q, dp, dq, qi` for Private RSA Keys +- `crv, x, y` for Public EC Keys +- `crv, x, y, n` for Private EC Keys +- `k` for Symmetric keys + +--- + +#### `key.kty` + +Returns the key's JWK Key Type Parameter. 'EC', 'RSA' or 'oct' for the respective supported key types + +- `` + +--- + +#### `key.alg` + +Returns the key's JWK Algorithm Parameter if set, undefined otherwise. If set the key is only usable +for that one algorithm and will fail when used with others. + +- `` + +--- + +#### `key.use` + +Returns the key's JWK Key Use Parameter if set, undefined otherwise. Only 'sig' and 'enc' values +are supported. If set the key can only be used for either signing / verification or encryption +related operations (key management or encryption) + +- `` + +--- + +#### `key.kid` + +Returns the key's JWK Key ID Parameter if set, if not set it will be calculated using the method +defined in [RFC7638][spec-thumbprint] + +- `` + +--- + +#### `key.public` + +Returns true/false if the key is asymmetric and public. Returns false for symmetric keys. + +- `` + +--- + +#### `key.private` + +Returns true/false if the key is asymmetric and private. Returns false for symmetric keys. + +- `` + +--- + +#### `key.algorithms([operation])` + +Returns a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) +of algorithms the key may perform. + +- `operation`: `` Must be one of 'encrypt', 'decrypt', 'sign', 'verify', 'wrapKey', 'unwrapKey' +- Returns: `Set` + +
+ Example (Click to expand) + +```js +const { JWK: { generateSync } } = require('@panva/jose') + +const privateKey = generateSync('RSA') +privateKey.algorithms() +// Set { +// 'PS256', +// 'RS256', +// 'PS384', +// 'RS384', +// 'PS512', +// 'RS512', +// 'RSA-OAEP', +// 'RSA1_5' } +privateKey.algorithms('wrapKey') +// Set { +// 'RSA-OAEP', +// 'RSA1_5' } + +const publicKey = generateSync('RSA', 2048, { use: 'enc' }, false) +publicKey.algorithms('sign') +// Set {} +publicKey.algorithms('unwrapKey') +// Set {} +publicKey.algorithms('wrapKey') +// Set { +// 'RSA-OAEP', +// 'RSA1_5' } +``` +
+ +--- + +#### `key.toJWK([private])` + +Exports the key to a JSON Web Key formatted object. + +- `private`: `` When true exports keys with its private components. **Default:** 'false' +- Returns: `` + +
+ Example (Click to expand) + +```js +const { JWK: { generateSync } } = require('@panva/jose') + +const key = generateSync('RSA', 2048, { use: 'sig', alg: 'PS256' }) +key.toJWK() +// { kty: 'RSA', +// kid: 'UFldqYiAzlc1aGj5SoqxqYnWcv2Nc4us2ryQe3-FsUA', +// e: 'AQAB', +// n: +// 'uKEKEJUrnfBdXr6zmzq91fQHhW_8GFFUAYtvt5Uvj9wzsWDbspfL9MorhJgkPioo9T6QQvyyEJBaAQOLZxLsPORk83vmB9OACQT3PEM2LbSFK7XUoZGwqlf8Anvs7M1GwvypYbc1v1WrCqcsjrbmYF9TZkV8nNsy2cweh9gFNR-lIiZCHWDgnP6PifoeGvC9RxKdusFa66vtUJGUcoVmMoiOM7EDVdYOP91qJtbDBx7NPPywwD-8pt3UVBW0bYvOqHGF6XXky5JiB8AZQ2NdZHWxklaM2fd8Mxu9CT3xSYg51nS0KV7wO9lAh_ynBpxE2Qmr-7nvKkkDMOL1FSoEQw', +// alg: 'PS256', +// use: 'sig' } +key.toJWK(true) +// { kty: 'RSA', +// kid: 'UFldqYiAzlc1aGj5SoqxqYnWcv2Nc4us2ryQe3-FsUA', +// e: 'AQAB', +// n: +// 'uKEKEJUrnfBdXr6zmzq91fQHhW_8GFFUAYtvt5Uvj9wzsWDbspfL9MorhJgkPioo9T6QQvyyEJBaAQOLZxLsPORk83vmB9OACQT3PEM2LbSFK7XUoZGwqlf8Anvs7M1GwvypYbc1v1WrCqcsjrbmYF9TZkV8nNsy2cweh9gFNR-lIiZCHWDgnP6PifoeGvC9RxKdusFa66vtUJGUcoVmMoiOM7EDVdYOP91qJtbDBx7NPPywwD-8pt3UVBW0bYvOqHGF6XXky5JiB8AZQ2NdZHWxklaM2fd8Mxu9CT3xSYg51nS0KV7wO9lAh_ynBpxE2Qmr-7nvKkkDMOL1FSoEQw', +// d: +// 'F9G24bLNAMBM23dQ5prqeNrVyZJL_LspUlWx4QZfL3kiNiUf0uegiYE3ohCaxGZeCF288Nd3BYoKAo15g5--WJDCsWLvp1zS7Nb2KpElQTpD4ALCXuHT3_Yf7hYc1-QX1_oOxCuFxJyBx4sPxY21JQPHV69pRzdEVTLvUWk-Kr8k-kgu8xFOsyqLK0g0IBAtwOX2ksIPLuHT-nGh_VQwfpJowq1MoUZD-y_6Ai5HWAZy9t6gARpG3K4yBcmAQBRIQgoFiGw41BqVB5fJyjVZDsMbvT_iEFKkrHRjifUI6QTNtt1k9xOFIL_Ojzn6aLylm58AGD8oORWZvfpmJJ03yQ', +// p: +// '8lvcv5Ov9rJsa_kaCJBRijeOdz3La11_26o2QDpkINFKKoDNWRpIT0KZNF4P16Z5OXOK6rSezuN2vACAPg3riUHVdbRyFhMI6FvQhlx7unyv07xBBqbnp8dV2NiQv3-rFeNPV_5RqZHJyqQga-VUXvwics3eUzm_2CbrMQG3Klc', +// q: +// 'wwVZ6d5uZm9kj3tWICF1FqCWHwSWMt1wgFZ3DOp2LPuqBHjYPas4zwXd3V4wolaCi8irbTfbL0F6c51yN72-enAjgm6r5yzxedkV9GWk5U0y8VrNwYm55qz1o88LB6PX6RG5Lp2rYZp_34dgCrllQc8T-5YY4KIHy7TaLkKkGfU', +// dp: +// 'sPZseCIxcPOVAT3xSWF_eGnah6zCVJH_4vglBr7cD65h9ij4R-BN_jnFvhwUe0Ud7No2C-x4rN4f-2RuP2FQo3dDkt-AEigx79_iocjzuxaCGBu0a1QBgFunjl-LSZjB5oiEjd6v6B4AdwtidQYNlhGKYcN6W9CmCQFZ5_21rZ8', +// dq: +// 'sokKmGSuUw61U_mIjh-zDoTzCfBsBKLepE8D7AoVJ_c43aE37bT7a-MmCst44JUsLAYIkhMpkKh0DrXb45XMdFCG4ZipvRhS9Ma9J6GKBPXYpkYHyZ9pVfmPY2he456mQdOc4UUsqU0EtcE8NnUlcsq9s3vkyHjthBrMBr-xdaU', +// qi: +// 'jbZrzP8f3y0-ZAjqSQAPbKnQI0Vli952nQTUgffF2Bh2q0dB719PHjmIV7NjwCFOMcNx-2usJFwI9VikgN9GTGauakvG7SFzXD8yHiRzFwcjYvXDuJ-4Q1Yjo1m4JUIW_BLVnzauSg0P9qnxT1dxvchEQRIIfF72FW80BsJD4LQ', +// alg: 'PS256', +// use: 'sig' } +``` +
+ +--- + +#### `JWK.importKey(key[, options])` asymmetric key import + +Imports an asymmetric private or public key. Supports importing JWK formatted keys (private, public, +secrets), `pem` and `der` formatted private and public keys, `pem` formatted X.509 certificates. +Private keys may also be passphrase protected. + + +- `key`: `` | `` | `` | `` + - `key`: `` | `` + - `format`: `` Must be 'pem' or 'der'. **Default:** 'pem'. + - `type`: `` Must be 'pkcs1', 'pkcs8' or 'sec1'. This option is required only if the format is 'der' and ignored if it is 'pem'. + - `passphrase`: `` | `` The passphrase to use for decryption. +- `options`: `` + - `alg`: `` option identifies the algorithm intended for use with the key. + - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. + - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] +- Returns: `` | `` + +See the underlying Node.js API for details on importing private and public keys in the different formats + +- [crypto.createPrivateKey(key)](https://nodejs.org/api/crypto.html#crypto_crypto_createprivatekey_key) +- [crypto.createPublicKey(key)](https://nodejs.org/api/crypto.html#crypto_crypto_createpublickey_key) +- [crypto.createSecretKey(key)](https://nodejs.org/api/crypto.html#crypto_crypto_createsecretkey_key) + +
+ Example (Click to expand) + +```js +const { readFileSync } = require('fs') +const { JWK: { importKey } } = require('@panva/jose') + +const key = importKey(readFileSync('path/to/key/file')) +// ECKey { +// kty: 'EC', +// public: true, +// kid: [Getter], +// crv: [Getter], +// x: [Getter], +// y: [Getter] } +``` +
+ +--- + +#### `JWK.importKey(secret[, options])` secret key import + +Imports a symmetric key. + +- `secret`: `` | `` | `` +- `options`: `` + - `alg`: `` option identifies the algorithm intended for use with the key. + - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. + - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] +- Returns: `` + +
+ Example (Click to expand) + +```js +const { JWK: { importKey } } = require('@panva/jose') + +const key = importKey(Buffer.from('8yHym6h5CG5FylbzrCn8fhxEbp3kOaTsgLaawaaJ')) +// OctKey { +// kty: 'oct', +// kid: [Getter], +// k: [Getter] } +``` +
+ +--- + +#### `JWK.importKey(jwk)` JWK-formatted key import + +Imports a JWK formatted key. This supports JWK formatted EC, RSA and oct keys. Asymmetrical keys +may be both private and public. + +- `jwk`: `` + - `kty`: `` Key type. Must be 'RSA', 'EC' or 'oct'. + - `alg`: `` option identifies the algorithm intended for use with the key. + - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. + - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] + - `e`, `n` properties as `` for RSA public keys + - `e`, `n`, `d`, `p`, `q`, `dp`, `dq`, `qi` properties as `` for RSA private keys + - `crv`, `x`, `y` properties as `` for EC public keys + - `crv`, `x`, `y`, `d` properties as `` for EC private keys + - `k` properties as `` for secret oct keys +- Returns: `` | `` | `` + +
+Example (Click to expand) + +```js +const { JWK: { importKey } } = require('@panva/jose') +const jwk = { + kty: 'RSA', + kid: 'r1LkbBo3925Rb2ZFFrKyU3MVex9T2817Kx0vbi6i_Kc', + use: 'sig', + e: 'AQAB', + n: 'xwQ72P9z9OYshiQ-ntDYaPnnfwG6u9JAdLMZ5o0dmjlcyrvwQRdoFIKPnO65Q8mh6F_LDSxjxa2Yzo_wdjhbPZLjfUJXgCzm54cClXzT5twzo7lzoAfaJlkTsoZc2HFWqmcri0BuzmTFLZx2Q7wYBm0pXHmQKF0V-C1O6NWfd4mfBhbM-I1tHYSpAMgarSm22WDMDx-WWI7TEzy2QhaBVaENW9BKaKkJklocAZCxk18WhR0fckIGiWiSM5FcU1PY2jfGsTmX505Ub7P5Dz75Ygqrutd5tFrcqyPAtPTFDk8X1InxkkUwpP3nFU5o50DGhwQolGYKPGtQ-ZtmbOfcWQ' +} + +const key = importKey(jwk) +// RSAKey { +// kty: 'RSA', +// public: true, +// use: 'sig', +// kid: 'r1LkbBo3925Rb2ZFFrKyU3MVex9T2817Kx0vbi6i_Kc', +// e: [Getter], +// n: [Getter] } +``` +
+ +--- + +#### `JWK.generate(kty[, crvOrSize[, options[, private]]])` generating new keys + +Securely generates a new RSA, EC or oct key. + +- `kty`: `` Key type. Must be 'RSA', 'EC' or 'oct'. +- `crvOrSize`: `` | `` key's bit size or in case of EC keys the curve +- `options`: `` + - `alg`: `` option identifies the algorithm intended for use with the key. + - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. + - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] +- `private`: `` **Default** 'true'. Is the resulting key private or public (when asymmetrical) +- Returns: `Promise` | `Promise` | `Promise` + +
+Example (Click to expand) + +```js +const { JWK: { generate } } = require('@panva/jose') +(async () => { + const key = await generate('EC', 'P-384', { use: 'sig' }) + // ECKey { + // kty: 'EC', + // private: true, + // use: 'sig', + // kid: [Getter], + // crv: [Getter], + // x: [Getter], + // y: [Getter], + // d: [Getter] } +})() +``` +
+ +--- + +#### `JWK.generateSync(kty[, crvOrSize[, options[, private]]])` + +Synchronous version of `JWK.generate()` + +- `kty`: `` Key type. Must be 'RSA', 'EC' or 'oct'. +- `crvOrSize`: `` | `` key's bit size or in case of EC keys the curve. **Default** 2048 for RSA, 'P-256' for EC and 256 for oct. +- `options`: `` + - `alg`: `` option identifies the algorithm intended for use with the key. + - `use`: `` option indicates whether the key is to be used for encrypting & decrypting data or signing & verifying data. Must be 'sig' or 'enc'. + - `kid`: `` Key ID Parameter. When not provided is computed using the method defined in [RFC7638][spec-thumbprint] +- `private`: `` **Default** 'true'. Is the resulting key private or public (when asymmetrical) +- Returns: `` | `` | `` + +
+Example (Click to expand) + +```js +const { JWK: { generateSync } } = require('@panva/jose') +const key = generateSync('RSA', 2048, { use: 'enc' }) +// RSAKey { +// kty: 'RSA', +// private: true, +// use: 'enc', +// kid: [Getter], +// e: [Getter], +// n: [Getter], +// d: [Getter], +// p: [Getter], +// q: [Getter], +// dp: [Getter], +// dq: [Getter], +// qi: [Getter] } +``` +
+ +--- + +#### `JWK.isKey(object)` + +Returns 'true' if the value is an instance of ``. + +- `object`: `` +- Returns: `` + +--- + +## JWKS (JSON Web Key Set) + + +- [Class: ](#class-jwkskeystore) + - [new JWKS.KeyStore([keys])](#new-jwkskeystorekeys) + - [keystore.size](#keystoresize) + - [keystore.all([parameters])](#keystoreallparameters) + - [keystore.get([parameters])](#keystoregetparameters) + - [keystore.add(key)](#keystoreaddkey) + - [keystore.remove(key)](#keystoreremovekey) + - [keystore.generate(...)](#keystoregenerate) + - [keystore.generateSync(...)](#keystoregeneratesync) + - [keystore.toJWKS([private])](#keystoretojwksprivate) + - [JWKS.KeyStore.fromJWKS(jwks)](#jwkskeystorefromjwksjwks) + + +```js +const { JWKS } = require('@panva/jose') +// { KeyStore: [Function: KeyStore] } +``` + +#### Class: `` + +`JWKS.KeyStore` is an abstraction representing a set of JWKs, a keystore instance may be queried for +keys matching specific parameters. Keystores may be instantiated either populated, or empty and there +are lifecycle `keystore.remove()` and `keystore.add()` methods for adding/removing keys from an existing +store. + +--- + +#### `new JWKS.KeyStore([keys])` + +Creates a new KeyStore, either empty or populated. + +- `keys`: `` Array of key keys instantiated by `JWK.importKey()` +- Returns: `` + +--- + +#### `keystore.size` + +Returns the number of keys in the keystore. + +- `` +--- + +#### `keystore.all([parameters])` + +Retrieves an array of keys matching the provider parameters, returns all if none are provided. The +returned array is sorted by relevance based on the parameters. Keys with the exact algorithm or use +specified by the parameters are first. + +- `parameters`: `` + - `kty`: `` Key Type to filter for. + - `alg`: `` Key supported algorithm to filter for. + - `use`: `` Key use to filter for. + - `kid`: `` Key ID to filter for. +- Returns: `` Array of key instances or an empty array when none are matching the parameters. + +--- + +#### `keystore.get([parameters])` + +Retrieves a single key matching the provider parameters. The most relevant Key based on the +parameters is returned. + +- `parameters`: `` + - `kty`: `` Key Type to filter for. + - `alg`: `` Key supported algorithm to filter for. + - `use`: `` Key use to filter for. + - `kid`: `` Key ID to filter for. +- Returns: `` | `` | `` | `` + +--- + +#### `keystore.add(key)` + +Adds a key instance to the store unless it is already included. + +- `key`: `` | `` | `` + +--- + +#### `keystore.remove(key)` + +Ensures a key is removed from a store. + +- `key`: `` | `` | `` + +--- + +#### `keystore.generate(...)` + +Asynchronously generates new random key and automatically adds it to the store. See `JWK.generate()` for the API. + +--- + +#### `keystore.generateSync(...)` + +Synchronous version of `keystore.generate()`. + +--- + +#### `keystore.toJWKS([private])` + +Exports the keystore to a JSON Web Key Set formatted object. + +- `private`: `` When true exports keys with their private components. **Default:** 'false' +- Returns: `` + +--- + +#### `JWKS.KeyStore.fromJWKS(jwks)` + +Creates a new KeyStore from a JSON Web Key Set. + +- `jwks`: `` JWKS formatted object (`{ keys: [{ kty: '...', ... }, ...] }`) +- Returns: `` + +
+Example (Click to expand) + +```js +const { JWKS: { KeyStore } } = require('@panva/jose') +const jwks = { keys: + [ { kty: 'RSA', + kid: 'gqUcZ2TjhmNrVOd1d27tedkabhOTs9WghMHIyjIBn7Y', + e: 'AQAB', + n: + 'vi1Aui6R0rUL_7pdcFKKMhBF25h4x8WiTZ4w66eNZhwIp48lz-vBuyUUrSR-RwcuvnxlXdjBdSaN-PZkNRDv2bXE3mVtjZgoYyzQlGLJ1wduQaBXIkrQWxc7yzL91MvtP1kWwFHHrQHZRlpiFQQm9gNCy2wXCTbWGT9kjrR1W1bkwhmOKK4rF-hMgaCNDrtEQ6xWknxV8aXW4itouJ0pJv8xplc6J14f_SNq6arVUcAZ26EzJYC2fcvqwsrnKzvW7QxQGQzh-u9Tn82Tl14Omh1KDV8C7Vb_m8XClv_9zOrKBGdaTl1zgINyMEaa_IMophnBgK_kAXvtVvEThQ93GQ', + use: 'enc' } ] } +const ks = KeyStore.fromJWKS(jwks) +// KeyStore {} +ks.size +// 1 +``` +
+ +--- + +## JWS (JSON Web Signature) + + +- [Class: <JWS.Sign>](#class-jwssign) + - [new JWS.Sign(payload)](#new-jwssignpayload) + - [sign.recipient(key[, protected[, header]])](#signrecipientkey-protected-header) + - [sign.sign(serialization)](#signsignserialization) +- [JWS.sign(payload, key[, protected])](#jwssignpayload-key-protected) +- [JWS.sign.flattened(payload, key[, protected[, header]])](#jwssignflattenedpayload-key-protected-header) +- [JWS.verify(jws, keyOrStore[, options])](#jwsverifyjws-keyorstore-options) + + +The `` module provides methods required to sign or verify JSON Web Signatures in either one of +the defined serializations. + +```js +const { JWS } = require('@panva/jose') +// { Sign: [Function: Sign], +// sign: +// { [Function: bound single] +// flattened: [Function: bound single] }, +// verify: [Function: bound jwsVerify] } +``` + +#### Class: `` + +`` is the class used when you need to produce a JWS for multiple recipients (with multiple +signatures of the same payload) using the General JWS JSON Serialization Syntax. + +
+Example (Click to expand) + +```js +const { JWK, JWS } = require('@panva/jose') + +const key = JWK.importKey({ + kty: 'oct', + k: 'hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg' +}) +const key2 = JWK.importKey({ + kty: 'oct', + k: 'AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8' +}) + +const payload = { + sub: 'John Doe' +} + +const sig = new JWS.Sign(payload) +sig.recipient(key, { alg: 'HS256' }, { foo: 'bar' }) +sig.recipient(key2, { alg: 'HS512' }, { foo: 'baz' }) +sig.sign('general') +// { payload: 'eyJzdWIiOiJKb2huIERvZSJ9', +// signatures: +// [ { protected: 'eyJhbGciOiJIUzI1NiJ9', +// header: { foo: 'bar' }, +// signature: 'mnBcKK-9setCco03NtYws-RMlYXP3LGlDu2RUB7vetQ' }, +// { protected: 'eyJhbGciOiJIUzUxMiJ9', +// header: { foo: 'baz' }, +// signature: +// 'R7e5ZUkgiZQLh8JagoCbwAY21e9A-Y0rhUGQkhihLOvIU8JG2AyZ9zROOUICaUucf8NQKc2dEaIKdRCXy-fDdQ' } ] } + +``` +
+ +--- + +#### `new JWS.Sign(payload)` + +Creates a new Sign object for the provided payload, intended for one or more recipients. + +- `payload`: `` | `` | `` The payload that will be signed. When `` it + will be automatically serialized to JSON before signing +- Returns: `` + +--- + +#### `sign.recipient(key[, protected[, header]])` + +Adds a recipient to the JWS, the Algorithm that will be used to sign with is either provided as part +of the Protected or Unprotected Header or inferred from the provided `` instance. + +- `key`: `` The key to sign with. +- `protected`: `` Protected Header for this recipient +- `header`: `` Unprotected Header for this recipient + +--- + +#### `sign.sign(serialization)` + +Performs the signing operations for each registered recipient and returns the final JWS representation +in the serialization requested. The JWS is validated for conformance during this step. Please note +that only 'general' and 'flattened' serialization supports Unprotected Per-Recipient Header and only +the 'general' serialization supports multiple recipients. See `` and `` +for shorthand methods to sign for a single recipient. + +- `serialization`: `` JWS Serialization. Must be one of 'general', 'flattened', 'compact' +- Returns: `` | `` + +--- + +#### `JWS.sign(payload, key[, protected])` + +Performs the signing operation and 'compact' JWS serialization of the result. The Algorithm that +will be used to sign with is either provided as part of the Protected Header or inferred from the +provided `` instance. + +- `payload`: `` | `` | `` The payload that will be signed. When `` it + will be automatically serialized to JSON before signing +- `key`: `` The key to sign with. +- `protected`: `` Protected Header +- Returns: `` + +
+Example (Click to expand) + +```js +const { JWK, JWS } = require('@panva/jose') + +const key = JWK.importKey({ + kty: 'oct', + k: 'hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg' +}) + +const payload = { + sub: 'John Doe' +} +JWS.sign(payload, key, { alg: 'HS256' }) +// eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2huIERvZSJ9.mnBcKK-9setCco03NtYws-RMlYXP3LGlDu2RUB7vetQ +``` +
+ +--- + +#### `JWS.sign.flattened(payload, key[, protected[, header]])` + +Performs the signing operation and 'flattened' JWS serialization of the result. The Algorithm that +will be used to sign with is either provided as part of the Protected or Unprotected Header or inferred from the +provided `` instance. + +- `payload`: `` | `` | `` The payload that will be signed. When `` it + will be automatically serialized to JSON before signing +- `key`: `` The key to sign with. +- `protected`: `` Protected Header +- `header`: `` Unprotected Header +- Returns: `` + +
+Example (Click to expand) + +```js +const { JWK, JWS } = require('@panva/jose') + +const key = JWK.importKey({ + kty: 'oct', + k: 'hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg' +}) + +const payload = { + sub: 'John Doe' +} + +JWS.sign.flattened(payload, key) +// { payload: 'eyJzdWIiOiJKb2huIERvZSJ9', +// protected: 'eyJhbGciOiJIUzI1NiJ9', +// signature: 'mnBcKK-9setCco03NtYws-RMlYXP3LGlDu2RUB7vetQ' } +``` +
+ +--- + +#### `JWS.verify(jws, keyOrStore[, options])` + +Verifies the provided JWS in either serialization with a given `` or `` + +- `jws`: `` | `` The JWS to verify. This must be a valid JWS. +- `keyOrStore`: `` | `` The key or store to verify with. When `` + instance is provided a selection of possible candidate keys will be done and the operation will + succeed if just one key or signature (in case of General JWS JSON Serialization Syntax) matches. +- `options`: `` + - `algorithms`: `string[]` Array of Algorithms to accept, when the signature does not use an + algorithm from this list the verification will fail. **Default:** 'undefined' - accepts all + algorithms available on the keys + - `complete`: `` When true returns a complete object with the parsed headers and payload + instead of just the verified payload. **Default:** 'false' + - `crit`: `string[]` Array of Critical Header Parameter names to recognize. **Default:** '[]' +- Returns: `` | `` + +
+Example (Click to expand) + +```js +const { JWK, JWS, JWKS } = require('@panva/jose') + +const key = JWK.importKey({ + kty: 'oct', + k: 'hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg' +}) +const key2 = JWK.importKey({ + kty: 'oct', + k: 'AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8' +}) + +const compact = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2huIERvZSJ9.mnBcKK-9setCco03NtYws-RMlYXP3LGlDu2RUB7vetQ' + +const flattened = { payload: 'eyJzdWIiOiJKb2huIERvZSJ9', + protected: 'eyJhbGciOiJIUzI1NiJ9', + signature: 'mnBcKK-9setCco03NtYws-RMlYXP3LGlDu2RUB7vetQ' } + +const general = { payload: 'eyJzdWIiOiJKb2huIERvZSJ9', + signatures: + [ { protected: 'eyJhbGciOiJIUzI1NiJ9', + header: { foo: 'bar' }, + signature: 'mnBcKK-9setCco03NtYws-RMlYXP3LGlDu2RUB7vetQ' }, + { protected: 'eyJhbGciOiJIUzUxMiJ9', + header: { foo: 'baz' }, + signature: + 'R7e5ZUkgiZQLh8JagoCbwAY21e9A-Y0rhUGQkhihLOvIU8JG2AyZ9zROOUICaUucf8NQKc2dEaIKdRCXy-fDdQ' } ] } + +JWS.verify(compact, key) +// { sub: 'John Doe' } + +JWS.verify(flattened, key2) +// JWSVerificationFailed: signature verification failed + +JWS.verify(compact, key, { complete: true }) +// { payload: { sub: 'John Doe' }, protected: { alg: 'HS256' }, key: OctKey {} } + +JWS.verify(flattened, key, { algorithms: ['PS256'] }) +// JOSEAlgNotWhitelisted: alg not whitelisted + +JWS.verify(general, key) +// { sub: 'John Doe' } +JWS.verify(general, key2) +// { sub: 'John Doe' } + +JWS.verify(general, key, { complete: true }) +// { payload: { sub: 'John Doe' }, +// protected: { alg: 'HS256' }, +// header: { foo: 'bar' }, +// key: : OctKey {} } <- key +JWS.verify(general, key2, { complete: true }) +// { payload: { sub: 'John Doe' }, +// protected: { alg: 'HS512' }, +// header: { foo: 'baz' }, +// key: : OctKey {} } <- key2 +const keystore = new JWKS.KeyStore(key) +JWS.verify(general, keystore, { complete: true }) +// { payload: { sub: 'John Doe' }, +// protected: { alg: 'HS256' }, +// header: { foo: 'bar' }, +// key: : OctKey {} } <- key that matched in the keystore +``` +
+ +--- + +## JWE (JSON Web Encryption) + + +- [Class: <JWE.Encrypt>](#class-jweencrypt) + - [new JWE.Encrypt(cleartext[, protected[, unprotected[, aad]]])](#new-jweencryptcleartext-protected-unprotected-aad) + - [encrypt.recipient(key[, header])](#encryptrecipientkey-header) + - [encrypt.encrypt(serialization)](#encryptencryptserialization) +- [JWE.encrypt(cleartext, key[, protected])](#jweencryptcleartext-key-protected) +- [JWE.encrypt.flattened(cleartext, key[, protected[, header[, aad]]])](#jweencryptflattenedcleartext-key-protected-header-aad) +- [JWE.decrypt(jwe, keyOrStore[, options])](#jwedecryptjwe-keyorstore-options) + + +The `` module provides methods required to encrypt or decrypt JSON Web Encryptions in either one of +the defined serializations. + +```js +const { JWE } = require('@panva/jose') +// { Encrypt: [Function: Encrypt], +// encrypt: +// { [Function: bound single] +// flattened: [Function: bound single] }, +// decrypt: [Function: bound jweDecrypt] } +``` + +#### Class: `` + +`` is the class used when you need to produce a JWE for multiple recipients using the +General JWE JSON Serialization Syntax. + +--- + +#### `new JWE.Encrypt(cleartext[, protected[, unprotected[, aad]]])` + +Creates a new Encrypt object for the provided cleartext with optional Protected and Unprotected +Headers and Additional Authenticated Data. + +- `cleartext`: `` | `` The cleartext that will be encrypted. +- `protected`: `` JWE Protected Header +- `unprotected`: `` JWE Shared Unprotected Header +- `aad`: `` | `` JWE Additional Authenticated Data +- Returns: `` + +--- + +#### `encrypt.recipient(key[, header])` + +Adds a recipient to the JWE, the Algorithm that will be used to wrap or derive the Content Encryption +Key (CEK) is either provided as part of the combined JWE Header for the recipient or inferred from +the provided `` instance. + +- `key`: `` The key to use for Key Management or Direct Encryption +- `header`: `` JWE Per-Recipient Unprotected Header + +--- + +#### `encrypt.encrypt(serialization)` + +Performs the encryption operations for each registered recipient and returns the final JWE representation +in the serialization requested. The JWE is validated for conformance during this step. Please note +that only 'general' and 'flattened' serialization supports Unprotected Per-Recipient Header and AAD +and only the 'general' serialization supports multiple recipients. See `` and `` +for shorthand methods to encrypt for a single recipient. + +- `serialization`: `` JWE Serialization. Must be one of 'general', 'flattened', 'compact' +- Returns: `` | `` + +--- + +#### `JWE.encrypt(cleartext, key[, protected])` + +Performs the encryption operation and 'compact' JWE serialization of the result. The Algorithm that +will be used to wrap or derive the Content Encryption Key (CEK) is either provided as part of the +Protected Header or inferred from the provided `` instance. + +- `cleartext`: `` | `` The cleartext that will be encrypted. +- `key`: `` The key to use for Key Management or Direct Encryption +- `protected`: `` JWE Protected Header +- Returns: `` + +--- + +#### `JWE.encrypt.flattened(cleartext, key[, protected[, header[, aad]]])` + +Performs the encryption operation and 'flattened' JWE serialization of the result. The Algorithm that +will be used to wrap or derive the Content Encryption Key (CEK) is either provided as part of the +combined JWE Header or inferred from the provided `` instance. + +- `cleartext`: `` | `` The cleartext that will be encrypted. +- `key`: `` The key to use for Key Management or Direct Encryption +- `protected`: `` JWE Protected Header +- `unprotected`: `` JWE Shared Unprotected Header +- `aad`: `` | `` JWE Additional Authenticated Data +- Returns: `` + +--- + +#### `JWE.decrypt(jwe, keyOrStore[, options])` + +Verifies the provided JWE in either serialization with a given `` or `` + +- `jwe`: `` | `` The JWE to decrypt. This must be a valid JWE. +- `keyOrStore`: `` | `` The key or store to decrypt with. When `` + instance is provided a selection of possible candidate keys will be done and the operation will + succeed if just one key or signature (in case of General JWE JSON Serialization Syntax) matches. +- `options`: `` + - `algorithms`: `string[]` Array of Algorithms to accept, when the JWE does not use an + Key Management algorithm from this list the decryption will fail. **Default:** 'undefined' - + accepts all algorithms available on the keys + - `complete`: `` When true returns a complete object with the parsed headers, verified AAD + and cleartext instead of just the cleartext. **Default:** 'false' +- Returns: `` | `` + + +[spec-thumbprint]: https://tools.ietf.org/html/rfc7638 +[support-patreon]: https://www.patreon.com/panva +[support-paypal]: https://www.paypal.me/panva diff --git a/package.json b/package.json index d55d4a10..c74fccf6 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { "name": "@panva/jose", - "version": "0.0.1", + "version": "0.8.0", "description": "JSON Web Almost Everything - JWA, JWS, JWE, JWK, JWKS for Node.js with minimal dependencies", "keywords": [ + "compact", "decrypt", "encrypt", + "flattened", + "general", "jose", "jwa", "jwe", @@ -63,6 +66,7 @@ } }, "nyc": { + "all": true, "reporter": [ "lcov", "text-summary"