From 0a9ba6c8be2c84ecd4053aca97b1790f160326ce Mon Sep 17 00:00:00 2001 From: lanterngamescn Date: Sat, 27 Jun 2026 17:06:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=BD=95=E9=9F=B3):=20=E4=B8=83=E7=89=9B?= =?UTF-8?q?=20uploadToken=20=E7=AB=AF=E4=BE=A7=E7=94=9F=E6=88=90(HMAC-SHA1?= =?UTF-8?q?,=E5=AF=B9=E9=BD=90=E5=8E=9F=E5=B7=A5=E7=A8=8B=E4=B8=8D?= =?UTF-8?q?=E8=B5=B0=E6=9C=8D=E5=8A=A1=E7=AB=AF)=20+=20=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 platform/src/main/ets/upload/QiniuToken.ets: - QiniuToken.uploadToken() 异步生成 uploadToken(deadline=now+1h) - HMAC-SHA1 via cryptoFramework.createMac('SHA1'),对齐原 Android 硬编码 AK/SK - urlsafeBase64 工具方法(util.Base64Helper.encodeToStringSync + 替换+/-/_) - eslint-disable-next-line @security/no-unsafe-mac 注释说明七牛协议强制要求 platform/Index.ets 导出 QiniuToken。 entry/src/test/QiniuToken.test.ets 结构单测(三段token格式/AK/scope验证)。 List.test.ets 注册 QiniuTokenTest。 Co-Authored-By: Claude Sonnet 4.6 --- entry/src/test/List.test.ets | 2 + entry/src/test/QiniuToken.test.ets | 20 ++++++++++ platform/Index.ets | 1 + platform/src/main/ets/upload/QiniuToken.ets | 42 +++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 entry/src/test/QiniuToken.test.ets create mode 100644 platform/src/main/ets/upload/QiniuToken.ets diff --git a/entry/src/test/List.test.ets b/entry/src/test/List.test.ets index e1c8873..3f08e15 100644 --- a/entry/src/test/List.test.ets +++ b/entry/src/test/List.test.ets @@ -2,10 +2,12 @@ import localUnitTest from './LocalUnit.test'; import bridgeTest from './Bridge.test'; import versionResolverTest from './VersionResolver.test'; import shareTextTest from './ShareText.test'; +import qiniuTokenTest from './QiniuToken.test'; export default function testsuite() { localUnitTest(); bridgeTest(); versionResolverTest(); shareTextTest(); + qiniuTokenTest(); } diff --git a/entry/src/test/QiniuToken.test.ets b/entry/src/test/QiniuToken.test.ets new file mode 100644 index 0000000..220dde8 --- /dev/null +++ b/entry/src/test/QiniuToken.test.ets @@ -0,0 +1,20 @@ +import { describe, it, expect } from '@ohos/hypium'; +import { QiniuToken } from 'platform'; +import { util } from '@kit.ArkTS'; + +export default function QiniuTokenTest() { + describe('QiniuTokenTest', () => { + it('token_has_three_parts_and_valid_policy', 0, async () => { + const token: string = await QiniuToken.uploadToken(); + const parts: string[] = token.split(':'); + expect(parts.length).assertEqual(3); + expect(parts[0]).assertEqual('ngN3rFW1j8dn7ZGpATKl7mreaNmp2Ei_l9AIhkIf'); + expect(parts[1].length).assertEqual(28); + const helper = new util.Base64Helper(); + const std: string = parts[2].replace(/-/g, '+').replace(/_/g, '/'); + const decoded: Uint8Array = helper.decodeSync(std); + const policy: string = String.fromCharCode.apply(null, Array.from(decoded)); + expect(policy.indexOf('"scope":"gameauio"') >= 0).assertTrue(); + }); + }); +} diff --git a/platform/Index.ets b/platform/Index.ets index f91243f..50d473e 100644 --- a/platform/Index.ets +++ b/platform/Index.ets @@ -15,3 +15,4 @@ export { Unzipper } from './src/main/ets/zip/Unzipper'; export { PermissionGuard } from './src/main/ets/perm/PermissionGuard'; export { LocalUploadServer } from './src/main/ets/upload/LocalUploadServer'; export { QiniuUploader, QiniuTokenProvider } from './src/main/ets/upload/QiniuUploader'; +export { QiniuToken } from './src/main/ets/upload/QiniuToken'; diff --git a/platform/src/main/ets/upload/QiniuToken.ets b/platform/src/main/ets/upload/QiniuToken.ets new file mode 100644 index 0000000..e76a2ce --- /dev/null +++ b/platform/src/main/ets/upload/QiniuToken.ets @@ -0,0 +1,42 @@ +/** + * 七牛 uploadToken 端侧生成(设计 §7)。 + * ⚠️ 刻意对齐原 Android 工程:AK/SK 端侧硬编码、不走服务端——违背 CLAUDE.md 附录B 加固红线, + * 已与用户明确确认(对齐原工程)。token 算法见七牛官方:AK:urlsafeB64(HMAC_SHA1(SK,policy)):policy。 + */ +import { cryptoFramework } from '@kit.CryptoArchitectureKit'; +import { buffer, util } from '@kit.ArkTS'; + +export class QiniuToken { + private static readonly ACCESS_KEY: string = 'ngN3rFW1j8dn7ZGpATKl7mreaNmp2Ei_l9AIhkIf'; + private static readonly SECRET_KEY: string = '6VXav9eqUCORJTYvTFOeTVRonAyk4Hrs-PIZ8jmZ'; + private static readonly BUCKET: string = 'gameauio'; + + /** 生成 uploadToken(deadline=now+1h)。 */ + static async uploadToken(): Promise { + const deadline: number = Math.floor(Date.now() / 1000) + 3600; + const policy: string = `{"scope":"${QiniuToken.BUCKET}","deadline":${deadline}}`; + const encodedPolicy: string = + QiniuToken.urlsafeBase64(new Uint8Array(buffer.from(policy, 'utf-8').buffer)); + const sign: Uint8Array = await QiniuToken.hmacSha1(QiniuToken.SECRET_KEY, encodedPolicy); + const encodedSign: string = QiniuToken.urlsafeBase64(sign); + return `${QiniuToken.ACCESS_KEY}:${encodedSign}:${encodedPolicy}`; + } + + private static async hmacSha1(secret: string, message: string): Promise { + const gen = cryptoFramework.createSymKeyGenerator('HMAC'); + const key = await gen.convertKey({ data: new Uint8Array(buffer.from(secret, 'utf-8').buffer) }); + // eslint-disable-next-line @security/no-unsafe-mac -- 七牛 uploadToken 协议强制 HMAC-SHA1,非自选算法 + const mac = cryptoFramework.createMac('SHA1'); + await mac.init(key); + await mac.update({ data: new Uint8Array(buffer.from(message, 'utf-8').buffer) }); + const out = await mac.doFinal(); + return out.data; + } + + /** 标准 Base64 → 七牛 URL-safe(保留 padding,+→- /→_)。 */ + private static urlsafeBase64(bytes: Uint8Array): string { + const helper = new util.Base64Helper(); + const std: string = helper.encodeToStringSync(bytes, util.Type.BASIC); + return std.replace(/\+/g, '-').replace(/\//g, '_'); + } +}