Skip to content

Commit

Permalink
Merge pull request #11 from eBay/dev
Browse files Browse the repository at this point in the history
Merging 3.0.0 into main
  • Loading branch information
LokeshRishi authored Apr 21, 2023
2 parents 2b03b59 + 3fde0fd commit f62dc0a
Show file tree
Hide file tree
Showing 53 changed files with 4,966 additions and 5,622 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"parserOptions": {
"ecmaVersion": 2018
},
"ignorePatterns": ["**/lib/*"],
"rules": {
"space-before-function-paren": [
"error",
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Due to regulatory requirements emanating from SCA for our European/UK sellers, w

This SDK is generic and the signature scheme is compliant with these upcoming IETF standards (currently not yet RFCs).

* [draft-ietf-httpbis-message-signatures-13](https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-13.html)
* [draft-ietf-httpbis-digest-headers-10](https://www.ietf.org/archive/id/draft-ietf-httpbis-digest-headers-10.html)
* [draft-ietf-httpbis-message-signatures-16](https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-16.html)
* [draft-ietf-httpbis-digest-headers-11](https://www.ietf.org/archive/id/draft-ietf-httpbis-digest-headers-11.html)

## Features

Expand All @@ -32,9 +32,9 @@ This SDK is intended to generate required message signature headers, as per the
This SDK incorporates

* Generation of the following HTTP message signature headers:
* **Content-Digest**: This header includes a SHA-256 digest over the HTTP payload (as specified in [draft-ietf-httpbis-digest-headers-10](https://www.ietf.org/archive/id/draft-ietf-httpbis-digest-headers-10.html)), if any. It is not required to be sent for APIs that do not include a request payload (e.g. GET requests).
* **Signature-Input**: This header indicates which headers and pseudo-headers are included, as well as the order in which they are used when calculating the signature. It is created as specified in [draft-ietf-httpbis-message-signatures-13](https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-13.html)
* **Signature**: The value of the Signature header is created as described in [Section 3.1, Creating a Signature](https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-13.html#name-creating-a-signature), of IETF draft-ietf-httpbis-message-signatures-13. It uses the Private Key value generated by the [Key Management API](https://developer.ebay.com/api-docs/developer/key-management/overview.html).
* **Content-Digest**: This header includes a SHA-256 digest over the HTTP payload (as specified in [draft-ietf-httpbis-digest-headers-11](https://www.ietf.org/archive/id/draft-ietf-httpbis-digest-headers-11.html)), if any. It is not required to be sent for APIs that do not include a request payload (e.g. GET requests).
* **Signature-Input**: This header indicates which headers and pseudo-headers are included, as well as the order in which they are used when calculating the signature. It is created as specified in [draft-ietf-httpbis-message-signatures-16](https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-16.html)
* **Signature**: The value of the Signature header is created as described in [Section 3.1, Creating a Signature](https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-16.html#name-creating-a-signature), of IETF draft-ietf-httpbis-message-signatures-16. It uses the Private Key value generated by the [Key Management API](https://developer.ebay.com/api-docs/developer/key-management/overview.html).
* **x-ebay-signature-key**: This header includes the JWE that is created using the [Key Management API](https://developer.ebay.com/api-docs/developer/key-management/overview.html)
* `signMessage` method to sign the incoming request object
* `validateSignature` method to validate the signature of the incoming request object
Expand Down
6 changes: 3 additions & 3 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

import { Response, Request } from 'express';
import { jwtDecrypt } from 'jose';
import { constants } from '../lib/constants';
import { readKey } from '../lib/helpers/common';
import * as DigitalSignatureSDK from '../lib/index';
import { constants } from '../src/constants';
import { readKey } from '../src/helpers/common';
import * as DigitalSignatureSDK from '../src/index';

const testData = require('./test.json');

Expand Down
28 changes: 28 additions & 0 deletions lib/cjs/constants.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export declare const constants: {
readonly BASE64: "base64";
readonly COLON: ":";
readonly CONTENT_DIGEST_SHA256: "sha-256=:";
readonly CONTENT_DIGEST_SHA512: "sha-512=:";
readonly HEADERS: {
readonly APPLICATION_JSON: "application/json";
readonly CONTENT_DIGEST: "content-digest";
readonly SIGNATURE_INPUT: "signature-input";
readonly SIGNATURE_KEY: "x-ebay-signature-key";
readonly SIGNATURE: "signature";
};
readonly HTTP_STATUS_CODE: {
readonly NO_CONTENT: 204;
readonly OK: 200;
readonly BAD_REQUEST: 400;
readonly INTERNAL_SERVER_ERROR: 500;
};
readonly KEY_PATTERN_END: RegExp;
readonly KEY_PATTERN_START: RegExp;
readonly KEY_END: "\n-----END PUBLIC KEY-----";
readonly KEY_START: "-----BEGIN PUBLIC KEY-----\n";
readonly SHA_256: "sha256";
readonly SHA_512: "sha512";
readonly SIGNATURE_PREFIX: "sig1=:";
readonly UTF8: "utf8";
readonly X_EBAY_SIGNATURE: "x-ebay-signature";
};
48 changes: 48 additions & 0 deletions lib/cjs/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* *
* * Copyright 2022 eBay Inc.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *
*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.constants = void 0;
exports.constants = {
BASE64: 'base64',
COLON: ':',
CONTENT_DIGEST_SHA256: 'sha-256=:',
CONTENT_DIGEST_SHA512: 'sha-512=:',
HEADERS: {
APPLICATION_JSON: 'application/json',
CONTENT_DIGEST: 'content-digest',
SIGNATURE_INPUT: 'signature-input',
SIGNATURE_KEY: 'x-ebay-signature-key',
SIGNATURE: 'signature'
},
HTTP_STATUS_CODE: {
NO_CONTENT: 204,
OK: 200,
BAD_REQUEST: 400,
INTERNAL_SERVER_ERROR: 500
},
KEY_PATTERN_END: /\n-----END PUBLIC KEY-----/,
KEY_PATTERN_START: /-----BEGIN PUBLIC KEY-----\n/,
KEY_END: '\n-----END PUBLIC KEY-----',
KEY_START: '-----BEGIN PUBLIC KEY-----\n',
SHA_256: 'sha256',
SHA_512: 'sha512',
SIGNATURE_PREFIX: 'sig1=:',
UTF8: 'utf8',
X_EBAY_SIGNATURE: 'x-ebay-signature'
};
3 changes: 3 additions & 0 deletions lib/cjs/helpers/common.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare function needsContentDigestValidation(requestBody: string): boolean;
declare function readKey(value: string): string;
export { needsContentDigestValidation, readKey };
61 changes: 61 additions & 0 deletions lib/cjs/helpers/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* *
* * Copyright 2022 eBay Inc.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *
*/
'use strict';
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readKey = exports.needsContentDigestValidation = void 0;
const fs = __importStar(require("fs"));
const constants_1 = require("../constants");
function needsContentDigestValidation(requestBody) {
return requestBody !== null &&
requestBody !== undefined &&
requestBody.length > 0;
}
exports.needsContentDigestValidation = needsContentDigestValidation;
function readKey(value) {
let key = value;
if (fs.existsSync(value)) {
key = fs.readFileSync(value, {
encoding: constants_1.constants.UTF8
});
}
return key;
}
exports.readKey = readKey;
18 changes: 18 additions & 0 deletions lib/cjs/helpers/digest-helper.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference types="node" />
/**
* Generates the 'Content-Digest' header value for the input payload.
*
* @param {Buffer} payload The request payload.
* @param {string} cipher The algorithm used to calculate the digest.
* @returns {string} contentDigest The 'Content-Digest' header value.
*/
declare function generateDigestHeader(payload: Buffer, cipher: string): string;
/**
* Validates the 'Content-Digest' header value.
*
* @param {string} contentDigestHeader The Content-Digest header value.
* @param {Buffer} body The HTTP request body.
* @throws {Error} If the Content-Digest header value is invalid.
*/
declare function validateDigestHeader(contentDigestHeader: string, body: Buffer): void;
export { generateDigestHeader, validateDigestHeader };
78 changes: 78 additions & 0 deletions lib/cjs/helpers/digest-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* *
* * Copyright 2022 eBay Inc.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *
*/
'use strict';
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateDigestHeader = exports.generateDigestHeader = void 0;
const crypto_1 = __importDefault(require("crypto"));
const constants_1 = require("../constants");
/**
* Generates the 'Content-Digest' header value for the input payload.
*
* @param {Buffer} payload The request payload.
* @param {string} cipher The algorithm used to calculate the digest.
* @returns {string} contentDigest The 'Content-Digest' header value.
*/
function generateDigestHeader(payload, cipher) {
let contentDigest = '';
// Validate the input payload
if (!payload) {
return contentDigest;
}
// Calculate the SHA-256 digest
const hash = crypto_1.default.createHash(cipher)
.update(payload)
.digest(constants_1.constants.BASE64);
const algo = cipher === constants_1.constants.SHA_512 ? constants_1.constants.CONTENT_DIGEST_SHA512 :
constants_1.constants.CONTENT_DIGEST_SHA256;
contentDigest = algo + hash + constants_1.constants.COLON;
return contentDigest;
}
exports.generateDigestHeader = generateDigestHeader;
;
/**
* Validates the 'Content-Digest' header value.
*
* @param {string} contentDigestHeader The Content-Digest header value.
* @param {Buffer} body The HTTP request body.
* @throws {Error} If the Content-Digest header value is invalid.
*/
function validateDigestHeader(contentDigestHeader, body) {
if (!contentDigestHeader) {
throw new Error("Content-Digest header missing");
}
// Validate
const contentDigestPattern = new RegExp("(.+)=:(.+):");
const contentDigestParts = contentDigestPattern.exec(contentDigestHeader);
if (!contentDigestParts || contentDigestParts.length == 0) {
throw new Error("Content-digest header invalid");
}
const cipher = contentDigestParts[1];
if (cipher !== "sha-256" && cipher !== "sha-512") {
throw new Error("Invalid cipher " + cipher);
}
const algorithm = cipher === "sha-256" ? constants_1.constants.SHA_256 : constants_1.constants.SHA_512;
const newDigest = generateDigestHeader(body, algorithm);
if (newDigest !== contentDigestHeader) {
throw new Error("Content-Digest value is invalid. Expected body digest is: "
+ newDigest);
}
}
exports.validateDigestHeader = validateDigestHeader;
16 changes: 16 additions & 0 deletions lib/cjs/helpers/jwe-helper.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Config } from '..';
/**
* Decrypts the input JWE string and returns the 'pkey' value from claims set.
*
* @param {string} jweString The JWE string.
* @param {Config} config The input config.
* @returns Promise<string> If the JWE decryption is successful, else returns Promise<undefined>.
*/
export declare function decryptJWE(jweString: string, config: Config): Promise<string | undefined>;
/**
* Generates JWE string.
*
* @param {Config} config The input config.
* @returns {Promise<string>} jwe The JWE as string.
*/
export declare function encryptJWE(config: Config): Promise<string>;
77 changes: 77 additions & 0 deletions lib/cjs/helpers/jwe-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* *
* * Copyright 2022 eBay Inc.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *
*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.encryptJWE = exports.decryptJWE = void 0;
const jose_1 = require("jose");
const constants_1 = require("../constants");
const common_1 = require("./common");
const signature_base_helper_1 = require("./signature-base-helper");
/**
* Decrypts the input JWE string and returns the 'pkey' value from claims set.
*
* @param {string} jweString The JWE string.
* @param {Config} config The input config.
* @returns Promise<string> If the JWE decryption is successful, else returns Promise<undefined>.
*/
async function decryptJWE(jweString, config) {
const masterKey = (0, common_1.readKey)(config.masterKey);
const masterKeyBuffer = Buffer.from(masterKey, constants_1.constants.BASE64);
const jwtDecryptResult = await (0, jose_1.jwtDecrypt)(jweString, masterKeyBuffer);
if (jwtDecryptResult['payload'] && jwtDecryptResult['payload']['pkey']) {
const pKey = jwtDecryptResult['payload']['pkey'];
return constants_1.constants.KEY_START + pKey + constants_1.constants.KEY_END;
}
}
exports.decryptJWE = decryptJWE;
/**
* Generates JWE string.
*
* @param {Config} config The input config.
* @returns {Promise<string>} jwe The JWE as string.
*/
async function encryptJWE(config) {
const masterKey = (0, common_1.readKey)(config.masterKey);
let publicKey = (0, common_1.readKey)(config.publicKey);
publicKey = formatPublicKey(publicKey);
const unixTimestamp = (0, signature_base_helper_1.getUnixTimestamp)();
const masterKeyBuffer = Buffer.from(masterKey, constants_1.constants.BASE64);
const jwe = await new jose_1.EncryptJWT(config.jwtPayload)
.setProtectedHeader(config.jweHeaderParams)
.setIssuedAt(unixTimestamp)
.setNotBefore(unixTimestamp)
.setExpirationTime(`${config.jwtExpiration}y`)
.encrypt(masterKeyBuffer);
return jwe;
}
exports.encryptJWE = encryptJWE;
/**
* Removes beginning and end markers from the input public key.
*
* @param {string} key The public key.
* @throws {Error} if the key format is invalid.
*/
function formatPublicKey(key) {
try {
const updatedKey = key.replace(constants_1.constants.KEY_PATTERN_START, '');
return updatedKey.replace(constants_1.constants.KEY_PATTERN_END, '');
}
catch (exception) {
throw new Error(`Invalid public key format`);
}
}
Loading

0 comments on commit f62dc0a

Please sign in to comment.