Files
2024-03-07 12:48:47 +01:00

143 lines
4.0 KiB
TypeScript

import { encode as base64url } from '../../runtime/base64url.ts'
import sign from '../../runtime/sign.ts'
import isDisjoint from '../../lib/is_disjoint.ts'
import { JWSInvalid } from '../../util/errors.ts'
import { encoder, decoder, concat } from '../../lib/buffer_utils.ts'
import type { KeyLike, FlattenedJWS, JWSHeaderParameters, SignOptions } from '../../types.d.ts'
import checkKeyType from '../../lib/check_key_type.ts'
import validateCrit from '../../lib/validate_crit.ts'
/**
* The FlattenedSign class is used to build and sign Flattened JWS objects.
*
*/
export class FlattenedSign {
private _payload: Uint8Array
private _protectedHeader!: JWSHeaderParameters
private _unprotectedHeader!: JWSHeaderParameters
/** @param payload Binary representation of the payload to sign. */
constructor(payload: Uint8Array) {
if (!(payload instanceof Uint8Array)) {
throw new TypeError('payload must be an instance of Uint8Array')
}
this._payload = payload
}
/**
* Sets the JWS Protected Header on the FlattenedSign object.
*
* @param protectedHeader JWS Protected Header.
*/
setProtectedHeader(protectedHeader: JWSHeaderParameters) {
if (this._protectedHeader) {
throw new TypeError('setProtectedHeader can only be called once')
}
this._protectedHeader = protectedHeader
return this
}
/**
* Sets the JWS Unprotected Header on the FlattenedSign object.
*
* @param unprotectedHeader JWS Unprotected Header.
*/
setUnprotectedHeader(unprotectedHeader: JWSHeaderParameters) {
if (this._unprotectedHeader) {
throw new TypeError('setUnprotectedHeader can only be called once')
}
this._unprotectedHeader = unprotectedHeader
return this
}
/**
* Signs and resolves the value of the Flattened JWS object.
*
* @param key Private Key or Secret to sign the JWS with. See
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
* @param options JWS Sign options.
*/
async sign(key: KeyLike | Uint8Array, options?: SignOptions): Promise<FlattenedJWS> {
if (!this._protectedHeader && !this._unprotectedHeader) {
throw new JWSInvalid(
'either setProtectedHeader or setUnprotectedHeader must be called before #sign()',
)
}
if (!isDisjoint(this._protectedHeader, this._unprotectedHeader)) {
throw new JWSInvalid(
'JWS Protected and JWS Unprotected Header Parameter names must be disjoint',
)
}
const joseHeader: JWSHeaderParameters = {
...this._protectedHeader,
...this._unprotectedHeader,
}
const extensions = validateCrit(
JWSInvalid,
new Map([['b64', true]]),
options?.crit,
this._protectedHeader,
joseHeader,
)
let b64: boolean = true
if (extensions.has('b64')) {
b64 = this._protectedHeader.b64!
if (typeof b64 !== 'boolean') {
throw new JWSInvalid(
'The "b64" (base64url-encode payload) Header Parameter must be a boolean',
)
}
}
const { alg } = joseHeader
if (typeof alg !== 'string' || !alg) {
throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid')
}
checkKeyType(alg, key, 'sign')
let payload = this._payload
if (b64) {
payload = encoder.encode(base64url(payload))
}
let protectedHeader: Uint8Array
if (this._protectedHeader) {
protectedHeader = encoder.encode(base64url(JSON.stringify(this._protectedHeader)))
} else {
protectedHeader = encoder.encode('')
}
const data = concat(protectedHeader, encoder.encode('.'), payload)
const signature = await sign(alg, key, data)
const jws: FlattenedJWS = {
signature: base64url(signature),
payload: '',
}
if (b64) {
jws.payload = decoder.decode(payload)
}
if (this._unprotectedHeader) {
jws.header = this._unprotectedHeader
}
if (this._protectedHeader) {
jws.protected = decoder.decode(protectedHeader)
}
return jws
}
}