mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-03-15 21:50:54 +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
|
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
|
## API
|
||||||
|
|
||||||
### `crypto.aes`
|
### `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.
|
This uses `CTR` mode.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const webcrypto = require('../webcrypto.js')
|
const webcrypto = require('../webcrypto')
|
||||||
const lengths = require('./lengths')
|
const lengths = require('./lengths')
|
||||||
|
|
||||||
const hashTypes = {
|
const hashTypes = {
|
||||||
@ -10,13 +10,13 @@ const hashTypes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sign = async (key, data) => {
|
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) {
|
exports.create = async function (hashType, secret) {
|
||||||
const hash = hashTypes[hashType]
|
const hash = hashTypes[hashType]
|
||||||
|
|
||||||
const key = await webcrypto.subtle.importKey(
|
const key = await webcrypto.get().subtle.importKey(
|
||||||
'raw',
|
'raw',
|
||||||
secret,
|
secret,
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const errcode = require('err-code')
|
const errcode = require('err-code')
|
||||||
const webcrypto = require('../webcrypto.js')
|
const webcrypto = require('../webcrypto')
|
||||||
const BN = require('asn1.js').bignum
|
const BN = require('asn1.js').bignum
|
||||||
const { toBase64, toBn } = require('../util')
|
const { toBase64, toBn } = require('../util')
|
||||||
const validateCurveType = require('./validate-curve-type')
|
const validateCurveType = require('./validate-curve-type')
|
||||||
@ -14,8 +14,7 @@ const bits = {
|
|||||||
|
|
||||||
exports.generateEphmeralKeyPair = async function (curve) {
|
exports.generateEphmeralKeyPair = async function (curve) {
|
||||||
validateCurveType(Object.keys(bits), curve)
|
validateCurveType(Object.keys(bits), curve)
|
||||||
|
const pair = await webcrypto.get().subtle.generateKey(
|
||||||
const pair = await webcrypto.subtle.generateKey(
|
|
||||||
{
|
{
|
||||||
name: 'ECDH',
|
name: 'ECDH',
|
||||||
namedCurve: curve
|
namedCurve: curve
|
||||||
@ -29,7 +28,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
|
|||||||
let privateKey
|
let privateKey
|
||||||
|
|
||||||
if (forcePrivate) {
|
if (forcePrivate) {
|
||||||
privateKey = await webcrypto.subtle.importKey(
|
privateKey = await webcrypto.get().subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
unmarshalPrivateKey(curve, forcePrivate),
|
unmarshalPrivateKey(curve, forcePrivate),
|
||||||
{
|
{
|
||||||
@ -44,7 +43,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const keys = [
|
const keys = [
|
||||||
await webcrypto.subtle.importKey(
|
await webcrypto.get().subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
unmarshalPublicKey(curve, theirPub),
|
unmarshalPublicKey(curve, theirPub),
|
||||||
{
|
{
|
||||||
@ -57,7 +56,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
|
|||||||
privateKey
|
privateKey
|
||||||
]
|
]
|
||||||
|
|
||||||
return Buffer.from(await webcrypto.subtle.deriveBits(
|
return Buffer.from(await webcrypto.get().subtle.deriveBits(
|
||||||
{
|
{
|
||||||
name: 'ECDH',
|
name: 'ECDH',
|
||||||
namedCurve: curve,
|
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 {
|
return {
|
||||||
key: marshalPublicKey(publicKey),
|
key: marshalPublicKey(publicKey),
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const webcrypto = require('../webcrypto.js')
|
const webcrypto = require('../webcrypto')
|
||||||
const randomBytes = require('../random-bytes')
|
const randomBytes = require('../random-bytes')
|
||||||
|
|
||||||
exports.utils = require('./rsa-utils')
|
exports.utils = require('./rsa-utils')
|
||||||
|
|
||||||
exports.generateKey = async function (bits) {
|
exports.generateKey = async function (bits) {
|
||||||
const pair = await webcrypto.subtle.generateKey(
|
const pair = await webcrypto.get().subtle.generateKey(
|
||||||
{
|
{
|
||||||
name: 'RSASSA-PKCS1-v1_5',
|
name: 'RSASSA-PKCS1-v1_5',
|
||||||
modulusLength: bits,
|
modulusLength: bits,
|
||||||
@ -27,7 +27,7 @@ exports.generateKey = async function (bits) {
|
|||||||
|
|
||||||
// Takes a jwk key
|
// Takes a jwk key
|
||||||
exports.unmarshalPrivateKey = async function (key) {
|
exports.unmarshalPrivateKey = async function (key) {
|
||||||
const privateKey = await webcrypto.subtle.importKey(
|
const privateKey = await webcrypto.get().subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
key,
|
key,
|
||||||
{
|
{
|
||||||
@ -57,7 +57,7 @@ exports.unmarshalPrivateKey = async function (key) {
|
|||||||
exports.getRandomValues = randomBytes
|
exports.getRandomValues = randomBytes
|
||||||
|
|
||||||
exports.hashAndSign = async function (key, msg) {
|
exports.hashAndSign = async function (key, msg) {
|
||||||
const privateKey = await webcrypto.subtle.importKey(
|
const privateKey = await webcrypto.get().subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
key,
|
key,
|
||||||
{
|
{
|
||||||
@ -68,7 +68,7 @@ exports.hashAndSign = async function (key, msg) {
|
|||||||
['sign']
|
['sign']
|
||||||
)
|
)
|
||||||
|
|
||||||
const sig = await webcrypto.subtle.sign(
|
const sig = await webcrypto.get().subtle.sign(
|
||||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||||
privateKey,
|
privateKey,
|
||||||
Uint8Array.from(msg)
|
Uint8Array.from(msg)
|
||||||
@ -78,7 +78,7 @@ exports.hashAndSign = async function (key, msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exports.hashAndVerify = async function (key, sig, msg) {
|
exports.hashAndVerify = async function (key, sig, msg) {
|
||||||
const publicKey = await webcrypto.subtle.importKey(
|
const publicKey = await webcrypto.get().subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
key,
|
key,
|
||||||
{
|
{
|
||||||
@ -89,7 +89,7 @@ exports.hashAndVerify = async function (key, sig, msg) {
|
|||||||
['verify']
|
['verify']
|
||||||
)
|
)
|
||||||
|
|
||||||
return webcrypto.subtle.verify(
|
return webcrypto.get().subtle.verify(
|
||||||
{ name: 'RSASSA-PKCS1-v1_5' },
|
{ name: 'RSASSA-PKCS1-v1_5' },
|
||||||
publicKey,
|
publicKey,
|
||||||
sig,
|
sig,
|
||||||
@ -99,13 +99,13 @@ exports.hashAndVerify = async function (key, sig, msg) {
|
|||||||
|
|
||||||
function exportKey (pair) {
|
function exportKey (pair) {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
webcrypto.subtle.exportKey('jwk', pair.privateKey),
|
webcrypto.get().subtle.exportKey('jwk', pair.privateKey),
|
||||||
webcrypto.subtle.exportKey('jwk', pair.publicKey)
|
webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function derivePublicFromPrivate (jwKey) {
|
function derivePublicFromPrivate (jwKey) {
|
||||||
return webcrypto.subtle.importKey(
|
return webcrypto.get().subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
{
|
{
|
||||||
kty: jwKey.kty,
|
kty: jwKey.kty,
|
||||||
|
@ -1,5 +1,24 @@
|
|||||||
/* global self */
|
/* eslint-env browser */
|
||||||
|
|
||||||
'use strict'
|
'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