Skip to content

Commit

Permalink
feature: add Signature class
Browse files Browse the repository at this point in the history
  • Loading branch information
alessiofrittoli committed Dec 14, 2024
1 parent dbea5b7 commit a03cfe7
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 3 deletions.
3 changes: 3 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const config = [
...tseslintReccommended,
{ files: [ 'src/**/*.{js,mjs,cjs,ts}' ] },
{ ignores: [ 'dist' ] },
{ rules: {
'@typescript-eslint/no-namespace': 'off',
} },
]

export default config
20 changes: 18 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,28 @@
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./types": {
"types": "./dist/types.d.ts",
"import": "./dist/types.mjs",
"require": "./dist/types.js"
}
},
"sideEffects": false,
"scripts": {
"build": "eslint && pnpm test:ci && pnpm test:ci:jsdom && tsup",
"build": "eslint && pnpm test:ci && tsup",
"lint": "eslint",
"test": "jest --watchAll --verbose",
"test:jsdom": "JSDOM=true pnpm test",
"test:ci": "jest --ci --verbose",
"test:ci:jsdom": "JSDOM=true pnpm test:ci",
"test:jest": "pnpm test jest.test.ts",
"test:jest:jsdom": "JSDOM=true pnpm test:jest"
"test:signature": "pnpm test signature.test.ts",
"test:verify": "pnpm test verify.test.ts"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
Expand All @@ -54,5 +66,9 @@
"tsup": "^8.3.5",
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.0"
},
"dependencies": {
"@alessiofrittoli/crypto-algorithm": "^1.2.0",
"@alessiofrittoli/crypto-key": "^1.0.0"
}
}
25 changes: 25 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

163 changes: 162 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,162 @@
//
import crypto from 'crypto'
import Hmac from '@alessiofrittoli/crypto-key/Hmac'
import Algorithm from '@alessiofrittoli/crypto-algorithm'
import type Sign from './types'


class Signature
{
private static Algorithm: Sign.AlgorithmJwkName = 'HS256'
private static HashDigest: Sign.Hash = 'SHA-256'


/**
* Sincronously create a signature with the given data.
*
* @param data The data to sign.
* @param key The private key used for HMAC or the PEM private key for RSA, ECDSA and RSASSA-PSS signing algorithms.
* @param algorithm ( Optional ) The Jwk Algorithm name to use. Default: `HS256`.
* @returns The signature Buffer. Throws a new Exception on failure.
*/
static sign(
data : crypto.BinaryLike,
key : Sign.PrivateKey,
algorithm : Sign.AlgorithmJwkName = Signature.Algorithm,
): Buffer
{
if ( ! data ) {
throw new Error( 'No data to sign has been provided.' )
}
if ( ! key ) {
throw new Error( 'No Private Key has been provided.' )
}

const digest = Signature.jwkAlgToHash( algorithm ) || Signature.HashDigest

/** HMAC SHA signing algorithm */
if ( algorithm.startsWith( 'HS' ) ) {

let secret = key as Sign.PrivateKey<'HMAC'>
secret = secret instanceof CryptoKey ? crypto.KeyObject.from( secret ) : secret

return (
Hmac.digest( data, secret, digest )
)
}


if ( algorithm === 'EdDSA' ) {

let secret = key as Sign.PrivateKey<'EdDSA'>
secret = secret instanceof CryptoKey ? crypto.KeyObject.from( secret ) : secret
const _data = typeof data !== 'string' ? data : Buffer.from( data )

return crypto.sign( null, _data, secret )

}


/** RSASSA/RSASSA-PSS/ECDSA/DSA SHA signing algorithm */
let secret = key as Sign.PrivateKey<'RSA-PSS' | 'RSASSA-PKCS1-v1_5' | 'ECDSA' | 'DSA'>
secret = secret instanceof CryptoKey ? crypto.KeyObject.from( secret ) : secret
const Sign = crypto.createSign( digest )

Sign.write( data )
Sign.end()

return Sign.sign( secret )

}


/**
* Sincronously verify a signature.
*
* @param signature The signature buffer.
* @param data The signed data.
* @param key The public key used for HMAC, or RSA, ECDSA and RSASSA-PSS signing verifications.
* @param algorithm ( Optional ) The Jwk Algorithm name to use. Default: `HS256`.
* @returns `true` if signature is valid. Throws a new Exception on failure.
*/
static isValid(
signature : Buffer,
data : crypto.BinaryLike,
key : Sign.PublicKey,
algorithm : Sign.AlgorithmJwkName = Signature.Algorithm,
): true
{
if ( ! signature ) {
throw new Error( 'No signature provided.' )
}
if ( ! data ) {
throw new Error( 'The signed data is needed for integrity controls.' )
}
if ( ! key ) {
throw new Error( 'No Private Key has been provided.' )
}

const digest = Signature.jwkAlgToHash( algorithm ) || Signature.HashDigest

/** HMAC SHA signing algorithm */
if ( algorithm.startsWith( 'HS' ) ) {

let secret = key as Sign.PublicKey<'HMAC'>
secret = secret instanceof CryptoKey ? crypto.KeyObject.from( secret ) : secret
const isValid = Hmac.isValid( signature, data, secret, digest )

if ( ! isValid ) {
throw new Error( 'Invalid signature.' )
}

return true

}

if ( algorithm === 'EdDSA' ) {

let secret = key as Sign.PublicKey<'EdDSA'>
secret = secret instanceof CryptoKey ? crypto.KeyObject.from( secret ) : secret
const _data = typeof data !== 'string' ? data : Buffer.from( data )
const isValid = crypto.verify( null, _data, secret, signature )

if ( ! isValid ) {
throw new Error( 'Invalid signature.' )
}

return true

}

/** RSASSA/RSASSA-PSS/ECDSA/DSA SHA signing algorithm */
let secret = key as Sign.PublicKey<'RSA-PSS' | 'RSASSA-PKCS1-v1_5' | 'ECDSA' | 'DSA'>
secret = secret instanceof CryptoKey ? crypto.KeyObject.from( secret ) : secret
const Verify = crypto.createVerify( digest )

Verify.write( data )
Verify.end()

const isValid = Verify.verify( secret, signature )

if ( ! isValid ) {
throw new Error( 'Invalid signature.' )
}

return true

}


/**
* Get the Algorithm digest hash name.
*
* @param jwkAlg The Algorithm.
* @returns The corresponding Algorithm digest hash name.
*/
private static jwkAlgToHash( jwkAlg: Sign.AlgorithmJwkName )
{
return Algorithm.by( { jwkAlg } )?.hash
}
}


export default Signature
39 changes: 39 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type crypto from 'crypto'
import type Algo from '@alessiofrittoli/crypto-algorithm/types'

namespace Sign
{
/**
* Signature algorithm parameter.
*
* @link https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
*/
export type AlgorithmJwkName = Algo.JwkName
export type Hash = Algo.Hash

/**
* The private key used for HMAC or the PEM private key for RSA, ECDSA and RSASSA-PSS signing algorithms.
*
*/
export type PrivateKey<
TAlg extends Algo.Name = Algo.Name
> = (
TAlg extends `HMAC` ? ( crypto.BinaryLike | crypto.KeyObject | CryptoKey )
: ( crypto.KeyLike | crypto.SignKeyObjectInput | crypto.SignPrivateKeyInput | CryptoKey )
)


/**
* The public key used for HMAC, or RSA, ECDSA and RSASSA-PSS signing verifications.
*
*/
export type PublicKey<
TAlg extends Algo.Name = Algo.Name
> = (
TAlg extends `HMAC` ? ( crypto.BinaryLike | crypto.KeyObject | CryptoKey )
: ( crypto.KeyLike | crypto.VerifyKeyObjectInput | crypto.VerifyPublicKeyInput | crypto.VerifyJsonWebKeyInput | CryptoKey )
)
}


export default Sign

0 comments on commit a03cfe7

Please sign in to comment.