Skip to content

Commit f56b3df

Browse files
Merge branch 'tunnel-dev'
2 parents 7086f03 + dfa50ef commit f56b3df

File tree

18 files changed

+1045
-292
lines changed

18 files changed

+1045
-292
lines changed

dist/mapboxgl/include-mapboxgl.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
inputScript(libsurl + '/mapbox-gl-js/1.13.2/mapbox-gl.js');
6060
}
6161
if (inArray(includes, 'mapbox-gl-enhance')) {
62-
inputCSS(libsurl + '/mapbox-gl-js-enhance/1.12.1-3/mapbox-gl-enhance.css');
63-
inputScript(libsurl + '/mapbox-gl-js-enhance/1.12.1-3/mapbox-gl-enhance.js');
62+
inputCSS(libsurl + '/mapbox-gl-js-enhance/1.12.1-4/mapbox-gl-enhance.css');
63+
inputScript(libsurl + '/mapbox-gl-js-enhance/1.12.1-4/mapbox-gl-enhance.js');
6464
}
6565
if (inArray(includes, 'g6')) {
6666
inputScript(libsurl + '/antv/g6/4.3.2/g6.min.js');

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
"@mapbox/vector-tile": "1.3.1",
134134
"@sinonjs/text-encoding": "^0.7.2",
135135
"@supermap/iclient-common": "file:src/common",
136+
"@supermap/tile-decryptor": "^0.0.1",
136137
"@tensorflow/tfjs": "^3.9.0",
137138
"@turf/turf": "6.5.0",
138139
"canvg": "3.0.10",
@@ -153,6 +154,7 @@
153154
"mapbox-gl": "1.13.2",
154155
"maplibre-gl": "3.1.0",
155156
"mapv": "2.0.62",
157+
"node-forge": "^1.3.1",
156158
"ol": "6.14.1",
157159
"pbf": "3.2.1",
158160
"process": "^0.11.10",

src/common/index.common.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,9 @@ import {
333333
ArrayStatistic,
334334
getMeterPerMapUnit,
335335
getWrapNum,
336-
conversionDegree
336+
conversionDegree,
337+
EncryptRequest,
338+
getServiceKey
337339
} from './util';
338340
import { CartoCSS, ThemeStyle } from './style';
339341
import {
@@ -498,6 +500,8 @@ export {
498500
isCORS,
499501
setCORS,
500502
FetchRequest,
503+
EncryptRequest,
504+
getServiceKey,
501505
ColorsPickerUtil,
502506
ArrayStatistic,
503507
getMeterPerMapUnit,

src/common/namespace.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ import {
337337
isCORS,
338338
setCORS,
339339
FetchRequest,
340+
EncryptRequest,
341+
getServiceKey,
340342
ColorsPickerUtil,
341343
ArrayStatistic,
342344
CartoCSS,
@@ -493,7 +495,8 @@ SuperMap.isCORS = isCORS;
493495
SuperMap.setRequestTimeout = setRequestTimeout;
494496
SuperMap.getRequestTimeout = getRequestTimeout;
495497
SuperMap.FetchRequest = FetchRequest;
496-
498+
SuperMap.EncryptRequest = EncryptRequest;
499+
SuperMap.getServiceKey = getServiceKey;
497500
// commontypes
498501
SuperMap.inherit = inheritExt;
499502
SuperMap.mixin = mixinExt;

src/common/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@
1515
"license": "Apache-2.0",
1616
"dependencies": {
1717
"@antv/g6": "^4.8.14",
18+
"@supermap/tile-decryptor": "^0.0.1",
1819
"echarts": "5.4.3",
1920
"fetch-ie8": "1.5.0",
2021
"fetch-jsonp": "1.1.3",
22+
"flatgeobuf": "3.23.1",
2123
"promise-polyfill": "8.2.3",
2224
"lodash.topairs": "4.3.0",
2325
"lodash.uniqby": "^4.7.0",
2426
"lodash.clonedeep": "^4.5.0",
2527
"mapv": "2.0.62",
26-
"flatgeobuf": "3.23.1",
28+
"node-forge": "^1.3.1",
2729
"rbush": "^2.0.2",
2830
"urijs": "^1.19.11",
2931
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"

src/common/util/EncryptRequest.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { FetchRequest } from './FetchRequest';
2+
import {
3+
RSAEncrypt,
4+
AESGCMEncrypt,
5+
AESGCMDecrypt,
6+
generateAESRandomKey,
7+
generateAESRandomIV
8+
} from './RequestcryptUtil';
9+
import URI from 'urijs';
10+
11+
/**
12+
* @name EncryptRequest
13+
* @version 11.2.0
14+
* @namespace
15+
* @category BaseTypes Util
16+
* @classdesc 加密请求地址
17+
* @param {string} serverUrl - 服务地址。
18+
*/
19+
export class EncryptRequest {
20+
constructor(serverUrl) {
21+
this.serverUrl = serverUrl;
22+
this.tunnelUrl = undefined;
23+
this.blockedUrlRegex = {
24+
HEAD: [],
25+
POST: [],
26+
GET: [],
27+
PUT: [],
28+
DELETE: []
29+
};
30+
this.encryptAESKey = generateAESRandomKey();
31+
this.encryptAESIV = generateAESRandomIV();
32+
}
33+
34+
/**
35+
* @function EncryptRequest.prototype.request
36+
* @description 加密请求地址。
37+
* @param {Object} config - 加密请求参数。
38+
* @param {string} config.method - 请求方法。
39+
* @param {string} config.url - 请求地址。
40+
* @param {string} config.params - 请求参数。
41+
* @param {Object} config.options - 请求的配置属性。
42+
* @returns {Promise} Promise 对象。
43+
*/
44+
async request(options) {
45+
if (!this.serverUrl) {
46+
throw 'serverUrl can not be empty.';
47+
}
48+
const config = Object.assign({ baseURL: '' }, options);
49+
const tunnelUrl = await this._createTunnel();
50+
if (!tunnelUrl) {
51+
return;
52+
}
53+
for (const pattern of this.blockedUrlRegex[config.method.toUpperCase()]) {
54+
const regex = new RegExp(pattern);
55+
if (regex.test(config.baseURL + config.url)) {
56+
const data = {
57+
url: config.baseURL + (config.url.startsWith('/') ? config.url.substring(1, config.url.length) : config.url),
58+
method: config.method,
59+
timeout: config.timeout,
60+
headers: config.headers,
61+
body: config.data
62+
};
63+
// 替换请求
64+
config.method = 'post';
65+
config.data = AESGCMEncrypt(this.encryptAESKey, this.encryptAESIV, JSON.stringify(data));
66+
if (!config.data) {
67+
throw 'encrypt failed';
68+
}
69+
config.url = this.tunnelUrl;
70+
break;
71+
}
72+
}
73+
const response = await FetchRequest.commit(config.method, config.url, config.data, config.options);
74+
if (config.url === this.tunnelUrl) {
75+
const result = await response.text();
76+
const decryptResult = AESGCMDecrypt(this.encryptAESKey, this.encryptAESIV, result);
77+
if (!decryptResult) {
78+
console.debug('解密请求响应失败');
79+
return;
80+
}
81+
const resultData = JSON.parse(decryptResult);
82+
const resData = Object.create({
83+
json: function () {
84+
return Promise.resolve(resultData.data);
85+
}
86+
});
87+
return Object.assign(resData, resultData);
88+
}
89+
return response;
90+
}
91+
92+
/**
93+
* @private
94+
* @description 获取RSA public key
95+
* @function EncryptRequest.prototype._getRSAPublicKey
96+
*/
97+
async _getRSAPublicKey() {
98+
try {
99+
const response = await FetchRequest.get(URI(this.serverUrl).segment('services/security/tunnel/v1/publickey').toString());
100+
// 解析publicKey
101+
const publicKeyObj = await response.json();
102+
// 生成AES密钥
103+
const aesKeyObj = {
104+
key: this.encryptAESKey,
105+
iv: this.encryptAESIV,
106+
mode: 'GCM',
107+
padding: 'NoPadding'
108+
};
109+
// 将AES密钥使用RSA公钥加密
110+
const aesCipherText = RSAEncrypt(publicKeyObj.publicKey, aesKeyObj.key + aesKeyObj.iv);
111+
return aesCipherText;
112+
} catch (error) {
113+
console.debug('RSA公钥获取失败,错误详情:' + error);
114+
}
115+
}
116+
117+
/**
118+
* @private
119+
* @description 创建隧道
120+
* @function EncryptRequest.prototype._createTunnel
121+
*/
122+
async _createTunnel() {
123+
if (!this.tunnelUrl) {
124+
try {
125+
const data = await this._getRSAPublicKey();
126+
if (!data) {
127+
throw 'fetch RSA publicKey failed';
128+
}
129+
// 创建隧道
130+
const response = await FetchRequest.post(URI(this.serverUrl).segment('services/security/tunnel/v1/tunnels').toString(), data);
131+
const result = await response.json();
132+
Object.assign(this, {
133+
tunnelUrl: result.tunnelUrl,
134+
blockedUrlRegex: Object.assign({}, this.blockedUrlRegex, result.blockedUrlRegex)
135+
});
136+
} catch (error) {
137+
console.debug('安全隧道创建失败,错误详情:' + error);
138+
}
139+
}
140+
return this.tunnelUrl;
141+
}
142+
}
143+
144+
/**
145+
* @function getServiceKey
146+
* @version 11.2.0
147+
* @category iServer
148+
* @description 获取矢量瓦片解密密钥
149+
* @param {string} serviceUrl - iserver服务地址,例如: 'http://127.0.0.1:8090/iserver/services/xxx'、 'http://127.0.0.1:8090/iserver/services/xxx/rest/maps' 、 'http://127.0.0.1:8090/iserver/services/xxx/restjsr/v1/vectortile'
150+
* @return {Promise} key - 矢量瓦片密钥
151+
*/
152+
export async function getServiceKey(serviceUrl) {
153+
try {
154+
const workspaceServerUrl = ((serviceUrl &&
155+
serviceUrl.match(/.+(?=(\/restjsr\/v1\/vectortile\/|\/rest\/maps\/))/)) ||
156+
[])[0];
157+
if (!workspaceServerUrl) {
158+
return;
159+
}
160+
const servicesResponse = await FetchRequest.get(workspaceServerUrl);
161+
const servicesResult = await servicesResponse.json();
162+
const matchRestData = (servicesResult || []).find(
163+
(item) => serviceUrl.includes(item.name) && item.serviceEncryptInfo
164+
);
165+
if (!matchRestData) {
166+
return;
167+
}
168+
const iserverHost = workspaceServerUrl.split('/services/')[0];
169+
const encryptRequest = new EncryptRequest(iserverHost);
170+
const svckeyUrl =
171+
matchRestData && `${iserverHost}/services/security/svckeys/${matchRestData.serviceEncryptInfo.encrptKeyID}.json`;
172+
const svcReponse = await encryptRequest.request({
173+
method: 'get',
174+
url: svckeyUrl
175+
});
176+
return svcReponse.json();
177+
} catch (error) {
178+
console.error(error);
179+
}
180+
}

src/common/util/RequestcryptUtil.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// import pki from 'node-forge/lib/pki';
2+
// import md from 'node-forge/lib/md';
3+
// import cipher from 'node-forge/lib/cipher';
4+
// import util from 'node-forge/lib/util';
5+
import { pki, md, cipher, util } from 'node-forge/dist/forge.min';
6+
7+
/**
8+
* @private
9+
* @function RSAEncrypt
10+
* @description RSAES-OAEP/SHA-256/MGF1-SHA-1加密,对应java的RSA/ECB/OAEPWithSHA-256AndMGF1Padding
11+
* @param publicKeyStr - RSA 公钥
12+
* @param message - 需要加密的信息
13+
* @returns {string|boolean} 加密成功返回base64编码的密文,加密失败返回false
14+
*/
15+
export function RSAEncrypt (publicKeyStr, message) {
16+
if (publicKeyStr && publicKeyStr.indexOf('BEGIN PUBLIC KEY') === -1) { // 转为PEM格式
17+
publicKeyStr = `-----BEGIN PUBLIC KEY-----\n${publicKeyStr}\n-----END PUBLIC KEY-----`;
18+
}
19+
const publicKey = pki.publicKeyFromPem(publicKeyStr);
20+
const obj = {
21+
md: md.sha256.create(),
22+
mgf1: {
23+
md: md.sha1.create()
24+
}
25+
};
26+
const encrypted = publicKey.encrypt(message, 'RSA-OAEP', obj);
27+
if (!encrypted) {
28+
return false; // 加密失败
29+
}
30+
return window.btoa(encrypted);
31+
}
32+
33+
/**
34+
* @private
35+
* @function AESGCMDecrypt
36+
* @description AES/GCM解密
37+
* @param key - 16位
38+
* @param iv - 12位
39+
* @param cipherText - 密文 = base64转码(加密内容 + 16位的mac值)
40+
* @returns {boolean|string} 解密成功返回明文,解密失败返回false
41+
*/
42+
export function AESGCMDecrypt (key, iv, cipherText) {
43+
const cipherStrAndMac = window.atob(cipherText);
44+
const cipherStr = cipherStrAndMac.substring(0, cipherStrAndMac.length - 16);
45+
const mac = cipherStrAndMac.substring(cipherStrAndMac.length - 16);
46+
const decipher = cipher.createDecipher('AES-GCM', util.createBuffer(key));
47+
decipher.start({
48+
iv: util.createBuffer(iv),
49+
additionalData: '', // optional
50+
tagLength: 128, // optional, defaults to 128 bits
51+
tag: mac // authentication tag from encryption
52+
});
53+
decipher.update(util.createBuffer(cipherStr));
54+
const pass = decipher.finish();
55+
if (pass) {
56+
return util.decodeUtf8(decipher.output.data);
57+
}
58+
return false;
59+
}
60+
61+
/**
62+
* @private
63+
* @function AESGCMEncrypt
64+
* @description AES/GCM加密
65+
* @param key - 16位
66+
* @param iv - 12位
67+
* @param msg - 明文
68+
* @returns {boolean|string} 加密成功返回明文,加密失败返回false
69+
*/
70+
export function AESGCMEncrypt (key, iv, msg) {
71+
msg = util.encodeUtf8(msg);
72+
const cipherInstance = cipher.createCipher('AES-GCM', key);
73+
cipherInstance.start({
74+
iv: iv,
75+
additionalData: '', // 'binary-encoded string', // optional
76+
tagLength: 128 // optional, defaults to 128 bits
77+
});
78+
cipherInstance.update(util.createBuffer(msg));
79+
const pass = cipherInstance.finish();
80+
if (pass) {
81+
const encrypted = cipherInstance.output;
82+
const tag = cipherInstance.mode.tag;
83+
return window.btoa(encrypted.data + tag.data);
84+
}
85+
return false;
86+
}
87+
88+
/**
89+
* @private
90+
* @function generateAESRandomKey
91+
* @description 生成随机的16位 AES key
92+
* @returns {string}
93+
*/
94+
export function generateAESRandomKey () {
95+
return randomString(16);
96+
}
97+
98+
/**
99+
* @private
100+
* @function generateAESRandomIV
101+
* @description 生成随机的12位 AES iv
102+
* @returns {string}
103+
*/
104+
export function generateAESRandomIV () {
105+
return randomString(12);
106+
}
107+
108+
/**
109+
* @private
110+
* @function randomString
111+
* @description 生成指定长度的随机字符串
112+
* @param length - 随机字符串长度
113+
* @returns {string}
114+
*/
115+
function randomString (length) {
116+
const str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
117+
let result = '';
118+
for (let i = length; i > 0; --i) { result += str[Math.floor(Math.random() * str.length)]; }
119+
return result;
120+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy