Skip to content

Commit eb33042

Browse files
committed
feat: move hmac fns to own package
- define hmac related functions in separate module Ticket: CE-5010
1 parent 6bcb1c6 commit eb33042

File tree

15 files changed

+468
-0
lines changed

15 files changed

+468
-0
lines changed

modules/sdk-hmac/.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist/
2+
src/opensslbytes.ts

modules/sdk-hmac/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
.idea/
3+
dist/

modules/sdk-hmac/.mocharc.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require: 'ts-node/register'
2+
timeout: '60000'
3+
reporter: 'min'
4+
reporter-option:
5+
- 'cdn=true'
6+
- 'json=false'
7+
exit: true
8+
spec: ['test/**/*.ts']

modules/sdk-hmac/.npmignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
!dist/
2+
.idea/
3+
.prettierrc.yml
4+
tsconfig.json
5+
src/
6+
test/
7+
scripts/
8+
.nyc_output
9+
CODEOWNERS
10+
node_modules/
11+
.prettierignore
12+
.mocharc.js

modules/sdk-hmac/.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.nyc_output/
2+
dist/

modules/sdk-hmac/.prettierrc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
printWidth: 120
2+
singleQuote: true
3+
trailingComma: 'es5'

modules/sdk-hmac/CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Change Log
2+
3+
All notable changes to this project will be documented in this file.
4+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5+
6+
# 2.0.0 (2024-08-20)
7+
8+
### Features
9+
10+
- move opensslbytes to own package ([e23c562](https://github.com/BitGo/BitGoJS/commit/e23c5627957916055e68329541dd1eb775704ca5))
11+
12+
### BREAKING CHANGES
13+
14+
- clients using challenge
15+
generation & TSS Recovery functions must now
16+
install @bitgo/sdk-opensslbytes separately &
17+
provide the openSSLBytes WASM themselves.
18+
19+
Ticket: CE-4329

modules/sdk-hmac/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# @bitgo/sdk-opensslbytes
2+
3+
Isolated module for users of the BitGo SDK that need to generate range proofs or recover funds on a TSS wallet.
4+
5+
## Installation
6+
7+
```shell
8+
npm i @bitgo/sdk-api @bitgo/sdk-lib-mpc @bitgo/sdk-opensslbytes
9+
```
10+
11+
Import the `openSSLBytes` from this module & pass to related functions that expect it:
12+
13+
```javascript
14+
import { loadWebAssembly } from '@bitgo/sdk-opensslbytes';
15+
import { EcdsaRangeProof } from '@bitgo-beta/sdk-lib-mpc';
16+
17+
const openSSLBytes = loadWebAssembly().buffer;
18+
19+
const nTilde = await EcdsaRangeProof.generateNtilde(openSSLBytes, 3072);
20+
```

modules/sdk-hmac/package.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "@bitgo/sdk-hmac",
3+
"version": "1.0.0",
4+
"description": "Split package for WASM code needed by sdk-lib-mpc",
5+
"main": "./dist/src/index.js",
6+
"types": "./dist/src/index.d.ts",
7+
"scripts": {
8+
"build": "yarn tsc --build --incremental --verbose .",
9+
"fmt": "prettier --write .",
10+
"check-fmt": "prettier --check .",
11+
"clean": "rm -r ./dist",
12+
"lint": "eslint --quiet .",
13+
"test": "npm run coverage",
14+
"coverage": "nyc -- npm run unit-test",
15+
"unit-test": "mocha",
16+
"prepare": "npm run build"
17+
},
18+
"dependencies": {
19+
"@bitgo/sjcl": "^1.0.1"
20+
},
21+
"devDependencies": {
22+
"should": "^13.1.3"
23+
},
24+
"author": "BitGo SDK Team <[email protected]>",
25+
"license": "MIT",
26+
"repository": {
27+
"type": "git",
28+
"url": "https://github.com/BitGo/BitGoJS.git",
29+
"directory": "modules/sdk-opensslbytes"
30+
},
31+
"files": [
32+
"dist/src"
33+
],
34+
"lint-staged": {
35+
"*.{js,ts}": [
36+
"yarn prettier --write",
37+
"yarn eslint --fix"
38+
]
39+
},
40+
"publishConfig": {
41+
"access": "public"
42+
},
43+
"nyc": {
44+
"extension": [
45+
".ts"
46+
]
47+
}
48+
}

modules/sdk-hmac/src/hmac.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { createHmac } from 'crypto';
2+
import * as urlLib from 'url';
3+
import * as sjcl from '@bitgo/sjcl';
4+
import {
5+
CalculateHmacSubjectOptions,
6+
CalculateRequestHeadersOptions,
7+
CalculateRequestHmacOptions,
8+
RequestHeaders,
9+
VerifyResponseInfo,
10+
VerifyResponseOptions,
11+
} from './types';
12+
13+
/**
14+
* Calculate the HMAC for the given key and message
15+
* @param key {String} - the key to use for the HMAC
16+
* @param message {String} - the actual message to HMAC
17+
* @returns {*} - the result of the HMAC operation
18+
*/
19+
export function calculateHMAC(key: string, message: string): string {
20+
return createHmac('sha256', key).update(message).digest('hex');
21+
}
22+
23+
/**
24+
* Calculate the subject string that is to be HMAC'ed for a HTTP request or response
25+
* @param urlPath request url, including query params
26+
* @param text request body text
27+
* @param timestamp request timestamp from `Date.now()`
28+
* @param statusCode Only set for HTTP responses, leave blank for requests
29+
* @param method request method
30+
* @returns {string}
31+
*/
32+
export function calculateHMACSubject({
33+
urlPath,
34+
text,
35+
timestamp,
36+
statusCode,
37+
method,
38+
authVersion,
39+
}: CalculateHmacSubjectOptions): string {
40+
const urlDetails = urlLib.parse(urlPath);
41+
const queryPath = urlDetails.query && urlDetails.query.length > 0 ? urlDetails.path : urlDetails.pathname;
42+
if (statusCode !== undefined && isFinite(statusCode) && Number.isInteger(statusCode)) {
43+
if (authVersion === 3) {
44+
return [method.toUpperCase(), timestamp, queryPath, statusCode, text].join('|');
45+
}
46+
return [timestamp, queryPath, statusCode, text].join('|');
47+
}
48+
if (authVersion === 3) {
49+
return [method.toUpperCase(), timestamp, '3.0', queryPath, text].join('|');
50+
}
51+
return [timestamp, queryPath, text].join('|');
52+
}
53+
54+
/**
55+
* Calculate the HMAC for an HTTP request
56+
*/
57+
export function calculateRequestHMAC({
58+
url: urlPath,
59+
text,
60+
timestamp,
61+
token,
62+
method,
63+
authVersion,
64+
}: CalculateRequestHmacOptions): string {
65+
const signatureSubject = calculateHMACSubject({ urlPath, text, timestamp, method, authVersion });
66+
67+
// calculate the HMAC
68+
return calculateHMAC(token, signatureSubject);
69+
}
70+
71+
/**
72+
* Calculate request headers with HMAC
73+
*/
74+
export function calculateRequestHeaders({
75+
url,
76+
text,
77+
token,
78+
method,
79+
authVersion,
80+
}: CalculateRequestHeadersOptions): RequestHeaders {
81+
const timestamp = Date.now();
82+
const hmac = calculateRequestHMAC({ url, text, timestamp, token, method, authVersion });
83+
84+
// calculate the SHA256 hash of the token
85+
const hashDigest = sjcl.hash.sha256.hash(token);
86+
const tokenHash = sjcl.codec.hex.fromBits(hashDigest);
87+
return {
88+
hmac,
89+
timestamp,
90+
tokenHash,
91+
};
92+
}
93+
94+
/**
95+
* Verify the HMAC for an HTTP response
96+
*/
97+
export function verifyResponse({
98+
url: urlPath,
99+
statusCode,
100+
text,
101+
timestamp,
102+
token,
103+
hmac,
104+
method,
105+
authVersion,
106+
}: VerifyResponseOptions): VerifyResponseInfo {
107+
const signatureSubject = calculateHMACSubject({
108+
urlPath,
109+
text,
110+
timestamp,
111+
statusCode,
112+
method,
113+
authVersion,
114+
});
115+
116+
// calculate the HMAC
117+
const expectedHmac = calculateHMAC(token, signatureSubject);
118+
119+
// determine if the response is still within the validity window (5 minute window)
120+
const now = Date.now();
121+
const isInResponseValidityWindow = timestamp >= now - 1000 * 60 * 5 && timestamp <= now;
122+
123+
// verify the HMAC and timestamp
124+
return {
125+
isValid: expectedHmac === hmac,
126+
expectedHmac,
127+
signatureSubject,
128+
isInResponseValidityWindow,
129+
verificationTime: now,
130+
};
131+
}

0 commit comments

Comments
 (0)