-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Now you can omit PKCE code verifier/challenge params for authorization code flows. They will be generated automatically. Be aware the API of the createAuthorizationUrl method changed as a result. It now has a PKCE param
- Loading branch information
Showing
4 changed files
with
139 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { assertValidCodeVerifier, CodeChallengeMethod, createCodeChallenge, generateCodeVerifier } from '@sphereon/oid4vci-common'; | ||
|
||
import { PKCEOpts } from '../types'; | ||
|
||
export const createPKCEOpts = (pkce: PKCEOpts) => { | ||
if (pkce.disabled) { | ||
return pkce; | ||
} | ||
if (!pkce.codeChallengeMethod) { | ||
pkce.codeChallengeMethod = CodeChallengeMethod.S256; | ||
} | ||
if (!pkce.codeVerifier) { | ||
pkce.codeVerifier = generateCodeVerifier(); | ||
} | ||
assertValidCodeVerifier(pkce.codeVerifier); | ||
if (!pkce.codeChallenge) { | ||
pkce.codeChallenge = createCodeChallenge(pkce.codeVerifier, pkce.codeChallengeMethod); | ||
} | ||
return pkce; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { randomBytes } from '../functions'; | ||
|
||
import { UNIT_TEST_TIMEOUT } from './CredentialOfferUtil.spec'; | ||
|
||
describe('randomBytes should', () => { | ||
it( | ||
'generate random bytes of length 32', | ||
() => { | ||
const bytes = randomBytes(32); | ||
expect(bytes).toBeDefined(); | ||
expect(bytes.length).toEqual(32); | ||
}, | ||
UNIT_TEST_TIMEOUT, | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import SHA from 'sha.js'; | ||
import * as u8a from 'uint8arrays'; | ||
import { SupportedEncodings } from 'uint8arrays/to-string'; | ||
|
||
import { CodeChallengeMethod } from '../types'; | ||
|
||
import { randomBytes } from './randomBytes'; | ||
|
||
export const CODE_VERIFIER_DEFAULT_LENGTH = 128; | ||
export const NONCE_LENGTH = 32; | ||
|
||
export const generateRandomString = (length: number, encoding?: SupportedEncodings): string => { | ||
return u8a.toString(randomBytes(length), encoding).slice(0, length); | ||
}; | ||
|
||
export const generateNonce = (length?: number): string => { | ||
return generateRandomString(length ?? NONCE_LENGTH); | ||
}; | ||
export const generateCodeVerifier = (length?: number): string => { | ||
const codeVerifier = generateRandomString(length ?? CODE_VERIFIER_DEFAULT_LENGTH, 'base64url'); | ||
assertValidCodeVerifier(codeVerifier); | ||
return codeVerifier; | ||
}; | ||
|
||
export const createCodeChallenge = (codeVerifier: string, codeChallengeMethod?: CodeChallengeMethod): string => { | ||
if (codeChallengeMethod === CodeChallengeMethod.plain) { | ||
return codeVerifier; | ||
} else if (!codeChallengeMethod || codeChallengeMethod === CodeChallengeMethod.S256) { | ||
return u8a.toString(SHA('sha256').update(codeVerifier).digest(), 'base64url'); | ||
} else { | ||
// Just a precaution if a new method would be introduced | ||
throw Error(`code challenge method ${codeChallengeMethod} not implemented`); | ||
} | ||
}; | ||
|
||
export const assertValidCodeVerifier = (codeVerifier: string) => { | ||
const length = codeVerifier.length; | ||
if (length < 43) { | ||
throw Error(`code_verifier should have a minimum length of 43; see rfc7636`); | ||
} else if (length > 128) { | ||
throw Error(`code_verifier should have a maximum length of 128; see rfc7636`); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
'use strict'; | ||
|
||
// limit of Crypto.getRandomValues() | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues | ||
const MAX_BYTES = 65536; | ||
|
||
// Node supports requesting up to this number of bytes | ||
// https://github.com/nodejs/node/blob/master/lib/internal/crypto/random.js#L48 | ||
const MAX_UINT32 = 4294967295; | ||
|
||
function oldBrowser() { | ||
throw new Error('Secure random number generation is not supported by this browser.\nUse Chrome, Firefox or Internet Explorer 11'); | ||
} | ||
|
||
// eslint-disable-next-line no-undef | ||
const _global = typeof globalThis !== 'undefined' ? globalThis : global; | ||
|
||
let crypto = _global.crypto || _global.msCrypto; | ||
if (!crypto) { | ||
try { | ||
// eslint-disable-next-line no-undef | ||
crypto = require('crypto'); | ||
} catch (err) { | ||
throw Error('crypto module is not available'); | ||
} | ||
} | ||
|
||
if (crypto && crypto.getRandomValues) { | ||
// eslint-disable-next-line no-undef | ||
module.exports = randomBytes; | ||
} else { | ||
// eslint-disable-next-line no-undef | ||
module.exports = oldBrowser; | ||
} | ||
|
||
function randomBytes(size) { | ||
// phantomjs needs to throw | ||
if (size > MAX_UINT32) throw new Error('requested too many random bytes'); | ||
|
||
// eslint-disable-next-line no-undef | ||
const bytes = Buffer.allocUnsafe(size); | ||
|
||
if (size > 0) { | ||
// getRandomValues fails on IE if size == 0 | ||
if (size > MAX_BYTES) { | ||
// this is the max bytes crypto.getRandomValues | ||
// can do at once see https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues | ||
for (let generated = 0; generated < size; generated += MAX_BYTES) { | ||
// buffer.slice automatically checks if the end is past the end of | ||
// the buffer so we don't have to here | ||
crypto.getRandomValues(bytes.slice(generated, generated + MAX_BYTES)); | ||
} | ||
} else { | ||
crypto.getRandomValues(bytes); | ||
} | ||
} | ||
return Uint8Array.from(bytes); | ||
} | ||
|
||
// eslint-disable-next-line no-undef | ||
module.exports = { randomBytes }; |