Skip to content

Commit

Permalink
feat: add EC P-256K JWK and ES256K sign/verify support
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Mar 27, 2019
1 parent c5c985b commit 2e33e1c
Show file tree
Hide file tree
Showing 15 changed files with 62 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Legend:
| -- | -- | -- |
| RSASSA-PKCS1-v1_5 || RS256, RS384, RS512 |
| RSASSA-PSS || PS256, PS384, PS512 |
| ECDSA || ES256, ES384, ES512 |
| ECDSA || ES256, ES256K, ES384, ES512 |
| HMAC with SHA-2 || HS256, HS384, HS512 |

| JWE Key Management Algorithms | Supported ||
Expand Down
1 change: 0 additions & 1 deletion lib/help/asn1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ const RSAPublicKey = asn1.define('RSAPublicKey', require('./rsa_public_key'))
types.set('RSAPublicKey', RSAPublicKey)

module.exports = types
module.exports.BN = asn1.bignum
1 change: 1 addition & 0 deletions lib/help/ecdsa_signatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const getParamSize = keySize => ((keySize / 8) | 0) + (keySize % 8 === 0 ? 0 : 1

const paramBytesForAlg = {
ES256: getParamSize(256),
ES256K: getParamSize(256),
ES384: getParamSize(384),
ES512: getParamSize(521)
}
Expand Down
10 changes: 9 additions & 1 deletion lib/help/key_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@ const base64url = require('./base64url')
const errors = require('../errors')
const asn1 = require('./asn1')

const EC_CURVES = new Set(['P-256', 'P-384', 'P-521'])
const EC_CURVES = new Set([
'P-256',
'P-256K',
'P-384',
'P-521'
])

const oidHexToCurve = new Map([
['06082a8648ce3d030107', 'P-256'],
['06052b8104000a', 'P-256K'],
['06052b81040022', 'P-384'],
['06052b81040023', 'P-521']
])
const EC_KEY_OID = '1.2.840.10045.2.1'.split('.')
const crvToOid = new Map([
['P-256', '1.2.840.10045.3.1.7'.split('.')],
['P-256K', '1.3.132.0.10'.split('.')],
['P-384', '1.3.132.0.34'.split('.')],
['P-521', '1.3.132.0.35'.split('.')]
])
const crvToOidBuf = new Map([
['P-256', Buffer.from('06082a8648ce3d030107', 'hex')],
['P-256K', Buffer.from('06052b8104000a', 'hex')],
['P-384', Buffer.from('06052b81040022', 'hex')],
['P-521', Buffer.from('06052b81040023', 'hex')]
])
Expand Down
1 change: 1 addition & 0 deletions lib/help/node_alg.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = (alg) => {
case 'PS256':
case 'HS256':
case 'ES256':
case 'ES256K':
return 'sha256'
case 'RS384':
case 'PS384':
Expand Down
2 changes: 1 addition & 1 deletion lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface KeyParameters {
use?: use
kid?: string
}
type curve = 'P-256' | 'P-384' | 'P-521'
type curve = 'P-256' | 'P-256K' | 'P-384' | 'P-521'
type keyType = 'RSA' | 'EC' | 'oct'
type keyOperation = 'encrypt' | 'decrypt' | 'sign' | 'verify' | 'wrapKey' | 'unwrapKey'

Expand Down
2 changes: 2 additions & 0 deletions lib/jwa/ecdh/derive.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const crvToCurve = (crv) => {
switch (crv) {
case 'P-256':
return 'prime256v1'
case 'P-256K':
return 'secp256k1'
case 'P-384':
return 'secp384r1'
case 'P-521':
Expand Down
2 changes: 1 addition & 1 deletion lib/jwa/ecdsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const verify = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature)
}

module.exports = (JWA) => {
['ES256', 'ES384', 'ES512'].forEach((jwaAlg) => {
['ES256', 'ES384', 'ES512', 'ES256K'].forEach((jwaAlg) => {
const nodeAlg = resolveNodeAlg(jwaAlg)

assert(!JWA.sign.has(jwaAlg), `sign alg ${jwaAlg} already registered`)
Expand Down
8 changes: 8 additions & 0 deletions lib/jwk/key/ec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const crvToDSA = (crv) => {
switch (crv) {
case 'P-256':
return 'ES256'
case 'P-256K':
return 'ES256K'
case 'P-384':
return 'ES384'
case 'P-521':
Expand Down Expand Up @@ -93,12 +95,18 @@ class ECKey extends Key {
}

static async generate (crv = 'P-256', privat = true) {
if (crv === 'P-256K') {
crv = 'secp256k1'
}
const { privateKey, publicKey } = await generateKeyPair('ec', { namedCurve: crv })

return privat ? privateKey : publicKey
}

static generateSync (crv = 'P-256', privat = true) {
if (crv === 'P-256K') {
crv = 'secp256k1'
}
const { privateKey, publicKey } = generateKeyPairSync('ec', { namedCurve: crv })

return privat ? privateKey : publicKey
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/P-256K.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgxTAmXNRL8ksBlr+F3yXD
rUdRDn1gyIvY/PC2e/iUK7ehRANCAARVFouq0yOD8lFoPORt+K3vOieQ4YNnjapt
nKWOGqyDdeaoE8aEQH9IScXKYVYNTRPa9F7/hx2clSCcRG6OkgLE
-----END PRIVATE KEY-----
4 changes: 4 additions & 0 deletions test/fixtures/P-256K.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEVRaLqtMjg/JRaDzkbfit7zonkOGDZ42q
bZyljhqsg3XmqBPGhEB/SEnFymFWDU0T2vRe/4cdnJUgnERujpICxA==
-----END PUBLIC KEY-----
12 changes: 12 additions & 0 deletions test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ module.exports.JWK = {
d: '_i_1Ac5oVmbBxGvEvOEFHMpzMXKZi8voUx8I3Gl6IxY'
},

'P-256K': {
kty: 'EC',
crv: 'P-256',
x: 'VRaLqtMjg_JRaDzkbfit7zonkOGDZ42qbZyljhqsg3U',
y: '5qgTxoRAf0hJxcphVg1NE9r0Xv-HHZyVIJxEbo6SAsQ',
d: 'xTAmXNRL8ksBlr-F3yXDrUdRDn1gyIvY_PC2e_iUK7c'
},

'P-384': {
kty: 'EC',
crv: 'P-384',
Expand Down Expand Up @@ -56,6 +64,10 @@ module.exports.PEM = {
private: readFileSync(join(__dirname, 'P-256.key')),
public: readFileSync(join(__dirname, 'P-256.pem'))
},
'P-256K': {
private: readFileSync(join(__dirname, 'P-256K.key')),
public: readFileSync(join(__dirname, 'P-256K.pem'))
},
'P-384': {
private: readFileSync(join(__dirname, 'P-384.key')),
public: readFileSync(join(__dirname, 'P-384.pem'))
Expand Down
6 changes: 3 additions & 3 deletions test/help/key_utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ test('jwkToPem only works for EC and RSA', t => {
}, { instanceOf: errors.JOSENotSupported, message: 'unsupported key type: OKP' })
})

test('jwkToPem only does rfc7518 EC', t => {
test('jwkToPem only handles known curves', t => {
t.throws(() => {
jwkToPem({ kty: 'EC', crv: 'P-256K' })
}, { instanceOf: errors.JOSENotSupported, message: 'unsupported EC key curve: P-256K' })
jwkToPem({ kty: 'EC', crv: 'foo' })
}, { instanceOf: errors.JOSENotSupported, message: 'unsupported EC key curve: foo' })
})

test('RSA Public key', t => {
Expand Down
8 changes: 7 additions & 1 deletion test/jwk/ec.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@ test(`EC key .algorithms invalid operation`, t => {

Object.entries({
'P-256': [256, 'rDd6H6t9-nJUoz72nTpz8tInvypVWhE2iQoPznj8ZY8'],
'P-256K': [256, 'zZYrH69YCAAihM7ZCoRj90VI55H5MmQscSpf-JuUS50'],
'P-384': [384, '5gebayAhpztJCs4Pxo-z1hhsN0upoyG2NAoKpiiH2b0'],
'P-521': [512, 'BQtkbSY3xgN4M2ZP3IHMLG7-Rp1L29teCMfNqgJHtTY']
}).forEach(([crv, [len, kid]]) => {
const alg = `ES${len}`
let alg
if (crv === 'P-256K') {
alg = 'ES256K'
} else {
alg = `ES${len}`
}

// private
;(() => {
Expand Down
7 changes: 7 additions & 0 deletions test/jwk/generate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ const { JWK: { generate, generateSync }, errors } = require('../..')
['EC', 'P-256', { use: 'enc', alg: 'ECDH-ES' }],
['EC', 'P-256', { alg: 'ES256' }],
['EC', 'P-256', { alg: 'ECDH-ES' }],
['EC', 'P-256K'],
['EC', 'P-256K', { use: 'sig' }],
['EC', 'P-256K', { use: 'enc' }],
['EC', 'P-256K', { use: 'sig', alg: 'ES256K' }],
['EC', 'P-256K', { use: 'enc', alg: 'ECDH-ES' }],
['EC', 'P-256K', { alg: 'ES256K' }],
['EC', 'P-256K', { alg: 'ECDH-ES' }],
['EC', 'P-384'],
['EC', 'P-384', { use: 'sig' }],
['EC', 'P-384', { use: 'enc' }],
Expand Down

4 comments on commit 2e33e1c

@Hirama
Copy link

@Hirama Hirama commented on 2e33e1c May 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, why you removed ES256K from the library?

@panva
Copy link
Owner Author

@panva panva commented on 2e33e1c May 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the support while the library was still not until semver stable API contract in commit e21fea1. I did so because the WG in charge of registering the algorithm and curve name decided not to use the already common "P-256K" curve name for various reasons. (see the full thread).

I plan to re-introduce the EC curve and alg as supported once the WG registers the final names in the IANA registry.

@panva
Copy link
Owner Author

@panva panva commented on 2e33e1c May 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to sign up to the mailing list and responding to the thread if you have reasons to believe their decision was wrong, especially if the name is used in blockchain technologies, which was something i couldn't tell myself

@panva
Copy link
Owner Author

@panva panva commented on 2e33e1c Jul 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ES256K is now back in the library (>=1.4.0) after corresponding draft was updated.

Please sign in to comment.