mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-03-15 19:50:58 +00:00
fix: better error for missing web crypto
This PR simply detects missing web crypto and throws an error with an appropriate message. This is a stepping stone that will help users understand the problem until we have time to do a refactor of this module and of all the modules that use it to enable optionally passing your own crypto implementation. refs https://github.com/libp2p/js-libp2p-crypto/pull/149 refs https://github.com/libp2p/js-libp2p-crypto/pull/150 refs https://github.com/libp2p/js-libp2p-crypto/issues/105 refs https://github.com/ipfs/js-ipfs/issues/2153 refs https://github.com/ipfs/js-ipfs/issues/2017 License: MIT Signed-off-by: Alan Shaw <alan@tableflip.io>
This commit is contained in:
parent
0b686d363c
commit
a5e05603ef
23
README.md
23
README.md
@ -51,11 +51,32 @@ This repo contains the JavaScript implementation of the crypto primitives needed
|
||||
npm install --save libp2p-crypto
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const crypto = require('libp2p-crypto')
|
||||
|
||||
// Now available to you:
|
||||
//
|
||||
// crypto.aes
|
||||
// crypto.hmac
|
||||
// crypto.keys
|
||||
// etc.
|
||||
//
|
||||
// See full API details below...
|
||||
```
|
||||
|
||||
### Web Crypto API
|
||||
|
||||
The `libp2p-crypto` library depends on the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) in the browser. Web Crypto is available in all modern browsers, however browsers restrict its usage to [Secure Contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
|
||||
|
||||
**This means you will not be able to use some `libp2p-crypto` functions in the browser when the page is served over HTTP.**
|
||||
|
||||
## API
|
||||
|
||||
### `crypto.aes`
|
||||
|
||||
Expoes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
|
||||
Exposes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
|
||||
|
||||
This uses `CTR` mode.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const webcrypto = require('../webcrypto.js')
|
||||
const webcrypto = require('../webcrypto')
|
||||
const lengths = require('./lengths')
|
||||
|
||||
const hashTypes = {
|
||||
@ -10,13 +10,13 @@ const hashTypes = {
|
||||
}
|
||||
|
||||
const sign = async (key, data) => {
|
||||
return Buffer.from(await webcrypto.subtle.sign({ name: 'HMAC' }, key, data))
|
||||
return Buffer.from(await webcrypto.get().subtle.sign({ name: 'HMAC' }, key, data))
|
||||
}
|
||||
|
||||
exports.create = async function (hashType, secret) {
|
||||
const hash = hashTypes[hashType]
|
||||
|
||||
const key = await webcrypto.subtle.importKey(
|
||||
const key = await webcrypto.get().subtle.importKey(
|
||||
'raw',
|
||||
secret,
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const errcode = require('err-code')
|
||||
const webcrypto = require('../webcrypto.js')
|
||||
const webcrypto = require('../webcrypto')
|
||||
const BN = require('asn1.js').bignum
|
||||
const { toBase64, toBn } = require('../util')
|
||||
const validateCurveType = require('./validate-curve-type')
|
||||
@ -14,8 +14,7 @@ const bits = {
|
||||
|
||||
exports.generateEphmeralKeyPair = async function (curve) {
|
||||
validateCurveType(Object.keys(bits), curve)
|
||||
|
||||
const pair = await webcrypto.subtle.generateKey(
|
||||
const pair = await webcrypto.get().subtle.generateKey(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve
|
||||
@ -29,7 +28,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
|
||||
let privateKey
|
||||
|
||||
if (forcePrivate) {
|
||||
privateKey = await webcrypto.subtle.importKey(
|
||||
privateKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPrivateKey(curve, forcePrivate),
|
||||
{
|
||||
@ -44,7 +43,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
|
||||
}
|
||||
|
||||
const keys = [
|
||||
await webcrypto.subtle.importKey(
|
||||
await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
unmarshalPublicKey(curve, theirPub),
|
||||
{
|
||||
@ -57,7 +56,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
|
||||
privateKey
|
||||
]
|
||||
|
||||
return Buffer.from(await webcrypto.subtle.deriveBits(
|
||||
return Buffer.from(await webcrypto.get().subtle.deriveBits(
|
||||
{
|
||||
name: 'ECDH',
|
||||
namedCurve: curve,
|
||||
@ -68,7 +67,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
|
||||
))
|
||||
}
|
||||
|
||||
const publicKey = await webcrypto.subtle.exportKey('jwk', pair.publicKey)
|
||||
const publicKey = await webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
|
||||
|
||||
return {
|
||||
key: marshalPublicKey(publicKey),
|
||||
|
@ -1,12 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const webcrypto = require('../webcrypto.js')
|
||||
const webcrypto = require('../webcrypto')
|
||||
const randomBytes = require('../random-bytes')
|
||||
|
||||
exports.utils = require('./rsa-utils')
|
||||
|
||||
exports.generateKey = async function (bits) {
|
||||
const pair = await webcrypto.subtle.generateKey(
|
||||
const pair = await webcrypto.get().subtle.generateKey(
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: bits,
|
||||
@ -27,7 +27,7 @@ exports.generateKey = async function (bits) {
|
||||
|
||||
// Takes a jwk key
|
||||
exports.unmarshalPrivateKey = async function (key) {
|
||||
const privateKey = await webcrypto.subtle.importKey(
|
||||
const privateKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
@ -57,7 +57,7 @@ exports.unmarshalPrivateKey = async function (key) {
|
||||
exports.getRandomValues = randomBytes
|
||||
|
||||
exports.hashAndSign = async function (key, msg) {
|
||||
const privateKey = await webcrypto.subtle.importKey(
|
||||
const privateKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
@ -68,7 +68,7 @@ exports.hashAndSign = async function (key, msg) {
|
||||
['sign']
|
||||
)
|
||||
|
||||
const sig = await webcrypto.subtle.sign(
|
||||
const sig = await webcrypto.get().subtle.sign(
|
||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||
privateKey,
|
||||
Uint8Array.from(msg)
|
||||
@ -78,7 +78,7 @@ exports.hashAndSign = async function (key, msg) {
|
||||
}
|
||||
|
||||
exports.hashAndVerify = async function (key, sig, msg) {
|
||||
const publicKey = await webcrypto.subtle.importKey(
|
||||
const publicKey = await webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
key,
|
||||
{
|
||||
@ -89,7 +89,7 @@ exports.hashAndVerify = async function (key, sig, msg) {
|
||||
['verify']
|
||||
)
|
||||
|
||||
return webcrypto.subtle.verify(
|
||||
return webcrypto.get().subtle.verify(
|
||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||
publicKey,
|
||||
sig,
|
||||
@ -99,13 +99,13 @@ exports.hashAndVerify = async function (key, sig, msg) {
|
||||
|
||||
function exportKey (pair) {
|
||||
return Promise.all([
|
||||
webcrypto.subtle.exportKey('jwk', pair.privateKey),
|
||||
webcrypto.subtle.exportKey('jwk', pair.publicKey)
|
||||
webcrypto.get().subtle.exportKey('jwk', pair.privateKey),
|
||||
webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
|
||||
])
|
||||
}
|
||||
|
||||
function derivePublicFromPrivate (jwKey) {
|
||||
return webcrypto.subtle.importKey(
|
||||
return webcrypto.get().subtle.importKey(
|
||||
'jwk',
|
||||
{
|
||||
kty: jwKey.kty,
|
||||
|
@ -1,5 +1,24 @@
|
||||
/* global self */
|
||||
/* eslint-env browser */
|
||||
|
||||
'use strict'
|
||||
|
||||
module.exports = self.crypto || self.msCrypto
|
||||
// Check native crypto exists and is enabled (In insecure context `self.crypto`
|
||||
// exists but `self.crypto.subtle` does not).
|
||||
exports.get = (win = self) => {
|
||||
const nativeCrypto = win.crypto || win.msCrypto
|
||||
|
||||
if (!nativeCrypto || !nativeCrypto.subtle) {
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
'Missing Web Crypto API. ' +
|
||||
'The most likely cause of this error is that this page is being accessed ' +
|
||||
'from an insecure context (i.e. not HTTPS). For more information and ' +
|
||||
'possible resolutions see ' +
|
||||
'https://github.com/libp2p/js-libp2p-crypto/blob/master/README.md#web-crypto-api'
|
||||
),
|
||||
{ code: 'ERR_MISSING_WEB_CRYPTO' }
|
||||
)
|
||||
}
|
||||
|
||||
return nativeCrypto
|
||||
}
|
||||
|
61
test/browser.js
Normal file
61
test/browser.js
Normal file
@ -0,0 +1,61 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const crypto = require('../')
|
||||
const webcrypto = require('../src/webcrypto')
|
||||
|
||||
async function expectMissingWebCrypto (fn) {
|
||||
try {
|
||||
await fn()
|
||||
} catch (err) {
|
||||
expect(err.code).to.equal('ERR_MISSING_WEB_CRYPTO')
|
||||
return
|
||||
}
|
||||
throw new Error('Expected missing web crypto error')
|
||||
}
|
||||
|
||||
describe('Missing web crypto', () => {
|
||||
let webcryptoGet
|
||||
let rsaPrivateKey
|
||||
|
||||
before(async () => {
|
||||
rsaPrivateKey = await crypto.keys.generateKeyPair('RSA', 512)
|
||||
})
|
||||
|
||||
before(() => {
|
||||
webcryptoGet = webcrypto.get
|
||||
webcrypto.get = () => webcryptoGet({})
|
||||
})
|
||||
|
||||
after(() => {
|
||||
webcrypto.get = webcryptoGet
|
||||
})
|
||||
|
||||
it('should error for hmac create when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.hmac.create('SHA256', Buffer.from('secret')))
|
||||
})
|
||||
|
||||
it('should error for generate ephemeral key pair when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.generateEphemeralKeyPair('P-256'))
|
||||
})
|
||||
|
||||
it('should error for generate rsa key pair when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.generateKeyPair('rsa', 256))
|
||||
})
|
||||
|
||||
it('should error for unmarshal RSA private key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => crypto.keys.unmarshalPrivateKey(crypto.keys.marshalPrivateKey(rsaPrivateKey)))
|
||||
})
|
||||
|
||||
it('should error for sign RSA private key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => rsaPrivateKey.sign(Buffer.from('test')))
|
||||
})
|
||||
|
||||
it('should error for verify RSA public key when web crypto is missing', () => {
|
||||
return expectMissingWebCrypto(() => rsaPrivateKey.public.verify(Buffer.from('test'), Buffer.from('test')))
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user