diff --git a/docs/rfc/000-Payload-v37.md b/docs/rfc/000-Payload-v37.md index 0dd058671011..00e9ccf76973 100644 --- a/docs/rfc/000-Payload-v37.md +++ b/docs/rfc/000-Payload-v37.md @@ -123,9 +123,7 @@ The implementation MUST NOT fail if the algorithm is not supported. This field represents the public key of the author. -The value is in the DER encoding of the SubjectPublicKeyInfo (`spki`) structure from [RFC 5280][rfc5280]. - -[rfc5280]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7 +The value is in the compressed format of the EC key. When it is `null`, it represents no this information is available (due to software defeat or user choice to opt-out). @@ -155,7 +153,7 @@ type PublicEncrypted = [kind: EncryptionKind.Public, AES_KEY: AES_KEY, iv: Binar ###### `AES_KEY` -This field represents the AES key of this payload. +This field represents the raw AES-256-GCM key (binary) of this payload. ###### `iv` field @@ -182,7 +180,7 @@ This field represents the iv used to encrypt the message. ###### `authorEphemeralPublicKey` field -This field is a Map of the DER encoding of the SubjectPublicKeyInfo (`spki`) structure from [RFC5280]. +This field is a Map of the compressed format of the EC key. The key indicates its format. @@ -199,68 +197,10 @@ The implementation MUST fail when the decryption result is NOT a valid TypedMess ### `secp256k1` -When `spki` is mentioned in this spec, the implementation MUST be able to recognize the SubjectPublicKeyInfo of the curve [`secp256k1`][secp256k1]. This curve is widely used in the Mask Network. - [secp256k1]: https://en.bitcoin.it/wiki/Secp256k1 -Here is an example of the `secp256k1` public key in Binary. - -```plaintext -[ - 48, 86, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, - 1, 6, 5, 43, 129, 4, 0, 10, 3, 66, 0, 4, - 236, 81, 1, 232, 133, 60, 235, 215, 107, 253, 124, 90, - 12, 21, 14, 139, 178, 143, 232, 52, 240, 119, 105, 91, - 196, 232, 84, 33, 238, 69, 42, 104, 223, 226, 96, 216, - 191, 166, 10, 63, 179, 111, 125, 99, 161, 131, 168, 172, - 181, 245, 168, 182, 150, 19, 182, 240, 202, 62, 202, 219, - 21, 175, 144, 205 -] -``` - -### `AES_KEY` - -```typescript -type AES_KEY = [alg: String, k: String] -``` - -This type is used in this specification to represent section 6.4 of a [JsonWebKey][rfc7518] of [AES family key][rfc7518-aes-family-key]. - -[rfc7518]: https://datatracker.ietf.org/doc/html/rfc7518 -[rfc7518-aes-family-key]: https://datatracker.ietf.org/doc/html/rfc7518#section-6.4 - -The implementation MUST fail when the `alg` is not recognized as a known algorithm. -The implementation MUST fail when the `k` is not valid for the given `alg`. - -When encrypting with AES key, the implementation MUST NOT use `additionalData`, the `tagLength` MUST be 128. - -#### Encoding from JsonWebKey `jwk` - -```js -function fromJsonWebKey(jwk) { - return [jwk.alg, jwk.k] -} -``` - -#### Decoding from `key` - -```js -function toJsonWebKey(key) { - const k = { ext: true, key_ops: ['encrypt', 'decrypt'], kty: 'oct' } - k.alg = key[0] - k.k = key[1] - return k -} -``` - ## FAQ ### Why the version number is negative? The pre 1.0 version of the Mask Network extension uses `-42` as its initial payload version. The number `42` comes from the book _The Hitchhiker's Guide to the Galaxy_ and the minus sign indicates this is an early version. When a new payload format is drafted, it's a natural idea that the version number should add by 1, therefore it should be `-41`. At the time of this RFC written, the latest payload is version `-38`, therefore this RFC follows the convention to mark the version as `-37`. - -### Why not uses the `raw` format defined in the Web Crypto specification for AES key? - -According to [the Web Crypto specification][webcrypto], `raw` format is NOT standardized therefore it might have a co-operational problem. - -[webcrypto]: https://w3c.github.io/webcrypto/#dfn-CryptoKey-slot-handle diff --git a/packages/encryption/src/encryption/Decryption.ts b/packages/encryption/src/encryption/Decryption.ts index e4141bb8462f..c67808eb761f 100644 --- a/packages/encryption/src/encryption/Decryption.ts +++ b/packages/encryption/src/encryption/Decryption.ts @@ -2,8 +2,8 @@ import { unreachable } from '@dimensiondev/kit' import { decodeTypedMessageFromDocument, decodeTypedMessageV38ToV40Format, TypedMessage } from '@masknet/typed-message' import { AESCryptoKey, EC_Public_CryptoKey, andThenAsync } from '@masknet/shared-base' import { None, Result } from 'ts-results' -import { AESAlgorithmEnum, PayloadParseResult } from '../payload' -import { decryptWithAES, importAESFromJWK } from '../utils' +import type { PayloadParseResult } from '../payload' +import { decryptWithAES, importAES } from '../utils' import { DecryptOptions, DecryptIO, @@ -40,13 +40,7 @@ export async function* decrypt(options: DecryptOptions, io: DecryptIO): AsyncIte if (AESKey.err) return yield new DecryptError(ErrorReasons.PayloadBroken, AESKey.val) if (iv.err) return yield new DecryptError(ErrorReasons.PayloadBroken, iv.val) // Not calling setPostCache here. It's public post and saving key is wasting storage space. - return yield* decryptWithPostAESKey( - version, - AESKey.val.key as AESCryptoKey, - iv.val, - encrypted, - options.onDecrypted, - ) + return yield* decryptWithPostAESKey(version, AESKey.val, iv.val, encrypted, options.onDecrypted) } else if (encryption.type === 'E2E') { const { iv: _iv, ownersAESKeyEncrypted } = encryption if (_iv.err) return yield new DecryptError(ErrorReasons.PayloadBroken, _iv.val) @@ -202,11 +196,11 @@ async function* decryptByECDH( : await derive(ephemeralPublicKey).then((aesArr) => aesArr.map((aes) => [aes, iv] as const)) for (const [derivedKey, derivedKeyNewIV] of derivedKeys) { const possiblePostKey = await andThenAsync( - decryptWithAES(AESAlgorithmEnum.A256GCM, derivedKey, derivedKeyNewIV, encryptedPostKey), + decryptWithAES(derivedKey, derivedKeyNewIV, encryptedPostKey), postKeyDecoder, ) if (possiblePostKey.err) continue - const decrypted = await decryptWithAES(AESAlgorithmEnum.A256GCM, possiblePostKey.val, iv, encrypted) + const decrypted = await decryptWithAES(possiblePostKey.val, iv, encrypted) if (decrypted.err) continue io.setPostKeyCache(possiblePostKey.val).catch(() => {}) @@ -225,7 +219,7 @@ async function* decryptWithPostAESKey( encrypted: Uint8Array, report: ((message: TypedMessage) => void) | undefined, ): AsyncIterableIterator { - const { err, val } = await decryptWithAES(AESAlgorithmEnum.A256GCM, postAESKey, iv, encrypted) + const { err, val } = await decryptWithAES(postAESKey, iv, encrypted) if (err) return yield new DecryptError(ErrorReasons.DecryptFailed, val) return yield* parseTypedMessage(version, val, report) } @@ -250,7 +244,7 @@ function importAESKeyFromJWKFromTextEncoder(aes_raw: Uint8Array) { const aes_text = new TextDecoder().decode(aes_raw) const aes_jwk = JSON.parse(aes_text) as JsonWebKey if (!aes_jwk.key_ops!.includes('decrypt')) aes_jwk.key_ops!.push('decrypt') - return (await importAESFromJWK.AES_GCM_256(aes_jwk)).unwrap() + return (await importAES(aes_jwk)).unwrap() }) } diff --git a/packages/encryption/src/encryption/Encryption.ts b/packages/encryption/src/encryption/Encryption.ts index 6f929aabdbd6..7f42cec8ce56 100644 --- a/packages/encryption/src/encryption/Encryption.ts +++ b/packages/encryption/src/encryption/Encryption.ts @@ -7,7 +7,7 @@ import { SerializableTypedMessages, } from '@masknet/typed-message' import { None, Option, Some } from 'ts-results' -import { AESAlgorithmEnum, EC_Key, encodePayload, PayloadWellFormed } from '../payload' +import { EC_Key, encodePayload, PayloadWellFormed } from '../payload' import { encryptWithAES } from '../utils' import { @@ -33,7 +33,7 @@ export async function encrypt(options: EncryptOptions, io: EncryptIO): Promise encryptWithAES(AESAlgorithmEnum.A256GCM, postKey, postIV, message)) + .then((message) => encryptWithAES(postKey, postIV, message)) .then((x) => x.unwrap()) let encryption: PayloadWellFormed.PublicEncryption | PayloadWellFormed.EndToEndEncryption @@ -43,7 +43,7 @@ export async function encrypt(options: EncryptOptions, io: EncryptIO): Promise + readonly AESKey: RequiredField readonly iv: RequiredField } /** @@ -71,8 +78,8 @@ export declare namespace PayloadWellFormed { */ export interface PublicEncryption { readonly type: 'public' - /** The key used to encrypt the payload. */ - readonly AESKey: AESKey + /** The key used to encrypt the payload. It should be AES-256-GCM */ + readonly AESKey: AESCryptoKey readonly iv: Uint8Array } /** @@ -93,18 +100,11 @@ export interface EC_Key { readonly algr: EC_KeyCurveEnum readonly key: K } -export interface AESKey { - readonly algr: AESAlgorithmEnum - readonly key: CryptoKey -} export enum EC_KeyCurveEnum { ed25519 = 0, secp256p1 = 1, // P-256 secp256k1 = 2, // K-256 } -export enum AESAlgorithmEnum { - A256GCM = 'A256GCM', -} export enum SocialNetworkEnum { Unknown = -1, Facebook = 0, diff --git a/packages/encryption/src/payload_internal/shared.ts b/packages/encryption/src/payload_internal/shared.ts index 3b5b6b772fe7..e49e6a1be2c9 100644 --- a/packages/encryption/src/payload_internal/shared.ts +++ b/packages/encryption/src/payload_internal/shared.ts @@ -2,9 +2,9 @@ import { ProfileIdentifier, CheckedError, OptionalResult, EnhanceableSite } from import { Ok } from 'ts-results' import { PayloadParseResult, SocialNetworkEnum } from '../payload' import { CryptoException, PayloadException } from '../types' -import { importAESFromJWK } from '../utils' +import { importAES } from '../utils' -const import_AES_GCM_256 = CheckedError.withErr(importAESFromJWK.AES_GCM_256, CryptoException.InvalidCryptoKey) +const import_AES_GCM_256 = CheckedError.withErr(importAES, CryptoException.InvalidCryptoKey) /** * @internal diff --git a/packages/encryption/src/payload_internal/version-37.encoder.ts b/packages/encryption/src/payload_internal/version-37.encoder.ts index 720dca2c4693..f0a05180633e 100644 --- a/packages/encryption/src/payload_internal/version-37.encoder.ts +++ b/packages/encryption/src/payload_internal/version-37.encoder.ts @@ -1,6 +1,8 @@ +import { compressSecp256k1KeyRaw } from '@masknet/shared-base' import { Ok } from 'ts-results' import type { PayloadWellFormed } from '..' -import { encodeMessagePack, exportCryptoKeyToSPKI } from '../utils' +import { EC_KeyCurveEnum } from '../payload/types' +import { encodeMessagePack, exportCryptoKeyToRaw } from '../utils' const enum Index { version = 0, @@ -22,12 +24,14 @@ export async function encode37(payload: PayloadWellFormed.Payload) { if (payload.authorPublicKey.some) { const { algr, key } = payload.authorPublicKey.val payload_arr[Index.authorPublicKeyAlgorithm] = algr - const spki = await exportCryptoKeyToSPKI(key) - if (spki.ok) { - payload_arr[Index.authorPublicKey] = spki.val + const raw = await exportCryptoKeyToRaw(key) + if (raw.ok) { + if (algr === EC_KeyCurveEnum.secp256k1) + payload_arr[Index.authorPublicKey] = compressSecp256k1KeyRaw(raw.val) + else payload_arr[Index.authorPublicKey] = raw.val } else { payload_arr[Index.authorPublicKey] = null - warn(key, spki.err) + warn(key, raw.err) } } if (payload.encryption.type === 'E2E') { @@ -35,24 +39,22 @@ export async function encode37(payload: PayloadWellFormed.Payload) { const keyMaterials: any = {} const subArr: any[] = [1, ownersAESKeyEncrypted, iv, keyMaterials] for (const [alg, key] of ephemeralPublicKey.entries()) { - const k = await exportCryptoKeyToSPKI(key) + const k = await exportCryptoKeyToRaw(key) if (k.err) warn(key, k.err) - else keyMaterials[alg] = k.val + else { + if (alg === EC_KeyCurveEnum.secp256k1) keyMaterials[alg] = compressSecp256k1KeyRaw(k.val) + else keyMaterials[alg] = k.val + } } payload_arr[Index.encryption] = subArr } else { const { AESKey, iv } = payload.encryption - const subArr = [0, [AESKey.algr, (await crypto.subtle.exportKey('jwk', AESKey.key)).k], iv] + const subArr = [0, new Uint8Array(await crypto.subtle.exportKey('raw', AESKey)), iv] payload_arr[Index.encryption] = subArr } payload_arr[Index.data] = payload.encrypted return Ok(encodeMessagePack(payload_arr)) } function warn(key: CryptoKey, err: any) { - console.warn( - '[@masknet/encryption] Failed to encode a public key object into spki format. key is', - key, - 'and the error is', - err, - ) + console.warn('[@masknet/encryption] Failed to export public key. key is', key, 'and the error is', err) } diff --git a/packages/encryption/src/payload_internal/version-37.parser.ts b/packages/encryption/src/payload_internal/version-37.parser.ts index 22fc4327924b..a00eb26fc6d0 100644 --- a/packages/encryption/src/payload_internal/version-37.parser.ts +++ b/packages/encryption/src/payload_internal/version-37.parser.ts @@ -1,10 +1,10 @@ import type { PayloadParserResult } from '.' import type { PayloadParseResult } from '../payload' import { CryptoException, PayloadException, assertArray, assertUint8Array } from '../types' -import { andThenAsync, CheckedError, OptionalResult } from '@masknet/shared-base' +import { andThenAsync, CheckedError, decompressSecp256k1KeyRaw, OptionalResult } from '@masknet/shared-base' import { Ok, Result } from 'ts-results' -import { AESKey, AESAlgorithmEnum, EC_Key, EC_KeyCurveEnum } from '../payload/types' -import { decodeMessagePackF, assertIVLengthEq16, importAESFromJWK, importEC_Key } from '../utils' +import { EC_Key, EC_KeyCurveEnum } from '../payload/types' +import { decodeMessagePackF, assertIVLengthEq16, importAES, importEC_Key } from '../utils' import { safeUnreachable } from '@dimensiondev/kit' import { parseSignatureContainer } from './SignatureContainer' import { parseAuthor } from './shared' @@ -13,8 +13,8 @@ import { parseAuthor } from './shared' const decode = decodeMessagePackF(PayloadException.InvalidPayload, PayloadException.DecodeFailed) const InvalidPayload = (msg?: string) => new CheckedError(PayloadException.InvalidPayload, msg).toErr() -const importSpki = CheckedError.withErr(importEC_Key, CryptoException.InvalidCryptoKey) -const importAES256 = CheckedError.withErr(importAESFromJWK, CryptoException.InvalidCryptoKey) +const importAES256 = CheckedError.withErr(importAES, CryptoException.InvalidCryptoKey) +const importEC = CheckedError.withErr(importEC_Key, CryptoException.InvalidCryptoKey) export async function parse37(input: Uint8Array): PayloadParserResult { const signatureContainer = parseSignatureContainer(input) if (signatureContainer.err) return signatureContainer @@ -86,28 +86,18 @@ async function parseEncryption(encryption: unknown): Promise - - return andThenAsync(assertArray('aes', CryptoException.InvalidCryptoKey)(aes), async (aes): T => { - const [algr, k] = aes - if (typeof k === 'string') { - if (algr === AESAlgorithmEnum.A256GCM) { - const jwk: JsonWebKey = { ext: true, key_ops: ['encrypt', 'decrypt'], kty: 'oct', alg: algr, k } - const key = await importAES256(jwk, algr) - if (key.err) return key - return Ok({ algr, key: key.val }) - } - } - return new CheckedError(CryptoException.UnsupportedAlgorithm, null).toErr() - }) +async function parseAES(aes: unknown) { + return andThenAsync(assertUint8Array(aes, 'aes', CryptoException.InvalidCryptoKey), importAES256) } function importAsymmetryKey(algr: unknown, key: unknown, name: string) { type T = Promise>> return andThenAsync(assertUint8Array(key, name, CryptoException.InvalidCryptoKey), async (pubKey): T => { if (typeof algr === 'number') { if (algr in EC_KeyCurveEnum) { - const key = await importSpki(pubKey, algr) + if (algr === EC_KeyCurveEnum.secp256k1) { + pubKey = decompressSecp256k1KeyRaw(pubKey) + } + const key = await importEC(pubKey, algr) if (key.err) return key return Ok({ algr, key: key.val }) } diff --git a/packages/encryption/src/payload_internal/version-38.encoder.ts b/packages/encryption/src/payload_internal/version-38.encoder.ts index 7e8eed3d2230..8b25e1e8ddc8 100644 --- a/packages/encryption/src/payload_internal/version-38.encoder.ts +++ b/packages/encryption/src/payload_internal/version-38.encoder.ts @@ -2,7 +2,6 @@ import { encodeText, encodeArrayBuffer } from '@dimensiondev/kit' import { Ok, Option, Result } from 'ts-results' import type { PayloadWellFormed, Signature } from '..' -import { AESAlgorithmEnum } from '../payload' import { CryptoException, PayloadException } from '../types' import { encryptWithAES, exportCryptoKeyToJWK } from '../utils' import { get_v38PublicSharedCryptoKey } from './shared' @@ -52,7 +51,7 @@ async function encodeAESKeyEncrypted( const publicSharedKey = await get_v38PublicSharedCryptoKey() if (publicSharedKey.err) return publicSharedKey - const jwk = await exportCryptoKeyToJWK(AESKey.key) + const jwk = await exportCryptoKeyToJWK(AESKey) if (jwk.err) return jwk.mapErr((e) => new CheckedError(CryptoException.InvalidCryptoKey, e)) // There is no reason that these two steps will fail. @@ -63,7 +62,7 @@ async function encodeAESKeyEncrypted( const text = `{"alg":"A256GCM","ext":true,"k":"${jwk.val.k}","key_ops":["decrypt","encrypt"],"kty":"oct"}` const ab = encodeText(text) - const encryptedKey = await encryptWithAES(AESAlgorithmEnum.A256GCM, publicSharedKey.val, iv, ab) + const encryptedKey = await encryptWithAES(publicSharedKey.val, iv, ab) if (encryptedKey.err) return encryptedKey.mapErr((e) => new CheckedError(CryptoException.EncryptFailed, e)) return Ok(encodeArrayBuffer(encryptedKey.val.slice())) } diff --git a/packages/encryption/src/payload_internal/version-38.parser.ts b/packages/encryption/src/payload_internal/version-38.parser.ts index b82f3c60b67a..fe06e2a7a306 100644 --- a/packages/encryption/src/payload_internal/version-38.parser.ts +++ b/packages/encryption/src/payload_internal/version-38.parser.ts @@ -1,5 +1,5 @@ /* eslint @dimensiondev/unicode-specific-set: ["error", { "only": "code" }] */ -import { AESKey, AESAlgorithmEnum, EC_Key, PayloadParseResult, EC_KeyCurveEnum, Signature } from '../payload' +import { EC_Key, PayloadParseResult, EC_KeyCurveEnum, Signature } from '../payload' import { CryptoException, PayloadException } from '../types' import { Result, Ok, Some } from 'ts-results' import { @@ -7,7 +7,7 @@ import { decodeTextF, decryptWithAES, assertIVLengthEq16, - importAESFromJWK, + importAES, importEC_Key, JSONParseF, } from '../utils' @@ -114,15 +114,15 @@ async function decodePublicSharedAESKey( const publicSharedKey = await get_v38PublicSharedCryptoKey() if (publicSharedKey.err) return publicSharedKey - const import_AES_GCM_256 = CheckedError.withErr(importAESFromJWK.AES_GCM_256, CryptoException.InvalidCryptoKey) + const import_AES_GCM_256 = CheckedError.withErr(importAES, CryptoException.InvalidCryptoKey) const decrypt = CheckedError.withErr(decryptWithAES, CryptoException.InvalidCryptoKey) - const jwk_in_u8arr = await decrypt(AESAlgorithmEnum.A256GCM, publicSharedKey.val, iv.val, encryptedKey.val) + const jwk_in_u8arr = await decrypt(publicSharedKey.val, iv.val, encryptedKey.val) const jwk_in_text = await andThenAsync(jwk_in_u8arr, decodeTextCrypto) const jwk = await andThenAsync(jwk_in_text, JSONParse) const aes = await andThenAsync(jwk, import_AES_GCM_256) - return aes.map((key) => ({ algr: AESAlgorithmEnum.A256GCM, key })) + return aes } async function decodeECDHPublicKey(compressedPublic: string): Promise> { diff --git a/packages/encryption/src/utils/crypto.ts b/packages/encryption/src/utils/crypto.ts index 0d24c95da155..3fcb0c783a4a 100644 --- a/packages/encryption/src/utils/crypto.ts +++ b/packages/encryption/src/utils/crypto.ts @@ -1,26 +1,25 @@ import { AESCryptoKey, CheckedError, EC_CryptoKey } from '@masknet/shared-base' import { Result, Ok } from 'ts-results' -import { AESAlgorithmEnum, EC_KeyCurveEnum } from '../payload' +import { EC_KeyCurveEnum } from '../payload' import { CryptoException } from '../types' -export function importAESFromJWK(key: JsonWebKey, kind: AESAlgorithmEnum): Promise> { +export function importAES(key: JsonWebKey | Uint8Array): Promise> { return Result.wrapAsync(() => { - const param: Record = { - [AESAlgorithmEnum.A256GCM]: { - name: 'AES-GCM', - length: 256, - }, + if (key instanceof Uint8Array) { + return crypto.subtle.importKey('raw', key, { name: 'AES-GCM', length: 256 }, true, [ + 'encrypt', + 'decrypt', + ]) as any } - return crypto.subtle.importKey('jwk', key, param[kind], true, ['encrypt', 'decrypt']) as any + return crypto.subtle.importKey('jwk', key, { name: 'AES-GCM', length: 256 }, true, [ + 'encrypt', + 'decrypt', + ]) as any }) } -importAESFromJWK.AES_GCM_256 = (key: JsonWebKey) => importAESFromJWK(key, AESAlgorithmEnum.A256GCM) export function exportCryptoKeyToJWK(key: CryptoKey) { return Result.wrapAsync(() => crypto.subtle.exportKey('jwk', key)) } -export function exportCryptoKeyToSPKI(key: CryptoKey) { - return Result.wrapAsync(() => crypto.subtle.exportKey('spki', key).then((x) => new Uint8Array(x))) -} export function exportCryptoKeyToRaw(key: CryptoKey) { return Result.wrapAsync(() => crypto.subtle.exportKey('raw', key).then((x) => new Uint8Array(x))) } @@ -37,27 +36,22 @@ export function importEC_Key(key: JsonWebKey | Uint8Array, kind: EC_KeyCurveEnum } const args = [ImportParamsMap[kind], true, DeriveKeyUsage] as const if (key instanceof Uint8Array) { - return crypto.subtle.importKey('spki', key, ...args) as Promise + return crypto.subtle.importKey('raw', key, ...args) as Promise } else { return crypto.subtle.importKey('jwk', key, ...args) as Promise } }) } -export function encryptWithAES(kind: AESAlgorithmEnum, key: CryptoKey, iv: Uint8Array, message: Uint8Array) { - const param = { - [AESAlgorithmEnum.A256GCM]: { name: 'AES-GCM', iv } as AesGcmParams, - } as const - return Result.wrapAsync(() => { - return crypto.subtle.encrypt(param[kind], key, message).then((x) => new Uint8Array(x)) +export function encryptWithAES(key: CryptoKey, iv: Uint8Array, message: Uint8Array) { + return Result.wrapAsync(async () => { + const x = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, message) + return new Uint8Array(x) }) } -export function decryptWithAES(kind: AESAlgorithmEnum, key: CryptoKey, iv: Uint8Array, message: Uint8Array) { - const param = { - [AESAlgorithmEnum.A256GCM]: { name: 'AES-GCM', iv } as AesGcmParams, - } as const +export function decryptWithAES(key: CryptoKey, iv: Uint8Array, message: Uint8Array) { return Result.wrapAsync(async () => { - return new Uint8Array(await crypto.subtle.decrypt(param[kind], key, message)) + return new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, message)) }) } export function assertIVLengthEq16(arrayBuffer: Uint8Array) { diff --git a/packages/encryption/tests/__snapshots__/encryption.ts.snap b/packages/encryption/tests/__snapshots__/encryption.ts.snap index 876657ededf8..d2ff72e13f38 100644 --- a/packages/encryption/tests/__snapshots__/encryption.ts.snap +++ b/packages/encryption/tests/__snapshots__/encryption.ts.snap @@ -105,7 +105,7 @@ Object { "network": "localhost", "postIV": "Z/9AS037AaQi7QQQfq+OIw==", }, - "output": Uint8Array [ 9200c501329700a96c6f63616c686f7374a3626f6202c4583056301006072a8648ce3d020106052b8104000a03420004fbdd1a19eeb484e44ced760c141ff185b4bef2eb8998a052054cb7e4548716bf1032b7bbdae5dd881e40cf98e40d196bfd1f0dc2d20c3c8f71e6eb7330b5f1a19401c430e3b51fe6c57b5831460666e8ddf01b49ebb513848cc9eb014a4c322b3afbbf0af21a902912080179494adc12941bc3dbc41067ff404b4dfb01a422ed04107eaf8e2381a132c4583056301006072a8648ce3d020106052b8104000a03420004640c0574dbba0be00e7e6f0080fb2f4428f72245b5adb4d3bd95694d645a6b2423c6d09bab7075a1b9f63d7c6760189bc6b0cfa341ea1be9fe3d8b95fda37268c42251312d4d2137b78846f8cd793113e78726fef59ec68c2335a51d6a27d12d4c78172d ], + "output": Uint8Array [ 9200c4c49700a96c6f63616c686f7374a3626f6202c42103fbdd1a19eeb484e44ced760c141ff185b4bef2eb8998a052054cb7e4548716bf9401c430e3b51fe6c57b5831460666e8ddf01b49ebb513848cc9eb014a4c322b3afbbf0af21a902912080179494adc12941bc3dbc41067ff404b4dfb01a422ed04107eaf8e2381a132c42102640c0574dbba0be00e7e6f0080fb2f4428f72245b5adb4d3bd95694d645a6b24c42251312d4d2137b78846f8cd793113e78726fef59ec68c2335a51d6a27d12d4c78172d ], "postKey": CryptoKey { [opaque crypto key material] }, } `; @@ -148,7 +148,7 @@ Object { "network": "localhost", "postIV": "AAECAwQFBgcICQoLDA0ODw==", }, - "output": Uint8Array [ 9200c5011c9700a96c6f63616c686f7374a5616c69636502c4583056301006072a8648ce3d020106052b8104000a03420004afdb55600abe8749b944469377a78c4d60522bb64842ccf0820a225346a8e58c931d5975900006531c46a73f84b33a03755dfbe567ec504c470dd244440dd63e930092a74132353647434dd92b4a526872524b796b6d6e6d335362754e77364f6358465f6a69773067496c57335169574e5630316a656145c410000102030405060708090a0b0c0d0e0fc463422847d65139731573ca340118fa81883ae4cffa41cb7c91d378dd43c72ebad227c7af84e61f19fd4820fca920472d68a1076ccbe42f620eb73036bc7e13eb330d6f065c1e6ea02cb219d3e8a06a31a58d3d0b52bf8a8d03a0d001119d1b93f377ceab ], + "output": Uint8Array [ 9200c4d19700a96c6f63616c686f7374a5616c69636502c42102afdb55600abe8749b944469377a78c4d60522bb64842ccf0820a225346a8e58c9300c42025186b44aca49a79b749bb8dc3a39c5c5fe38b0d202255b742258d574d6379a1c410000102030405060708090a0b0c0d0e0fc463422847d65139731573ca340118fa81883ae4cffa41cb7c91d378dd43c72ebad227c7af84e61f19fd4820fca920472d68a1076ccbe42f620eb73036bc7e13eb330d6f065c1e6ea02cb219d3e8a06a31a58d3d0b52bf8a8d03a0d001119d1b93f377ceab ], "postKey": CryptoKey { [opaque crypto key material] }, } `; @@ -207,10 +207,7 @@ Object { })), "encrypted": Ok(Uint8Array [ 422847d65139731573ca340118fa81883ae4cffa41cb7c91d378dd43c72ebad227c7af84e61f19fd4820fca920472d68a1076ccbe42f620eb73036bc7e13eb330d6f065c1e6ea02cb219d3e8a06a31a58d3d0b52bf8a8d03a0d001119d1b93f377ceab ]), "encryption": Ok(Object { - "AESKey": Ok(Object { - "algr": "A256GCM", - "key": CryptoKey { [opaque crypto key material] }, - }), + "AESKey": Ok(CryptoKey { [opaque crypto key material] }), "iv": Ok(Uint8Array [ 000102030405060708090a0b0c0d0e0f ]), "type": "public", }), @@ -230,7 +227,7 @@ Object { "network": "localhost", "postIV": "AAECAwQFBgcICQoLDA0ODw==", }, - "output": Uint8Array [ 9200c4829700a96c6f63616c686f7374a5616c696365c0c0930092a74132353647434dd92b4a526872524b796b6d6e6d335362754e77364f6358465f6a69773067496c57335169574e5630316a656145c410000102030405060708090a0b0c0d0e0fc422422847d751786b14798828024dea879478f32c9aed87c5f427ae352faa7b50a13721 ], + "output": Uint8Array [ 9200c46e9700a96c6f63616c686f7374a5616c696365c0c09300c42025186b44aca49a79b749bb8dc3a39c5c5fe38b0d202255b742258d574d6379a1c410000102030405060708090a0b0c0d0e0fc422422847d751786b14798828024dea879478f32c9aed87c5f427ae352faa7b50a13721 ], "postKey": CryptoKey { [opaque crypto key material] }, } `; @@ -259,10 +256,7 @@ Object { "authorPublicKey": Ok(None), "encrypted": Ok(Uint8Array [ 422847d751786b14798828024dea879478f32c9aed87c5f427ae352faa7b50a13721 ]), "encryption": Ok(Object { - "AESKey": Ok(Object { - "algr": "A256GCM", - "key": CryptoKey { [opaque crypto key material] }, - }), + "AESKey": Ok(CryptoKey { [opaque crypto key material] }), "iv": Ok(Uint8Array [ 000102030405060708090a0b0c0d0e0f ]), "type": "public", }), @@ -443,10 +437,7 @@ Object { })), "encrypted": Ok(Uint8Array [ b84dbfba3f98b7136e8820505beab78cb64a1d7440403c589a989a ]), "encryption": Ok(Object { - "AESKey": Ok(Object { - "algr": "A256GCM", - "key": CryptoKey { [opaque crypto key material] }, - }), + "AESKey": Ok(CryptoKey { [opaque crypto key material] }), "iv": Ok(Uint8Array [ 000102030405060708090a0b0c0d0e0f ]), "type": "public", }), @@ -495,10 +486,7 @@ Object { "authorPublicKey": Ok(None), "encrypted": Ok(Uint8Array [ b84dbfba3f98b7136e8820505beab78cb64a1d7440403c589a989a ]), "encryption": Ok(Object { - "AESKey": Ok(Object { - "algr": "A256GCM", - "key": CryptoKey { [opaque crypto key material] }, - }), + "AESKey": Ok(CryptoKey { [opaque crypto key material] }), "iv": Ok(Uint8Array [ 000102030405060708090a0b0c0d0e0f ]), "type": "public", }), diff --git a/packages/encryption/tests/__snapshots__/v37-encode-decode.ts.snap b/packages/encryption/tests/__snapshots__/v37-encode-decode.ts.snap index 26cf9f425cc7..4c266abfd920 100644 --- a/packages/encryption/tests/__snapshots__/v37-encode-decode.ts.snap +++ b/packages/encryption/tests/__snapshots__/v37-encode-decode.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Encode v37 payload: Empty payload 1`] = `Uint8Array [ 9200c4579700c0c0c0c0930092a74132353647434dd92b4a526872524b796b6d6e6d335362754e77364f6358465f6a69773067496c57335169574e5630316a656145c410000102030405060708090a0b0c0d0e0fc4050001020304 ]`; +exports[`Encode v37 payload: Empty payload 1`] = `Uint8Array [ 9200c4439700c0c0c0c09300c42025186b44aca49a79b749bb8dc3a39c5c5fe38b0d202255b742258d574d6379a1c410000102030405060708090a0b0c0d0e0fc4050001020304 ]`; exports[`Encode v37 payload: Empty payload parsed 1`] = ` Object { @@ -8,10 +8,7 @@ Object { "authorPublicKey": Ok(None), "encrypted": Ok(Uint8Array [ 0001020304 ]), "encryption": Ok(Object { - "AESKey": Ok(Object { - "algr": "A256GCM", - "key": CryptoKey { [opaque crypto key material] }, - }), + "AESKey": Ok(CryptoKey { [opaque crypto key material] }), "iv": Ok(Uint8Array [ 000102030405060708090a0b0c0d0e0f ]), "type": "public", }), @@ -20,7 +17,7 @@ Object { } `; -exports[`Encode v37 payload: Full payload 1`] = `Uint8Array [ 9200c4ef9700a96c6f63616c686f7374a7756e6b6e6f776e02c4583056301006072a8648ce3d020106052b8104000a03420004afdb55600abe8749b944469377a78c4d60522bb64842ccf0820a225346a8e58c931d5975900006531c46a73f84b33a03755dfbe567ec504c470dd244440dd63e9401c40605060708090ac410000102030405060708090a0b0c0d0e0f81a132c4583056301006072a8648ce3d020106052b8104000a03420004afdb55600abe8749b944469377a78c4d60522bb64842ccf0820a225346a8e58c931d5975900006531c46a73f84b33a03755dfbe567ec504c470dd244440dd63ec4050001020304 ]`; +exports[`Encode v37 payload: Full payload 1`] = `Uint8Array [ 9200c4819700a96c6f63616c686f7374a7756e6b6e6f776e02c42102afdb55600abe8749b944469377a78c4d60522bb64842ccf0820a225346a8e58c9401c40605060708090ac410000102030405060708090a0b0c0d0e0f81a132c42102afdb55600abe8749b944469377a78c4d60522bb64842ccf0820a225346a8e58cc4050001020304 ]`; exports[`Encode v37 payload: Full payload parsed 1`] = ` Object { diff --git a/packages/encryption/tests/__snapshots__/v38-encode-decode.ts.snap b/packages/encryption/tests/__snapshots__/v38-encode-decode.ts.snap index df67c5c94d82..adbe3f504857 100644 --- a/packages/encryption/tests/__snapshots__/v38-encode-decode.ts.snap +++ b/packages/encryption/tests/__snapshots__/v38-encode-decode.ts.snap @@ -14,10 +14,7 @@ Ok(Object { })), "encrypted": Ok(Uint8Array [ 3a0d6ee692c6f46896b196f14301c01ad2fa26aa ]), "encryption": Ok(Object { - "AESKey": Ok(Object { - "algr": "A256GCM", - "key": CryptoKey { [opaque crypto key material] }, - }), + "AESKey": Ok(CryptoKey { [opaque crypto key material] }), "iv": Ok(Uint8Array [ 0633db7e24805c2bdcff69ea2afda7cd ]), "type": "public", }), @@ -54,10 +51,7 @@ Object { })), "encrypted": Ok(Uint8Array [ 3a0d6ee692c6f46896b196f14301c01ad2fa26aa ]), "encryption": Ok(Object { - "AESKey": Ok(Object { - "algr": "A256GCM", - "key": CryptoKey { [opaque crypto key material] }, - }), + "AESKey": Ok(CryptoKey { [opaque crypto key material] }), "iv": Ok(Uint8Array [ 0633db7e24805c2bdcff69ea2afda7cd ]), "type": "public", }), diff --git a/packages/encryption/tests/encryption.ts b/packages/encryption/tests/encryption.ts index e9204435fe6c..f53ac71741cc 100644 --- a/packages/encryption/tests/encryption.ts +++ b/packages/encryption/tests/encryption.ts @@ -10,7 +10,7 @@ import { EncryptOptions, parsePayload, } from '../src' -import { importAESFromJWK } from '../src/utils' +import { importAES } from '../src/utils' import { ProfileIdentifier } from '@masknet/shared-base' import { makeTypedMessageText, makeTypedMessageTupleSerializable } from '@masknet/typed-message' import { @@ -334,7 +334,7 @@ async function returnFalse(): Promise { return false } async function returnTestKey() { - return (await importAESFromJWK.AES_GCM_256(testKey)).unwrap() + return (await importAES(testKey)).unwrap() } function complexMessage() { diff --git a/packages/encryption/tests/v37-encode-decode.ts b/packages/encryption/tests/v37-encode-decode.ts index 58f85f2a6460..660a0e4d0556 100644 --- a/packages/encryption/tests/v37-encode-decode.ts +++ b/packages/encryption/tests/v37-encode-decode.ts @@ -1,7 +1,7 @@ import { test, expect } from '@jest/globals' import { None, Some } from 'ts-results' -import { encodePayload, AESAlgorithmEnum, parsePayload, PayloadWellFormed } from '../src' -import { importAESFromJWK } from '../src/utils' +import { encodePayload, parsePayload, PayloadWellFormed } from '../src' +import { importAES } from '../src/utils' import { ProfileIdentifier } from '@masknet/shared-base' import { queryTestPublicKey } from './keys' @@ -55,10 +55,7 @@ test('Encode v37 payload', async () => { }) async function getAESKey(): Promise { - return { - algr: AESAlgorithmEnum.A256GCM, - key: (await importAESFromJWK.AES_GCM_256(testKey)).unwrap(), - } + return (await importAES(testKey)).unwrap() } const testKey = { alg: 'A256GCM', diff --git a/packages/encryption/tests/v38-encode-decode.ts b/packages/encryption/tests/v38-encode-decode.ts index f08e35f39b53..489ceee6c246 100644 --- a/packages/encryption/tests/v38-encode-decode.ts +++ b/packages/encryption/tests/v38-encode-decode.ts @@ -1,8 +1,8 @@ import { expect, test } from '@jest/globals' -import { AESAlgorithmEnum, encodePayload, parsePayload, PayloadWellFormed } from '../src' +import { encodePayload, parsePayload, PayloadWellFormed } from '../src' import { None, Some } from 'ts-results' import { ProfileIdentifier } from '@masknet/shared-base' -import { importAESFromJWK } from '../src/utils' +import { importAES } from '../src/utils' import { queryTestPublicKey } from './keys' test('Parse v38 encoded by old infra', async () => { @@ -23,10 +23,7 @@ test('Encode v38 payload', async () => { encryption: { type: 'public', iv: new Uint8Array(Buffer.from('0633db7e24805c2bdcff69ea2afda7cd', 'hex')), - AESKey: { - algr: AESAlgorithmEnum.A256GCM, - key: await importAESFromJWK(AESKey, AESAlgorithmEnum.A256GCM).then((x) => x.unwrap() as any), - }, + AESKey: await importAES(AESKey).then((x) => x.unwrap()), }, signature: None, version: -38, diff --git a/packages/shared-base/src/crypto/secp256k1.ts b/packages/shared-base/src/crypto/secp256k1.ts index a9aecc240215..cb017bdac46e 100644 --- a/packages/shared-base/src/crypto/secp256k1.ts +++ b/packages/shared-base/src/crypto/secp256k1.ts @@ -49,6 +49,17 @@ export function decompressSecp256k1Point(point: Uint8Array): { x: string; y: str return { x: toBase64URL(x), y: toBase64URL(y) } } +export function compressSecp256k1KeyRaw(point: Uint8Array) { + if (!secp256k1.isPoint(point)) throw new TypeError('Not a point on secp256k1!') + if (secp256k1.isPointCompressed(point)) return point + return secp256k1.pointCompress(point, true) +} +export function decompressSecp256k1KeyRaw(point: Uint8Array) { + if (!secp256k1.isPoint(point)) throw new TypeError('Not a point on secp256k1!') + if (!secp256k1.isPointCompressed(point)) return point + return secp256k1.pointCompress(point, false) +} + export function compressSecp256k1Key(key: EC_JsonWebKey): string { const arr = compressSecp256k1Point(key.x!, key.y!) return encodeArrayBuffer(arr)