mirror of
https://github.com/certd/certd.git
synced 2026-07-08 13:47:36 +08:00
perf: 支持部署证书到网宿CDN
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { HttpRequestMsg } from "../model/HttpRequestMsg.js";
|
||||
import { AkSkConfig } from "../model/AkSkConfig.js";
|
||||
import { CryptoUtils } from "../util/CryptoUtils.js";
|
||||
import { HttpUtils } from "../util/HttpUtils.js";
|
||||
import { Constant } from "../common/Constant.js";
|
||||
|
||||
export class AkSkAuth {
|
||||
|
||||
public static invoke(akSkConfig: AkSkConfig, jsonBody: string): Promise<string | null> {
|
||||
const requestMsg = AkSkAuth.transferHttpRequestMsg(akSkConfig, jsonBody);
|
||||
AkSkAuth.getAuthAndSetHeaders(requestMsg, akSkConfig.accessKey, akSkConfig.secretKey);
|
||||
return HttpUtils.call(requestMsg);
|
||||
}
|
||||
|
||||
static transferHttpRequestMsg(akSkConfig: AkSkConfig, jsonBody: string): HttpRequestMsg {
|
||||
const requestMsg = new HttpRequestMsg();
|
||||
requestMsg.uri = akSkConfig.uri;
|
||||
if (akSkConfig.endPoint && akSkConfig.endPoint !== Constant.END_POINT) {
|
||||
requestMsg.host = akSkConfig.endPoint;
|
||||
requestMsg.url = `${Constant.HTTPS_REQUEST_PREFIX}${akSkConfig.endPoint}${requestMsg.uri}`;
|
||||
} else {
|
||||
requestMsg.host = Constant.HTTP_DOMAIN;
|
||||
requestMsg.url = `${Constant.HTTP_REQUEST_PREFIX}${requestMsg.uri}`;
|
||||
}
|
||||
requestMsg.method = akSkConfig.method;
|
||||
requestMsg.signedHeaders = AkSkAuth.getSignedHeaders(akSkConfig.signedHeaders);
|
||||
if (['POST', 'PUT', 'PATCH', 'DELETE'].indexOf(akSkConfig.method) !== -1) {
|
||||
requestMsg.body = jsonBody;
|
||||
}
|
||||
return requestMsg;
|
||||
}
|
||||
|
||||
static getAuthAndSetHeaders(requestMsg: HttpRequestMsg, accessKey: string, secretKey: string): void {
|
||||
const timeStamp = (Date.now() / 1000 | 0).toString();
|
||||
requestMsg.headers['Host'] = requestMsg.host;
|
||||
requestMsg.headers[Constant.HEAD_SIGN_ACCESS_KEY] = accessKey;
|
||||
requestMsg.headers[Constant.HEAD_SIGN_TIMESTAMP] = timeStamp;
|
||||
requestMsg.headers["Accept"] = Constant.APPLICATION_JSON;
|
||||
const signature = AkSkAuth.getSignature(requestMsg, secretKey, timeStamp);
|
||||
requestMsg.headers['Authorization'] = AkSkAuth.genAuthorization(accessKey, AkSkAuth.getSignedHeaders(requestMsg.signedHeaders), signature);
|
||||
}
|
||||
|
||||
private static genAuthorization(accessKey: string, signedHeaders: string, signature: string): string {
|
||||
return `${Constant.HEAD_SIGN_ALGORITHM} Credential=${accessKey}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||
}
|
||||
|
||||
private static getSignature(requestMsg: HttpRequestMsg, secretKey: string, timestamp: string): string {
|
||||
let bodyStr = requestMsg.body || "";
|
||||
const hashedRequestPayload = CryptoUtils.sha256Hex(bodyStr);
|
||||
const canonicalRequest = `${requestMsg.method}\n${requestMsg.uri.split("?")[0]}\n${decodeURIComponent(requestMsg.getQueryString())}\n${AkSkAuth.getCanonicalHeaders(requestMsg.headers, AkSkAuth.getSignedHeaders(requestMsg.signedHeaders))}\n${AkSkAuth.getSignedHeaders(requestMsg.signedHeaders)}\n${hashedRequestPayload}`;
|
||||
const stringToSign = `${Constant.HEAD_SIGN_ALGORITHM}\n${timestamp}\n${CryptoUtils.sha256Hex(canonicalRequest)}`;
|
||||
return CryptoUtils.hmac256(secretKey, stringToSign).toLowerCase();
|
||||
}
|
||||
|
||||
private static getCanonicalHeaders(headers: Record<string, string>, signedHeaders: string): string {
|
||||
const headerNames = signedHeaders.split(";");
|
||||
let canonicalHeaders = "";
|
||||
for (const headerName of headerNames) {
|
||||
const headerValue = AkSkAuth.getValueByHeader(headerName, headers);
|
||||
if (headerValue !== null) {
|
||||
canonicalHeaders += `${headerName}:${headerValue.toLowerCase()}\n`;
|
||||
} else {
|
||||
// Handle missing headers if necessary, e.g., log a warning or skip
|
||||
console.warn(`Header ${headerName} not found in provided headers.`);
|
||||
}
|
||||
}
|
||||
return canonicalHeaders;
|
||||
}
|
||||
|
||||
|
||||
private static getSignedHeaders(signedHeaders: string): string {
|
||||
if (!signedHeaders) {
|
||||
return "content-type;host";
|
||||
}
|
||||
const headers = signedHeaders.split(";");
|
||||
return headers.map(header => header.toLowerCase()).sort().join(";");
|
||||
}
|
||||
|
||||
private static getValueByHeader(name: string, customHeaderMap: { [key: string]: string }): string | null {
|
||||
for (const key in customHeaderMap) {
|
||||
if (key.toLowerCase() === name.toLowerCase()) {
|
||||
return customHeaderMap[key];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export class Constant {
|
||||
private constructor() {}
|
||||
|
||||
public static readonly HTTP_REQUEST_PREFIX: string = "https://open.chinanetcenter.com";
|
||||
public static readonly HTTPS_REQUEST_PREFIX: string = "https://";
|
||||
public static readonly HTTP_DOMAIN: string = "open.chinanetcenter.com";
|
||||
|
||||
public static readonly APPLICATION_JSON: string = "application/json";
|
||||
|
||||
public static readonly HEAD_SIGN_ACCESS_KEY: string = "x-cnc-accessKey";
|
||||
public static readonly HEAD_SIGN_TIMESTAMP: string = "x-cnc-timestamp";
|
||||
public static readonly HEAD_SIGN_ALGORITHM: string = "CNC-HMAC-SHA256";
|
||||
|
||||
public static readonly X_CNC_AUTH_METHOD: string = "x-cnc-auth-method";
|
||||
|
||||
public static readonly AUTH_METHOD: string = "AKSK";
|
||||
|
||||
public static readonly END_POINT: string = "{endPoint}";
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export class ApiAuthException extends Error {
|
||||
public cause?: any;
|
||||
|
||||
constructor(message: string, cause?: any) {
|
||||
super(message);
|
||||
this.cause = cause;
|
||||
this.name = 'ApiAuthException';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { AkSkConfig } from "./model/AkSkConfig.js";
|
||||
import { AkSkAuth } from "./auth/AkSkAuth.js";
|
||||
|
||||
export { AkSkAuth, AkSkConfig}
|
||||
@@ -0,0 +1,56 @@
|
||||
export class AkSkConfig {
|
||||
private _accessKey: string | undefined;
|
||||
private _secretKey: string | undefined;
|
||||
private _uri: string | undefined;
|
||||
private _endPoint: string | undefined;
|
||||
private _method: string | undefined;
|
||||
private _signedHeaders: string | undefined;
|
||||
|
||||
public get accessKey(): string {
|
||||
return this._accessKey;
|
||||
}
|
||||
|
||||
public set accessKey(value: string) {
|
||||
this._accessKey = value;
|
||||
}
|
||||
|
||||
public get secretKey(): string {
|
||||
return this._secretKey;
|
||||
}
|
||||
|
||||
public set secretKey(value: string) {
|
||||
this._secretKey = value;
|
||||
}
|
||||
|
||||
public get uri(): string {
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
public set uri(value: string) {
|
||||
this._uri = value;
|
||||
}
|
||||
|
||||
public get endPoint(): string {
|
||||
return this._endPoint;
|
||||
}
|
||||
|
||||
public set endPoint(value: string) {
|
||||
this._endPoint = value;
|
||||
}
|
||||
|
||||
public get method(): string {
|
||||
return this._method;
|
||||
}
|
||||
|
||||
public set method(value: string) {
|
||||
this._method = value;
|
||||
}
|
||||
|
||||
public get signedHeaders(): string {
|
||||
return this._signedHeaders;
|
||||
}
|
||||
|
||||
public set signedHeaders(value: string) {
|
||||
this._signedHeaders = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Constant } from '../common/Constant.js'; // Assuming you have a TypeScript version of this
|
||||
|
||||
export class HttpRequestMsg {
|
||||
uri: string ;
|
||||
url: string;
|
||||
host: string;
|
||||
method: string;
|
||||
protocol: string;
|
||||
params: Record<string, string>;
|
||||
headers: Record<string, string>;
|
||||
body: string;
|
||||
signedHeaders: string;
|
||||
msg: any;
|
||||
|
||||
constructor() {
|
||||
this.params = {};
|
||||
this.headers = {};
|
||||
this.putHeader('Content-Type', Constant.APPLICATION_JSON);
|
||||
this.putHeader(Constant.X_CNC_AUTH_METHOD, Constant.AUTH_METHOD);
|
||||
}
|
||||
|
||||
putParam(name: string, value: string): void {
|
||||
this.params[name] = value;
|
||||
}
|
||||
|
||||
getParam(name: string): string | null {
|
||||
const value = this.params[name];
|
||||
return value && value.trim() !== '' ? value : null;
|
||||
}
|
||||
|
||||
getQueryString(): string {
|
||||
if(this.uri == undefined)
|
||||
return "";
|
||||
const index = this.uri.indexOf("?");
|
||||
if (this.method === 'POST' || index === -1) {
|
||||
return "";
|
||||
}
|
||||
return this.uri.substring(index + 1);
|
||||
}
|
||||
|
||||
putHeader(name: string, value: string): void {
|
||||
this.headers[name] = value;
|
||||
}
|
||||
|
||||
getHeader(name: string): string | null {
|
||||
for (const key in this.headers) {
|
||||
if (key.toLowerCase() === name.toLowerCase()) {
|
||||
return this.headers[key];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getHeaderByNames(...names: string[]): string | null {
|
||||
for (const name of names) {
|
||||
const value = this.getHeader(name);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
removeHeader(name: string): void {
|
||||
for (const key in this.headers) {
|
||||
if (key.toLowerCase() === name.toLowerCase()) {
|
||||
delete this.headers[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setJsonBody(object: any): void {
|
||||
this.body = JSON.stringify(object);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
export class CryptoUtils {
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* hmac+sha256+hex
|
||||
*/
|
||||
public static sha256Hex(s: string): string {
|
||||
const hash = CryptoJS.SHA256(s);
|
||||
return hash.toString(CryptoJS.enc.Hex).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* hmac+sha256
|
||||
*/
|
||||
public static hmac256(secretKey: string, message: string): string {
|
||||
const keyWordArray = CryptoJS.enc.Utf8.parse(secretKey);
|
||||
const messageWordArray = CryptoJS.enc.Utf8.parse(message);
|
||||
const hash = CryptoJS.HmacSHA256(messageWordArray, keyWordArray);
|
||||
return hash.toString(CryptoJS.enc.Hex).toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { HttpRequestMsg } from '../model/HttpRequestMsg.js'; // Assuming you have a TypeScript version of this
|
||||
import { ApiAuthException } from '../exception/ApiAuthException.js'; // Assuming you have a TypeScript version of this
|
||||
import axios, { AxiosError } from 'axios';
|
||||
|
||||
export class HttpUtils {
|
||||
private constructor() { }
|
||||
public static async call(requestMsg: HttpRequestMsg): Promise<string | null> {
|
||||
var response;
|
||||
try {
|
||||
response = await axios({
|
||||
method: requestMsg.method,
|
||||
url: requestMsg.url,
|
||||
headers: requestMsg.headers,
|
||||
data: requestMsg.body
|
||||
});
|
||||
console.info("API invoke success. Response:", response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
// Handle AxiosError specifically
|
||||
console.error('API invoke failed. Response:', error.response.data);
|
||||
return error.response.data;
|
||||
} else {
|
||||
// Handle other types of errors
|
||||
console.error('API invoke failed.', error);
|
||||
}
|
||||
throw new ApiAuthException('API invoke failed.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user