Skip to content

Commit

Permalink
💥 Pad values following RFC5054
Browse files Browse the repository at this point in the history
Fixes #12

Migration guide:

While verifiers generated with an earlier version of this library will continue to work, the client and the server must either both be using the padding, or both not.

The recommended upgrade path is to have both versions running on your server, which gives you time to upgrade all the clients. This can be accomplished by depending on this library two times:

```js
{
  // ...
  "dependencies": {
    // ...
    "secure-remote-password": "^0.4.0",
    "secure-remote-password-pre-rfc5054": "LinusU/secure-remote-password#pre-rfc5054"
  }
}
```

You will then have to implement another version of your endpoint, which uses the newer version.

```js
const loginCtrl1 = require('./controller/login-v1')
const loginCtrl2 = require('./controller/login-v2')

app.post('/v1/login-sessions', loginCtrl1.initiate)
app.post('/v1/login-sessions/:sessionId/finalize', loginCtrl1.finalize)

app.post('/v2/login-sessions', loginCtrl2.initiate)
app.post('/v2/login-sessions/:sessionId/finalize', loginCtrl2.finalize)
```

The `login-v1` controller should import the library as such:

```js
require('secure-remote-password-pre-rfc5054/server')
```

The `login-v2` controller should import the library as usual:

```js
require('secure-remote-password/server')
```
  • Loading branch information
LinusU committed May 23, 2018
1 parent 4229c17 commit 73e5f73
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 51 deletions.
3 changes: 3 additions & 0 deletions browser/sha256.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ function concat (buffers) {
return combined.buffer
}

/**
* @param {(string | SRPInteger)[]} args
*/
module.exports = function sha256 (...args) {
const buffer = concat(args.map((arg) => {
if (arg instanceof SRPInteger) {
Expand Down
55 changes: 28 additions & 27 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ const params = require('./lib/params')
const SRPInteger = require('./lib/srp-integer')

exports.generateSalt = function () {
// s User's salt
// s User's salt
const s = SRPInteger.randomInteger(params.hashOutputBytes)

return s.toHex()
}

exports.derivePrivateKey = function (salt, username, password) {
// H() One-way hash function
// H() One-way hash function
const { H } = params

// s User's salt
// I Username
// p Cleartext Password
// s User's salt
// I Username
// p Cleartext Password
const s = SRPInteger.fromHex(salt)
const I = String(username)
const p = String(password)
Expand All @@ -28,11 +28,11 @@ exports.derivePrivateKey = function (salt, username, password) {
}

exports.deriveVerifier = function (privateKey) {
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
const { N, g } = params

// x Private key (derived from p and s)
// x Private key (derived from p and s)
const x = SRPInteger.fromHex(privateKey)

// v = g^x (computes password verifier)
Expand All @@ -42,8 +42,8 @@ exports.deriveVerifier = function (privateKey) {
}

exports.generateEphemeral = function () {
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
const { N, g } = params

// A = g^a (a = random number)
Expand All @@ -57,17 +57,18 @@ exports.generateEphemeral = function () {
}

exports.deriveSession = function (clientSecretEphemeral, serverPublicEphemeral, salt, username, privateKey) {
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
// H() One-way hash function
const { N, g, k, H } = params

// a Secret ephemeral values
// B Public ephemeral values
// s User's salt
// I Username
// x Private key (derived from p and s)
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
// H() One-way hash function
// PAD() Pad the number to have the same number of bytes as N
const { N, g, k, H, PAD } = params

// a Secret ephemeral values
// B Public ephemeral values
// s User's salt
// I Username
// x Private key (derived from p and s)
const a = SRPInteger.fromHex(clientSecretEphemeral)
const B = SRPInteger.fromHex(serverPublicEphemeral)
const s = SRPInteger.fromHex(salt)
Expand All @@ -83,8 +84,8 @@ exports.deriveSession = function (clientSecretEphemeral, serverPublicEphemeral,
throw new Error('The server sent an invalid public ephemeral')
}

// u = H(A, B)
const u = H(A, B)
// u = H(PAD(A), PAD(B))
const u = H(PAD(A), PAD(B))

// S = (B - kg^x) ^ (a + ux)
const S = B.subtract(k.multiply(g.modPow(x, N))).modPow(a.add(u.multiply(x)), N)
Expand All @@ -102,12 +103,12 @@ exports.deriveSession = function (clientSecretEphemeral, serverPublicEphemeral,
}

exports.verifySession = function (clientPublicEphemeral, clientSession, serverSessionProof) {
// H() One-way hash function
// H() One-way hash function
const { H } = params

// A Public ephemeral values
// M Proof of K
// K Shared, strong session key
// A Public ephemeral values
// M Proof of K
// K Shared, strong session key
const A = SRPInteger.fromHex(clientPublicEphemeral)
const M = SRPInteger.fromHex(clientSession.proof)
const K = SRPInteger.fromHex(clientSession.key)
Expand Down
22 changes: 16 additions & 6 deletions lib/params.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,26 @@ const input = {
`,
generatorModulo: '02',
hashFunction: 'sha256',
hashOutputBytes: (256 / 8)
hashOutputBytes: (256 / 8),
paddedLength: 512
}

// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
// H() One-way hash function
/**
* @param {SRPInteger} integer
*/
function pad (integer) {
return integer.pad(input.paddedLength)
}

// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
// H() One-way hash function
// PAD() Pad the number to have the same number of bytes as N
exports.N = SRPInteger.fromHex(input.largeSafePrime.replace(/\s+/g, ''))
exports.g = SRPInteger.fromHex(input.generatorModulo.replace(/\s+/g, ''))
exports.k = sha256(exports.N, exports.g)
exports.k = sha256(exports.N, pad(exports.g))
exports.H = sha256
exports.PAD = pad

exports.hashOutputBytes = input.hashOutputBytes
3 changes: 3 additions & 0 deletions lib/sha256.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const crypto = require('crypto')

const SRPInteger = require('./srp-integer')

/**
* @param {(string | SRPInteger)[]} args
*/
module.exports = function sha256 (...args) {
const h = crypto.createHash('sha256')

Expand Down
8 changes: 8 additions & 0 deletions lib/srp-integer.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ class SRPInteger {
return new SRPInteger(this[kBigInteger].xor(val[kBigInteger]), this[kHexLength])
}

pad (hexLength) {
if (hexLength < this[kHexLength]) {
throw new Error('Cannot pad to a shorter length')
}

return new SRPInteger(this[kBigInteger], hexLength)
}

inspect () {
const hex = this[kBigInteger].toString(16)

Expand Down
37 changes: 19 additions & 18 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ const params = require('./lib/params')
const SRPInteger = require('./lib/srp-integer')

exports.generateEphemeral = function (verifier) {
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
const { N, g, k } = params

// v Password verifier
// v Password verifier
const v = SRPInteger.fromHex(verifier)

// B = kv + g^b (b = random number)
Expand All @@ -23,18 +23,19 @@ exports.generateEphemeral = function (verifier) {
}

exports.deriveSession = function (serverSecretEphemeral, clientPublicEphemeral, salt, username, verifier, clientSessionProof) {
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
// H() One-way hash function
const { N, g, k, H } = params

// b Secret ephemeral values
// A Public ephemeral values
// s User's salt
// p Cleartext Password
// I Username
// v Password verifier
// N A large safe prime (N = 2q+1, where q is prime)
// g A generator modulo N
// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6)
// H() One-way hash function
// PAD() Pad the number to have the same number of bytes as N
const { N, g, k, H, PAD } = params

// b Secret ephemeral values
// A Public ephemeral values
// s User's salt
// p Cleartext Password
// I Username
// v Password verifier
const b = SRPInteger.fromHex(serverSecretEphemeral)
const A = SRPInteger.fromHex(clientPublicEphemeral)
const s = SRPInteger.fromHex(salt)
Expand All @@ -50,8 +51,8 @@ exports.deriveSession = function (serverSecretEphemeral, clientPublicEphemeral,
throw new Error('The client sent an invalid public ephemeral')
}

// u = H(A, B)
const u = H(A, B)
// u = H(PAD(A), PAD(B))
const u = H(PAD(A), PAD(B))

// S = (Av^u) ^ b (computes session key)
const S = A.multiply(v.modPow(u, N)).modPow(b, N)
Expand Down

0 comments on commit 73e5f73

Please sign in to comment.