Skip to content
This repository was archived by the owner on Jul 21, 2023. It is now read-only.

Commit af9f64c

Browse files
committed
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 #149 refs #150 refs #105 refs ipfs/js-ipfs#2153 refs ipfs/js-ipfs#2017 License: MIT Signed-off-by: Alan Shaw <alan@tableflip.io>
1 parent 2d15e71 commit af9f64c

File tree

6 files changed

+123
-22
lines changed

6 files changed

+123
-22
lines changed

README.md

+22-1
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,32 @@ This repo contains the JavaScript implementation of the crypto primitives needed
5151
npm install --save libp2p-crypto
5252
```
5353

54+
## Usage
55+
56+
```js
57+
const crypto = require('libp2p-crypto')
58+
59+
// Now available to you:
60+
//
61+
// crypto.aes
62+
// crypto.hmac
63+
// crypto.keys
64+
// etc.
65+
//
66+
// See full API details below...
67+
```
68+
69+
### Web Crypto API
70+
71+
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).
72+
73+
**This means you will not be able to use some `libp2p-crypto` functions in the browser when the page is served over HTTP.**
74+
5475
## API
5576

5677
### `crypto.aes`
5778

58-
Expoes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
79+
Exposes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
5980

6081
This uses `CTR` mode.
6182

src/hmac/index-browser.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

3-
const webcrypto = require('../webcrypto.js')
3+
const webcrypto = require('../webcrypto')
44
const lengths = require('./lengths')
55

66
const hashTypes = {
@@ -10,13 +10,13 @@ const hashTypes = {
1010
}
1111

1212
const sign = async (key, data) => {
13-
return Buffer.from(await webcrypto.subtle.sign({ name: 'HMAC' }, key, data))
13+
return Buffer.from(await webcrypto.get().subtle.sign({ name: 'HMAC' }, key, data))
1414
}
1515

1616
exports.create = async function (hashType, secret) {
1717
const hash = hashTypes[hashType]
1818

19-
const key = await webcrypto.subtle.importKey(
19+
const key = await webcrypto.get().subtle.importKey(
2020
'raw',
2121
secret,
2222
{

src/keys/ecdh-browser.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

3-
const webcrypto = require('../webcrypto.js')
3+
const webcrypto = require('../webcrypto')
44
const BN = require('asn1.js').bignum
55
const { toBase64, toBn } = require('../util')
66

@@ -11,7 +11,7 @@ const bits = {
1111
}
1212

1313
exports.generateEphmeralKeyPair = async function (curve) {
14-
const pair = await webcrypto.subtle.generateKey(
14+
const pair = await webcrypto.get().subtle.generateKey(
1515
{
1616
name: 'ECDH',
1717
namedCurve: curve
@@ -25,7 +25,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
2525
let privateKey
2626

2727
if (forcePrivate) {
28-
privateKey = await webcrypto.subtle.importKey(
28+
privateKey = await webcrypto.get().subtle.importKey(
2929
'jwk',
3030
unmarshalPrivateKey(curve, forcePrivate),
3131
{
@@ -40,7 +40,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
4040
}
4141

4242
const keys = [
43-
await webcrypto.subtle.importKey(
43+
await webcrypto.get().subtle.importKey(
4444
'jwk',
4545
unmarshalPublicKey(curve, theirPub),
4646
{
@@ -53,7 +53,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
5353
privateKey
5454
]
5555

56-
return Buffer.from(await webcrypto.subtle.deriveBits(
56+
return Buffer.from(await webcrypto.get().subtle.deriveBits(
5757
{
5858
name: 'ECDH',
5959
namedCurve: curve,
@@ -64,7 +64,7 @@ exports.generateEphmeralKeyPair = async function (curve) {
6464
))
6565
}
6666

67-
const publicKey = await webcrypto.subtle.exportKey('jwk', pair.publicKey)
67+
const publicKey = await webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
6868

6969
return {
7070
key: marshalPublicKey(publicKey),

src/keys/rsa-browser.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use strict'
22

3-
const webcrypto = require('../webcrypto.js')
3+
const webcrypto = require('../webcrypto')
44
const randomBytes = require('../random-bytes')
55

66
exports.utils = require('./rsa-utils')
77

88
exports.generateKey = async function (bits) {
9-
const pair = await webcrypto.subtle.generateKey(
9+
const pair = await webcrypto.get().subtle.generateKey(
1010
{
1111
name: 'RSASSA-PKCS1-v1_5',
1212
modulusLength: bits,
@@ -27,7 +27,7 @@ exports.generateKey = async function (bits) {
2727

2828
// Takes a jwk key
2929
exports.unmarshalPrivateKey = async function (key) {
30-
const privateKey = await webcrypto.subtle.importKey(
30+
const privateKey = await webcrypto.get().subtle.importKey(
3131
'jwk',
3232
key,
3333
{
@@ -57,7 +57,7 @@ exports.unmarshalPrivateKey = async function (key) {
5757
exports.getRandomValues = randomBytes
5858

5959
exports.hashAndSign = async function (key, msg) {
60-
const privateKey = await webcrypto.subtle.importKey(
60+
const privateKey = await webcrypto.get().subtle.importKey(
6161
'jwk',
6262
key,
6363
{
@@ -68,7 +68,7 @@ exports.hashAndSign = async function (key, msg) {
6868
['sign']
6969
)
7070

71-
const sig = await webcrypto.subtle.sign(
71+
const sig = await webcrypto.get().subtle.sign(
7272
{ name: 'RSASSA-PKCS1-v1_5' },
7373
privateKey,
7474
Uint8Array.from(msg)
@@ -78,7 +78,7 @@ exports.hashAndSign = async function (key, msg) {
7878
}
7979

8080
exports.hashAndVerify = async function (key, sig, msg) {
81-
const publicKey = await webcrypto.subtle.importKey(
81+
const publicKey = await webcrypto.get().subtle.importKey(
8282
'jwk',
8383
key,
8484
{
@@ -89,7 +89,7 @@ exports.hashAndVerify = async function (key, sig, msg) {
8989
['verify']
9090
)
9191

92-
return webcrypto.subtle.verify(
92+
return webcrypto.get().subtle.verify(
9393
{ name: 'RSASSA-PKCS1-v1_5' },
9494
publicKey,
9595
sig,
@@ -99,13 +99,13 @@ exports.hashAndVerify = async function (key, sig, msg) {
9999

100100
function exportKey (pair) {
101101
return Promise.all([
102-
webcrypto.subtle.exportKey('jwk', pair.privateKey),
103-
webcrypto.subtle.exportKey('jwk', pair.publicKey)
102+
webcrypto.get().subtle.exportKey('jwk', pair.privateKey),
103+
webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
104104
])
105105
}
106106

107107
function derivePublicFromPrivate (jwKey) {
108-
return webcrypto.subtle.importKey(
108+
return webcrypto.get().subtle.importKey(
109109
'jwk',
110110
{
111111
kty: jwKey.kty,

src/webcrypto.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1-
/* global self */
1+
/* eslint-env browser */
22

33
'use strict'
44

5-
module.exports = self.crypto || self.msCrypto
5+
// Check native crypto exists and is enabled (In insecure context `self.crypto`
6+
// exists but `self.crypto.subtle` does not).
7+
exports.get = (win = self) => {
8+
const nativeCrypto = win.crypto || win.msCrypto
9+
10+
if (!nativeCrypto || !nativeCrypto.subtle) {
11+
throw Object.assign(
12+
new Error(
13+
'Missing Web Crypto API. ' +
14+
'The most likely cause of this error is that this page is being accessed ' +
15+
'from an insecure context (i.e. not HTTPS). For more information and ' +
16+
'possible resolutions see ' +
17+
'https://github.com/libp2p/js-libp2p-crypto/blob/master/README.md#web-crypto-api'
18+
),
19+
{ code: 'ERR_MISSING_WEB_CRYPTO' }
20+
)
21+
}
22+
23+
return nativeCrypto
24+
}

test/browser.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const chai = require('chai')
5+
const dirtyChai = require('dirty-chai')
6+
const expect = chai.expect
7+
chai.use(dirtyChai)
8+
const crypto = require('../')
9+
const webcrypto = require('../src/webcrypto')
10+
11+
async function expectMissingWebCrypto (fn) {
12+
try {
13+
await fn()
14+
} catch (err) {
15+
expect(err.code).to.equal('ERR_MISSING_WEB_CRYPTO')
16+
return
17+
}
18+
throw new Error('Expected missing web crypto error')
19+
}
20+
21+
describe('Missing web crypto', () => {
22+
let webcryptoGet
23+
let rsaPrivateKey
24+
25+
before(async () => {
26+
rsaPrivateKey = await crypto.keys.generateKeyPair('RSA', 512)
27+
})
28+
29+
before(() => {
30+
webcryptoGet = webcrypto.get
31+
webcrypto.get = () => webcryptoGet({})
32+
})
33+
34+
after(() => {
35+
webcrypto.get = webcryptoGet
36+
})
37+
38+
it('should error for hmac create when web crypto is missing', () => {
39+
return expectMissingWebCrypto(() => crypto.hmac.create('SHA256', Buffer.from('secret')))
40+
})
41+
42+
it('should error for generate ephemeral key pair when web crypto is missing', () => {
43+
return expectMissingWebCrypto(() => crypto.keys.generateEphemeralKeyPair('P-256'))
44+
})
45+
46+
it('should error for generate rsa key pair when web crypto is missing', () => {
47+
return expectMissingWebCrypto(() => crypto.keys.generateKeyPair('rsa', 256))
48+
})
49+
50+
it('should error for unmarshal RSA private key when web crypto is missing', () => {
51+
return expectMissingWebCrypto(() => crypto.keys.unmarshalPrivateKey(crypto.keys.marshalPrivateKey(rsaPrivateKey)))
52+
})
53+
54+
it('should error for sign RSA private key when web crypto is missing', () => {
55+
return expectMissingWebCrypto(() => rsaPrivateKey.sign(Buffer.from('test')))
56+
})
57+
58+
it('should error for verify RSA public key when web crypto is missing', () => {
59+
return expectMissingWebCrypto(() => rsaPrivateKey.public.verify(Buffer.from('test'), Buffer.from('test')))
60+
})
61+
})

0 commit comments

Comments
 (0)