zapo-js 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/appstate/WaAppStateCrypto.js +49 -41
- package/dist/appstate/WaAppStateSyncClient.js +79 -42
- package/dist/appstate/index.js +2 -2
- package/dist/auth/WaAuthClient.js +20 -11
- package/dist/auth/{flow/WaAuthCredentialsFlow.js → credentials-flow.js} +83 -18
- package/dist/auth/pairing/WaPairingFlow.js +26 -29
- package/dist/auth/pairing/{WaPairingCodeCrypto.js → pairing-code-crypto.js} +29 -13
- package/dist/client/WaClient.js +115 -75
- package/dist/client/WaClientFactory.js +113 -30
- package/dist/client/connection/WaConnectionManager.js +4 -1
- package/dist/client/coordinators/WaAbPropsCoordinator.js +141 -0
- package/dist/client/coordinators/WaBusinessCoordinator.js +3 -12
- package/dist/client/coordinators/WaEmailCoordinator.js +63 -0
- package/dist/client/coordinators/WaIncomingNodeCoordinator.js +33 -8
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +55 -25
- package/dist/client/coordinators/WaOfflineResumeCoordinator.js +114 -0
- package/dist/client/coordinators/WaPassiveTasksCoordinator.js +38 -20
- package/dist/client/coordinators/WaProfileCoordinator.js +3 -1
- package/dist/client/coordinators/WaRetryCoordinator.js +11 -9
- package/dist/client/coordinators/WaTrustedContactTokenCoordinator.js +22 -4
- package/dist/client/dirty.js +1 -1
- package/dist/client/events/abprops.js +43 -0
- package/dist/client/events/privacy-token.js +1 -2
- package/dist/client/events/registration.js +42 -0
- package/dist/client/incoming.js +37 -0
- package/dist/client/mailbox.js +17 -1
- package/dist/client/media.js +243 -0
- package/dist/client/messages.js +163 -86
- package/dist/crypto/core/index.js +4 -1
- package/dist/crypto/core/random.js +3 -9
- package/dist/crypto/core/xeddsa.js +57 -0
- package/dist/crypto/curves/X25519.js +18 -0
- package/dist/crypto/curves/constants.js +2 -1
- package/dist/esm/appstate/WaAppStateCrypto.js +39 -31
- package/dist/esm/appstate/WaAppStateSyncClient.js +68 -31
- package/dist/esm/appstate/index.js +1 -1
- package/dist/esm/appstate/{WaAppStateSyncResponseParser.js → response-parser.js} +1 -1
- package/dist/esm/auth/WaAuthClient.js +17 -8
- package/dist/esm/auth/{flow/WaAuthCredentialsFlow.js → credentials-flow.js} +83 -18
- package/dist/esm/auth/pairing/WaPairingFlow.js +25 -28
- package/dist/esm/auth/pairing/{WaPairingCodeCrypto.js → pairing-code-crypto.js} +20 -6
- package/dist/esm/client/WaClient.js +116 -76
- package/dist/esm/client/WaClientFactory.js +114 -31
- package/dist/esm/client/connection/WaConnectionManager.js +4 -1
- package/dist/esm/client/coordinators/WaAbPropsCoordinator.js +137 -0
- package/dist/esm/client/coordinators/WaBusinessCoordinator.js +4 -13
- package/dist/esm/client/coordinators/WaEmailCoordinator.js +60 -0
- package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +35 -10
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +47 -17
- package/dist/esm/client/coordinators/WaOfflineResumeCoordinator.js +110 -0
- package/dist/esm/client/coordinators/WaPassiveTasksCoordinator.js +38 -20
- package/dist/esm/client/coordinators/WaProfileCoordinator.js +3 -1
- package/dist/esm/client/coordinators/WaRetryCoordinator.js +11 -9
- package/dist/esm/client/coordinators/WaTrustedContactTokenCoordinator.js +24 -6
- package/dist/esm/client/dirty.js +1 -1
- package/dist/esm/client/events/abprops.js +40 -0
- package/dist/esm/client/events/privacy-token.js +1 -2
- package/dist/esm/client/events/registration.js +39 -0
- package/dist/esm/client/incoming.js +36 -0
- package/dist/esm/client/mailbox.js +17 -1
- package/dist/esm/client/media.js +234 -0
- package/dist/esm/client/messages.js +162 -85
- package/dist/esm/crypto/core/index.js +1 -0
- package/dist/esm/crypto/core/random.js +2 -7
- package/dist/esm/crypto/core/xeddsa.js +53 -0
- package/dist/esm/crypto/curves/X25519.js +20 -2
- package/dist/esm/crypto/curves/constants.js +1 -0
- package/dist/esm/infra/perf/StoreLock.js +7 -4
- package/dist/esm/media/WaMediaCrypto.js +257 -62
- package/dist/esm/media/WaMediaTransferClient.js +47 -190
- package/dist/esm/media/constants.js +2 -0
- package/dist/esm/media/processor.js +1 -0
- package/dist/esm/message/addon-crypto.js +130 -3
- package/dist/esm/message/content.js +12 -6
- package/dist/esm/message/icdc.js +8 -8
- package/dist/esm/message/incoming.js +14 -12
- package/dist/esm/message/phash.js +32 -12
- package/dist/esm/message/reporting-token.js +3 -3
- package/dist/esm/message/use-case-secret.js +1 -1
- package/dist/esm/protocol/abprops.js +159 -0
- package/dist/esm/protocol/browser.js +14 -0
- package/dist/esm/protocol/constants.js +3 -1
- package/dist/esm/protocol/email.js +30 -0
- package/dist/esm/protocol/jid.js +44 -10
- package/dist/esm/protocol/nodes.js +6 -2
- package/dist/esm/protocol/notification.js +7 -1
- package/dist/esm/retry/reason.js +1 -1
- package/dist/esm/signal/api/SignalDeviceSyncApi.js +5 -2
- package/dist/esm/signal/api/SignalDigestSyncApi.js +8 -6
- package/dist/esm/signal/api/SignalIdentitySyncApi.js +4 -4
- package/dist/esm/signal/api/SignalMissingPreKeysSyncApi.js +1 -1
- package/dist/esm/signal/api/SignalSessionSyncApi.js +1 -1
- package/dist/esm/signal/crypto/WaAdvSignature.js +5 -51
- package/dist/esm/signal/crypto/constants.js +0 -4
- package/dist/esm/signal/encoding.js +11 -54
- package/dist/esm/signal/group/SenderKeyChain.js +3 -3
- package/dist/esm/signal/group/SenderKeyCodec.js +5 -6
- package/dist/esm/signal/group/SenderKeyManager.js +13 -10
- package/dist/esm/signal/registration/keygen.js +2 -3
- package/dist/esm/signal/registration/utils.js +2 -2
- package/dist/esm/signal/session/SignalProtocol.js +18 -17
- package/dist/esm/signal/session/SignalRatchet.js +21 -10
- package/dist/esm/signal/session/SignalSerializer.js +5 -6
- package/dist/esm/signal/session/SignalSession.js +11 -9
- package/dist/esm/signal/session/resolver.js +6 -6
- package/dist/esm/store/contracts/identity.store.js +1 -0
- package/dist/esm/store/contracts/message-secret.store.js +1 -0
- package/dist/esm/store/contracts/pre-key.store.js +1 -0
- package/dist/esm/store/contracts/session.store.js +1 -0
- package/dist/esm/store/createStore.js +48 -12
- package/dist/esm/store/index.js +4 -0
- package/dist/esm/store/locks/identity.lock.js +16 -0
- package/dist/esm/store/locks/message-secret.lock.js +17 -0
- package/dist/esm/store/locks/pre-key.lock.js +27 -0
- package/dist/esm/store/locks/session.lock.js +19 -0
- package/dist/esm/store/locks/signal.lock.js +0 -24
- package/dist/esm/store/noop.store.js +20 -0
- package/dist/esm/store/providers/memory/device-list.store.js +3 -0
- package/dist/esm/store/providers/memory/identity.store.js +31 -0
- package/dist/esm/store/providers/memory/message-secret.store.js +81 -0
- package/dist/esm/store/providers/memory/participants.store.js +3 -0
- package/dist/esm/store/providers/memory/pre-key.store.js +97 -0
- package/dist/esm/store/providers/memory/retry.store.js +25 -11
- package/dist/esm/store/providers/memory/session.store.js +45 -0
- package/dist/esm/store/providers/memory/signal.store.js +1 -164
- package/dist/esm/transport/WaComms.js +4 -3
- package/dist/esm/transport/WaWebSocket.js +9 -1
- package/dist/esm/transport/index.js +6 -0
- package/dist/esm/transport/keepalive/WaKeepAlive.js +17 -8
- package/dist/esm/transport/node/WaMobileTcpSocket.js +114 -0
- package/dist/esm/transport/node/WaNodeOrchestrator.js +17 -8
- package/dist/esm/transport/node/builders/abprops.js +20 -0
- package/dist/esm/transport/node/builders/device.js +11 -0
- package/dist/esm/transport/node/builders/email.js +65 -0
- package/dist/esm/transport/node/builders/offline.js +14 -0
- package/dist/esm/transport/node/builders/prekeys.js +37 -40
- package/dist/esm/transport/node/builders/presence.js +13 -0
- package/dist/esm/transport/node/builders/privacy-token.js +19 -23
- package/dist/esm/transport/node/builders/retry.js +1 -1
- package/dist/esm/transport/node/helpers.js +24 -0
- package/dist/esm/transport/node/mex/argo-decoder.js +152 -0
- package/dist/esm/transport/node/mex/client.js +83 -0
- package/dist/esm/transport/node/mex/persist-ids.js +10 -0
- package/dist/esm/transport/noise/WaClientPayload.js +15 -10
- package/dist/esm/transport/noise/WaFrameCodec.js +2 -2
- package/dist/esm/transport/noise/WaMobileClientPayload.js +53 -0
- package/dist/esm/transport/noise/WaNoiseCert.js +9 -27
- package/dist/esm/transport/noise/WaNoiseSession.js +12 -11
- package/dist/infra/perf/StoreLock.js +7 -4
- package/dist/media/WaMediaCrypto.js +253 -58
- package/dist/media/WaMediaTransferClient.js +50 -223
- package/dist/media/constants.js +3 -1
- package/dist/media/processor.js +2 -0
- package/dist/message/addon-crypto.js +131 -0
- package/dist/message/content.js +13 -5
- package/dist/message/icdc.js +8 -8
- package/dist/message/incoming.js +14 -12
- package/dist/message/phash.js +32 -12
- package/dist/message/reporting-token.js +2 -2
- package/dist/message/use-case-secret.js +1 -1
- package/dist/protocol/abprops.js +163 -0
- package/dist/protocol/browser.js +15 -0
- package/dist/protocol/constants.js +14 -2
- package/dist/protocol/email.js +33 -0
- package/dist/protocol/jid.js +45 -10
- package/dist/protocol/nodes.js +6 -2
- package/dist/protocol/notification.js +8 -2
- package/dist/retry/reason.js +1 -1
- package/dist/signal/api/SignalDeviceSyncApi.js +5 -2
- package/dist/signal/api/SignalDigestSyncApi.js +8 -6
- package/dist/signal/api/SignalIdentitySyncApi.js +4 -4
- package/dist/signal/crypto/WaAdvSignature.js +2 -50
- package/dist/signal/crypto/constants.js +1 -5
- package/dist/signal/encoding.js +11 -49
- package/dist/signal/group/SenderKeyChain.js +2 -2
- package/dist/signal/group/SenderKeyCodec.js +4 -5
- package/dist/signal/group/SenderKeyManager.js +12 -9
- package/dist/signal/registration/keygen.js +1 -2
- package/dist/signal/registration/utils.js +2 -2
- package/dist/signal/session/SignalProtocol.js +18 -17
- package/dist/signal/session/SignalRatchet.js +19 -8
- package/dist/signal/session/SignalSerializer.js +5 -6
- package/dist/signal/session/SignalSession.js +11 -9
- package/dist/signal/session/resolver.js +6 -6
- package/dist/store/contracts/identity.store.js +2 -0
- package/dist/store/contracts/message-secret.store.js +2 -0
- package/dist/store/contracts/pre-key.store.js +2 -0
- package/dist/store/contracts/session.store.js +2 -0
- package/dist/store/createStore.js +47 -11
- package/dist/store/index.js +9 -1
- package/dist/store/locks/identity.lock.js +19 -0
- package/dist/store/locks/message-secret.lock.js +20 -0
- package/dist/store/locks/pre-key.lock.js +30 -0
- package/dist/store/locks/session.lock.js +22 -0
- package/dist/store/locks/signal.lock.js +0 -24
- package/dist/store/noop.store.js +21 -1
- package/dist/store/providers/memory/device-list.store.js +3 -0
- package/dist/store/providers/memory/identity.store.js +35 -0
- package/dist/store/providers/memory/message-secret.store.js +85 -0
- package/dist/store/providers/memory/participants.store.js +3 -0
- package/dist/store/providers/memory/pre-key.store.js +101 -0
- package/dist/store/providers/memory/retry.store.js +24 -10
- package/dist/store/providers/memory/session.store.js +49 -0
- package/dist/store/providers/memory/signal.store.js +1 -164
- package/dist/transport/WaComms.js +4 -3
- package/dist/transport/WaWebSocket.js +9 -1
- package/dist/transport/index.js +17 -1
- package/dist/transport/keepalive/WaKeepAlive.js +17 -8
- package/dist/transport/node/WaMobileTcpSocket.js +118 -0
- package/dist/transport/node/WaNodeOrchestrator.js +16 -7
- package/dist/transport/node/builders/abprops.js +23 -0
- package/dist/transport/node/builders/device.js +14 -0
- package/dist/transport/node/builders/email.js +72 -0
- package/dist/transport/node/builders/offline.js +17 -0
- package/dist/transport/node/builders/prekeys.js +36 -39
- package/dist/transport/node/builders/presence.js +16 -0
- package/dist/transport/node/builders/privacy-token.js +18 -22
- package/dist/transport/node/builders/retry.js +1 -1
- package/dist/transport/node/helpers.js +26 -0
- package/dist/transport/node/mex/argo-decoder.js +189 -0
- package/dist/transport/node/mex/client.js +86 -0
- package/dist/transport/node/mex/persist-ids.js +13 -0
- package/dist/transport/noise/WaClientPayload.js +14 -9
- package/dist/transport/noise/WaFrameCodec.js +1 -1
- package/dist/transport/noise/WaMobileClientPayload.js +56 -0
- package/dist/transport/noise/WaNoiseCert.js +8 -26
- package/dist/transport/noise/WaNoiseSession.js +11 -10
- package/dist/types/appstate/WaAppStateCrypto.d.ts +11 -8
- package/dist/types/appstate/WaAppStateSyncClient.d.ts +6 -2
- package/dist/types/appstate/index.d.ts +1 -1
- package/dist/types/appstate/{WaAppStateSyncResponseParser.d.ts → response-parser.d.ts} +1 -1
- package/dist/types/appstate/types.d.ts +1 -1
- package/dist/types/auth/WaAuthClient.d.ts +9 -3
- package/dist/types/auth/credentials-flow.d.ts +20 -0
- package/dist/types/auth/pairing/WaPairingFlow.d.ts +3 -2
- package/dist/types/auth/pairing/{WaPairingCodeCrypto.d.ts → pairing-code-crypto.d.ts} +6 -1
- package/dist/types/auth/types.d.ts +40 -0
- package/dist/types/client/WaClient.d.ts +19 -8
- package/dist/types/client/WaClientFactory.d.ts +10 -4
- package/dist/types/client/coordinators/WaAbPropsCoordinator.d.ts +26 -0
- package/dist/types/client/coordinators/WaBusinessCoordinator.d.ts +1 -1
- package/dist/types/client/coordinators/WaEmailCoordinator.d.ts +24 -0
- package/dist/types/client/coordinators/WaIncomingNodeCoordinator.d.ts +6 -1
- package/dist/types/client/coordinators/WaMessageDispatchCoordinator.d.ts +15 -2
- package/dist/types/client/coordinators/WaOfflineResumeCoordinator.d.ts +31 -0
- package/dist/types/client/coordinators/WaPassiveTasksCoordinator.d.ts +13 -2
- package/dist/types/client/coordinators/WaPrivacyCoordinator.d.ts +1 -1
- package/dist/types/client/coordinators/WaProfileCoordinator.d.ts +4 -2
- package/dist/types/client/coordinators/WaRetryCoordinator.d.ts +6 -0
- package/dist/types/client/coordinators/WaTrustedContactTokenCoordinator.d.ts +11 -1
- package/dist/types/client/dirty.d.ts +3 -1
- package/dist/types/client/events/abprops.d.ts +14 -0
- package/dist/types/client/events/registration.d.ts +17 -0
- package/dist/types/client/incoming.d.ts +6 -1
- package/dist/types/client/mailbox.d.ts +2 -0
- package/dist/types/client/media.d.ts +31 -0
- package/dist/types/client/messages.d.ts +2 -0
- package/dist/types/client/persistence/WriteBehindPersistence.d.ts +1 -1
- package/dist/types/client/types.d.ts +100 -1
- package/dist/types/crypto/core/index.d.ts +1 -0
- package/dist/types/crypto/core/primitives.d.ts +1 -1
- package/dist/types/crypto/core/random.d.ts +1 -1
- package/dist/types/crypto/core/xeddsa.d.ts +2 -0
- package/dist/types/crypto/curves/constants.d.ts +1 -0
- package/dist/types/crypto/index.d.ts +1 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/infra/log/ConsoleLogger.d.ts +1 -1
- package/dist/types/infra/log/PinoLogger.d.ts +1 -1
- package/dist/types/infra/perf/StoreLock.d.ts +1 -0
- package/dist/types/media/WaMediaCrypto.d.ts +15 -6
- package/dist/types/media/WaMediaTransferClient.d.ts +3 -11
- package/dist/types/media/constants.d.ts +2 -0
- package/dist/types/media/index.d.ts +1 -0
- package/dist/types/media/processor.d.ts +28 -0
- package/dist/types/media/types.d.ts +9 -3
- package/dist/types/message/addon-crypto.d.ts +34 -3
- package/dist/types/message/content.d.ts +3 -1
- package/dist/types/message/icdc.d.ts +4 -4
- package/dist/types/message/types.d.ts +16 -24
- package/dist/types/protocol/abprops.d.ts +142 -0
- package/dist/types/protocol/browser.d.ts +1 -0
- package/dist/types/protocol/constants.d.ts +5 -1
- package/dist/types/protocol/email.d.ts +32 -0
- package/dist/types/protocol/jid.d.ts +1 -0
- package/dist/types/protocol/nodes.d.ts +4 -0
- package/dist/types/protocol/notification.d.ts +6 -0
- package/dist/types/protocol/stream.d.ts +1 -0
- package/dist/types/retry/reason.d.ts +1 -1
- package/dist/types/signal/api/SignalDigestSyncApi.d.ts +3 -0
- package/dist/types/signal/api/SignalIdentitySyncApi.d.ts +3 -3
- package/dist/types/signal/crypto/WaAdvSignature.d.ts +0 -2
- package/dist/types/signal/crypto/constants.d.ts +0 -1
- package/dist/types/signal/encoding.d.ts +7 -1
- package/dist/types/signal/group/SenderKeyChain.d.ts +1 -1
- package/dist/types/signal/group/SenderKeyManager.d.ts +7 -2
- package/dist/types/signal/registration/utils.d.ts +2 -1
- package/dist/types/signal/session/SignalProtocol.d.ts +11 -2
- package/dist/types/signal/session/SignalSerializer.d.ts +2 -1
- package/dist/types/signal/session/resolver.d.ts +4 -2
- package/dist/types/signal/types.d.ts +16 -4
- package/dist/types/store/contracts/identity.store.d.ts +11 -0
- package/dist/types/store/contracts/message-secret.store.d.ts +16 -0
- package/dist/types/store/contracts/pre-key.store.d.ts +13 -0
- package/dist/types/store/contracts/session.store.d.ts +14 -0
- package/dist/types/store/contracts/signal.store.d.ts +1 -34
- package/dist/types/store/index.d.ts +9 -1
- package/dist/types/store/locks/identity.lock.d.ts +3 -0
- package/dist/types/store/locks/message-secret.lock.d.ts +3 -0
- package/dist/types/store/locks/pre-key.lock.d.ts +3 -0
- package/dist/types/store/locks/session.lock.d.ts +3 -0
- package/dist/types/store/noop.store.d.ts +4 -0
- package/dist/types/store/providers/memory/identity.store.d.ts +18 -0
- package/dist/types/store/providers/memory/message-secret.store.d.ts +21 -0
- package/dist/types/store/providers/memory/pre-key.store.d.ts +23 -0
- package/dist/types/store/providers/memory/retry.store.d.ts +7 -1
- package/dist/types/store/providers/memory/session.store.d.ts +21 -0
- package/dist/types/store/providers/memory/signal.store.d.ts +3 -45
- package/dist/types/store/providers/memory/thread.store.d.ts +1 -1
- package/dist/types/store/types.d.ts +21 -1
- package/dist/types/transport/WaWebSocket.d.ts +1 -0
- package/dist/types/transport/index.d.ts +8 -1
- package/dist/types/transport/keepalive/WaKeepAlive.d.ts +4 -1
- package/dist/types/transport/node/WaMobileTcpSocket.d.ts +18 -0
- package/dist/types/transport/node/WaNodeOrchestrator.d.ts +6 -2
- package/dist/types/transport/node/builders/abprops.d.ts +5 -0
- package/dist/types/transport/node/builders/device.d.ts +2 -0
- package/dist/types/transport/node/builders/email.d.ts +11 -0
- package/dist/types/transport/node/builders/offline.d.ts +2 -0
- package/dist/types/transport/node/builders/prekeys.d.ts +4 -3
- package/dist/types/transport/node/builders/presence.d.ts +6 -0
- package/dist/types/transport/node/helpers.d.ts +3 -0
- package/dist/types/transport/node/mex/argo-decoder.d.ts +11 -0
- package/dist/types/transport/node/mex/client.d.ts +18 -0
- package/dist/types/transport/node/mex/persist-ids.d.ts +14 -0
- package/dist/types/transport/noise/WaMobileClientPayload.d.ts +29 -0
- package/dist/types/transport/noise/WaNoiseCert.d.ts +7 -1
- package/dist/types/transport/noise/WaNoiseSession.d.ts +1 -0
- package/dist/types/transport/types.d.ts +8 -0
- package/package.json +6 -4
- package/dist/auth/pairing/constants.js +0 -5
- package/dist/client/connection/WaKeyShareCoordinator.js +0 -63
- package/dist/esm/auth/pairing/constants.js +0 -2
- package/dist/esm/client/connection/WaKeyShareCoordinator.js +0 -59
- package/dist/esm/transport/node/builders/index.js +0 -11
- package/dist/transport/node/builders/index.js +0 -51
- package/dist/types/auth/flow/WaAuthCredentialsFlow.d.ts +0 -14
- package/dist/types/auth/pairing/constants.d.ts +0 -2
- package/dist/types/client/connection/WaKeyShareCoordinator.d.ts +0 -14
- package/dist/types/transport/node/builders/index.d.ts +0 -11
- /package/dist/appstate/{WaAppStateSyncResponseParser.js → response-parser.js} +0 -0
|
@@ -1,24 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { montgomeryToEdwardsPublic } from '../../crypto/curves/X25519.js';
|
|
1
|
+
import { xeddsaVerify } from '../../crypto/index.js';
|
|
3
2
|
import { proto } from '../../proto.js';
|
|
4
3
|
import { ROOT_CA_PUBLIC_KEY_HEX, ROOT_CA_SERIAL } from '../noise/constants.js';
|
|
5
4
|
import { assertByteLength, decodeProtoBytes, hexToBytes, uint8Equal } from '../../util/bytes.js';
|
|
6
5
|
import { toSafeNumber } from '../../util/primitives.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
const signature = new Uint8Array(signatureInput);
|
|
13
|
-
const lastByte = signature[63];
|
|
14
|
-
if ((lastByte & 0x60) !== 0) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
const signBit = lastByte & 0x80;
|
|
18
|
-
signature[63] = lastByte & 0x7f;
|
|
19
|
-
const edwardsPublicKey = montgomeryToEdwardsPublic(publicKey.subarray(1), signBit);
|
|
20
|
-
return Ed25519.verify(message, signature, edwardsPublicKey);
|
|
21
|
-
}
|
|
6
|
+
const PRODUCTION_ROOT_CA = {
|
|
7
|
+
publicKey: hexToBytes(ROOT_CA_PUBLIC_KEY_HEX),
|
|
8
|
+
serial: ROOT_CA_SERIAL
|
|
9
|
+
};
|
|
22
10
|
function parseNoiseCertificate(certificate) {
|
|
23
11
|
if (!certificate) {
|
|
24
12
|
throw new Error('missing noise certificate');
|
|
@@ -38,21 +26,16 @@ function parseNoiseCertificate(certificate) {
|
|
|
38
26
|
signature: signatureBytes
|
|
39
27
|
};
|
|
40
28
|
}
|
|
41
|
-
function
|
|
42
|
-
const raw = hexToBytes(ROOT_CA_PUBLIC_KEY_HEX);
|
|
43
|
-
return toSerializedPubKey(raw);
|
|
44
|
-
}
|
|
45
|
-
export async function verifyNoiseCertificateChain(certificateChain, serverStatic) {
|
|
29
|
+
export async function verifyNoiseCertificateChain(certificateChain, serverStatic, rootCa = PRODUCTION_ROOT_CA) {
|
|
46
30
|
const chain = proto.CertChain.decode(certificateChain);
|
|
47
31
|
if (!chain.leaf || !chain.intermediate) {
|
|
48
32
|
throw new Error('noise certificate chain is missing leaf/intermediate');
|
|
49
33
|
}
|
|
50
34
|
const intermediate = parseNoiseCertificate(chain.intermediate);
|
|
51
|
-
if (intermediate.issuerSerial !==
|
|
35
|
+
if (intermediate.issuerSerial !== rootCa.serial) {
|
|
52
36
|
throw new Error('intermediate certificate issuer mismatch');
|
|
53
37
|
}
|
|
54
|
-
const
|
|
55
|
-
const validIntermediate = await verifySignalVariant(rootKey, intermediate.details, intermediate.signature);
|
|
38
|
+
const validIntermediate = await xeddsaVerify(rootCa.publicKey, intermediate.details, intermediate.signature);
|
|
56
39
|
if (!validIntermediate) {
|
|
57
40
|
throw new Error('intermediate certificate signature is invalid');
|
|
58
41
|
}
|
|
@@ -60,8 +43,7 @@ export async function verifyNoiseCertificateChain(certificateChain, serverStatic
|
|
|
60
43
|
if (leaf.issuerSerial !== intermediate.serial) {
|
|
61
44
|
throw new Error('leaf certificate issuer mismatch');
|
|
62
45
|
}
|
|
63
|
-
const
|
|
64
|
-
const validLeaf = await verifySignalVariant(intermediatePublicSerialized, leaf.details, leaf.signature);
|
|
46
|
+
const validLeaf = await xeddsaVerify(intermediate.key, leaf.details, leaf.signature);
|
|
65
47
|
if (!validLeaf) {
|
|
66
48
|
throw new Error('leaf certificate signature is invalid');
|
|
67
49
|
}
|
|
@@ -7,13 +7,13 @@ import { buildLoginPayload, buildRegistrationPayload } from '../noise/WaClientPa
|
|
|
7
7
|
import { WaFrameCodec } from '../noise/WaFrameCodec.js';
|
|
8
8
|
import { verifyNoiseCertificateChain } from '../noise/WaNoiseCert.js';
|
|
9
9
|
import { WaNoiseHandshake } from '../noise/WaNoiseHandshake.js';
|
|
10
|
-
import { concatBytes
|
|
10
|
+
import { concatBytes } from '../../util/bytes.js';
|
|
11
11
|
import { toError } from '../../util/primitives.js';
|
|
12
12
|
function resolvePayload(payload) {
|
|
13
13
|
if (payload instanceof Uint8Array) {
|
|
14
14
|
return Promise.resolve(payload);
|
|
15
15
|
}
|
|
16
|
-
return Promise.resolve(payload())
|
|
16
|
+
return Promise.resolve(payload());
|
|
17
17
|
}
|
|
18
18
|
async function resolveHandshakePayload(config) {
|
|
19
19
|
if (config.isRegistered) {
|
|
@@ -58,6 +58,7 @@ export class WaNoiseSession {
|
|
|
58
58
|
this.closedError = null;
|
|
59
59
|
this.noiseSocket = null;
|
|
60
60
|
this.serverStaticKey = null;
|
|
61
|
+
this.trustedRootCa = undefined;
|
|
61
62
|
this.handshakeFrameTimeoutMs = WA_DEFAULTS.CONNECT_TIMEOUT_MS;
|
|
62
63
|
this.sendWire = sendWire;
|
|
63
64
|
this.logger = logger;
|
|
@@ -68,9 +69,7 @@ export class WaNoiseSession {
|
|
|
68
69
|
isRegistered: config.isRegistered,
|
|
69
70
|
hasServerStaticKey: !!config.serverStaticKey
|
|
70
71
|
});
|
|
71
|
-
const protocolHeader = config.protocolHeader
|
|
72
|
-
? toBytesView(config.protocolHeader)
|
|
73
|
-
: WA_PROTO_HEADER;
|
|
72
|
+
const protocolHeader = config.protocolHeader ?? WA_PROTO_HEADER;
|
|
74
73
|
const introFrame = config.routingInfo && config.routingInfo.length > 0
|
|
75
74
|
? concatBytes([buildRoutingInfoPrefix(config.routingInfo), protocolHeader])
|
|
76
75
|
: protocolHeader;
|
|
@@ -80,6 +79,7 @@ export class WaNoiseSession {
|
|
|
80
79
|
resolveHandshakePayload(config)
|
|
81
80
|
]);
|
|
82
81
|
const verifyCertificates = config.verifyCertificateChain !== false;
|
|
82
|
+
this.trustedRootCa = config.trustedRootCa;
|
|
83
83
|
if (config.serverStaticKey && config.serverStaticKey.length === 32) {
|
|
84
84
|
this.logger.info('noise session attempting resume handshake (IK)');
|
|
85
85
|
this.noiseSocket = await this.resumeHandshake(config.serverStaticKey, config.clientStaticKeyPair, ephemeralKeyPair, payload, protocolHeader, verifyCertificates);
|
|
@@ -181,6 +181,7 @@ export class WaNoiseSession {
|
|
|
181
181
|
this.closedError = null;
|
|
182
182
|
this.noiseSocket = null;
|
|
183
183
|
this.serverStaticKey = null;
|
|
184
|
+
this.trustedRootCa = undefined;
|
|
184
185
|
this.writeChain = Promise.resolve();
|
|
185
186
|
this.readChain = Promise.resolve();
|
|
186
187
|
}
|
|
@@ -244,7 +245,7 @@ export class WaNoiseSession {
|
|
|
244
245
|
if (!serverHello.payload) {
|
|
245
246
|
throw new Error('noise resume handshake missing certificate payload');
|
|
246
247
|
}
|
|
247
|
-
const serverEphemeral =
|
|
248
|
+
const serverEphemeral = serverHello.ephemeral;
|
|
248
249
|
await handshake.authenticate(serverEphemeral);
|
|
249
250
|
const [dh1, dh2] = await Promise.all([
|
|
250
251
|
X25519.scalarMult(ephemeralKeyPair.privKey, serverEphemeral),
|
|
@@ -252,7 +253,7 @@ export class WaNoiseSession {
|
|
|
252
253
|
]);
|
|
253
254
|
await handshake.mixIntoKey(dh1);
|
|
254
255
|
await handshake.mixIntoKey(dh2);
|
|
255
|
-
await handshake.decrypt(
|
|
256
|
+
await handshake.decrypt(serverHello.payload);
|
|
256
257
|
this.serverStaticKey = serverStaticKey;
|
|
257
258
|
this.logger.info('noise resume handshake successful without fallback');
|
|
258
259
|
return { socket: await handshake.finish(), serverHelloFrame: null };
|
|
@@ -270,14 +271,14 @@ export class WaNoiseSession {
|
|
|
270
271
|
if (!serverHello?.ephemeral || !serverHello.static || !serverHello.payload) {
|
|
271
272
|
throw new Error('noise full handshake missing server hello fields');
|
|
272
273
|
}
|
|
273
|
-
const serverEphemeral =
|
|
274
|
+
const serverEphemeral = serverHello.ephemeral;
|
|
274
275
|
await handshake.authenticate(serverEphemeral);
|
|
275
276
|
await handshake.mixIntoKey(await X25519.scalarMult(ephemeralKeyPair.privKey, serverEphemeral));
|
|
276
|
-
const serverStatic = await handshake.decrypt(
|
|
277
|
+
const serverStatic = await handshake.decrypt(serverHello.static);
|
|
277
278
|
await handshake.mixIntoKey(await X25519.scalarMult(ephemeralKeyPair.privKey, serverStatic));
|
|
278
|
-
const certificate = await handshake.decrypt(
|
|
279
|
+
const certificate = await handshake.decrypt(serverHello.payload);
|
|
279
280
|
if (verifyCertificates) {
|
|
280
|
-
await verifyNoiseCertificateChain(certificate, serverStatic);
|
|
281
|
+
await verifyNoiseCertificateChain(certificate, serverStatic, this.trustedRootCa);
|
|
281
282
|
this.logger.trace('noise certificate chain verified');
|
|
282
283
|
}
|
|
283
284
|
this.serverStaticKey = serverStatic;
|
|
@@ -28,12 +28,14 @@ class StoreLock {
|
|
|
28
28
|
current = Promise.reject(error);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
const tracker = current.then(
|
|
31
|
+
const tracker = current.then(StoreLock._noop, StoreLock._noop);
|
|
32
32
|
this.chains.set(key, tracker);
|
|
33
|
-
|
|
34
|
-
if (this.chains.get(key) === tracker)
|
|
33
|
+
tracker.then(() => {
|
|
34
|
+
if (this.chains.get(key) === tracker)
|
|
35
|
+
this.chains.delete(key);
|
|
36
|
+
}, () => {
|
|
37
|
+
if (this.chains.get(key) === tracker)
|
|
35
38
|
this.chains.delete(key);
|
|
36
|
-
}
|
|
37
39
|
});
|
|
38
40
|
return current;
|
|
39
41
|
}
|
|
@@ -79,3 +81,4 @@ class StoreLock {
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
exports.StoreLock = StoreLock;
|
|
84
|
+
StoreLock._noop = () => undefined;
|
|
@@ -15,6 +15,87 @@ const constants_1 = require("./constants");
|
|
|
15
15
|
const constants_2 = require("../protocol/constants");
|
|
16
16
|
const bytes_1 = require("../util/bytes");
|
|
17
17
|
const primitives_2 = require("../util/primitives");
|
|
18
|
+
const AES_BLOCK_SIZE = 16;
|
|
19
|
+
const PKCS7_FULL_BLOCK = new Uint8Array(AES_BLOCK_SIZE).fill(AES_BLOCK_SIZE);
|
|
20
|
+
async function aesCbcEncryptChunk(key, iv, chunk, isFinal) {
|
|
21
|
+
const encrypted = await (0, primitives_1.aesCbcEncrypt)(key, iv, chunk);
|
|
22
|
+
if (isFinal) {
|
|
23
|
+
return {
|
|
24
|
+
ciphertext: encrypted,
|
|
25
|
+
nextIv: encrypted.subarray(encrypted.byteLength - AES_BLOCK_SIZE)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const ciphertext = encrypted.subarray(0, encrypted.byteLength - AES_BLOCK_SIZE);
|
|
29
|
+
return {
|
|
30
|
+
ciphertext,
|
|
31
|
+
nextIv: ciphertext.subarray(ciphertext.byteLength - AES_BLOCK_SIZE)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function aesCbcDecryptChunk(key, iv, ciphertext, isFinal) {
|
|
35
|
+
const nextIv = (0, bytes_1.toBytesView)(ciphertext.subarray(ciphertext.byteLength - AES_BLOCK_SIZE));
|
|
36
|
+
if (isFinal) {
|
|
37
|
+
return { plaintext: await (0, primitives_1.aesCbcDecrypt)(key, iv, ciphertext), nextIv };
|
|
38
|
+
}
|
|
39
|
+
const padBlock = (await (0, primitives_1.aesCbcEncrypt)(key, nextIv, PKCS7_FULL_BLOCK)).subarray(0, AES_BLOCK_SIZE);
|
|
40
|
+
const withPad = (0, bytes_1.concatBytes)([ciphertext, padBlock]);
|
|
41
|
+
return { plaintext: await (0, primitives_1.aesCbcDecrypt)(key, iv, withPad), nextIv };
|
|
42
|
+
}
|
|
43
|
+
async function computeFirstFrameSidecar(macKey, ivCiphertext, firstFrameLength) {
|
|
44
|
+
const aligned = Math.ceil(firstFrameLength / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
|
|
45
|
+
const slice = ivCiphertext.subarray(0, constants_1.IV_SIZE + aligned);
|
|
46
|
+
const key = await (0, primitives_1.importHmacKey)(macKey);
|
|
47
|
+
const digest = await (0, primitives_1.hmacSign)(key, slice);
|
|
48
|
+
return digest.subarray(0, constants_1.SIDECAR_HMAC_SIZE);
|
|
49
|
+
}
|
|
50
|
+
class SidecarAccumulator {
|
|
51
|
+
constructor(macKey, estimatedSize = 0) {
|
|
52
|
+
this.resultOffset = 0;
|
|
53
|
+
this.totalPushed = 0;
|
|
54
|
+
this.windowOffset = 0;
|
|
55
|
+
this.nextChunkStart = 0;
|
|
56
|
+
this.macKey = macKey;
|
|
57
|
+
this.window = new Uint8Array(constants_1.IV_SIZE + constants_1.SIDECAR_CHUNK_SIZE);
|
|
58
|
+
const estimated = Math.max(Math.ceil(estimatedSize / constants_1.SIDECAR_CHUNK_SIZE) + 1, 16);
|
|
59
|
+
this.result = new Uint8Array(estimated * constants_1.SIDECAR_HMAC_SIZE);
|
|
60
|
+
}
|
|
61
|
+
push(data) {
|
|
62
|
+
let srcOffset = 0;
|
|
63
|
+
while (srcOffset < data.byteLength) {
|
|
64
|
+
const windowEnd = this.nextChunkStart + constants_1.IV_SIZE + constants_1.SIDECAR_CHUNK_SIZE;
|
|
65
|
+
const remaining = windowEnd - this.totalPushed;
|
|
66
|
+
const toCopy = Math.min(remaining, data.byteLength - srcOffset);
|
|
67
|
+
this.window.set(data.subarray(srcOffset, srcOffset + toCopy), this.windowOffset);
|
|
68
|
+
this.windowOffset += toCopy;
|
|
69
|
+
this.totalPushed += toCopy;
|
|
70
|
+
srcOffset += toCopy;
|
|
71
|
+
if (this.totalPushed === windowEnd) {
|
|
72
|
+
this.flushChunk();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
finish() {
|
|
77
|
+
if (this.windowOffset > 0) {
|
|
78
|
+
this.flushChunk();
|
|
79
|
+
}
|
|
80
|
+
return this.result.subarray(0, this.resultOffset);
|
|
81
|
+
}
|
|
82
|
+
flushChunk() {
|
|
83
|
+
const digest = (0, node_crypto_1.createHmac)('sha256', this.macKey)
|
|
84
|
+
.update(this.window.subarray(0, this.windowOffset))
|
|
85
|
+
.digest();
|
|
86
|
+
if (this.resultOffset + constants_1.SIDECAR_HMAC_SIZE > this.result.byteLength) {
|
|
87
|
+
const grown = new Uint8Array(this.result.byteLength * 2);
|
|
88
|
+
grown.set(this.result);
|
|
89
|
+
this.result = grown;
|
|
90
|
+
}
|
|
91
|
+
this.result.set(digest.subarray(0, constants_1.SIDECAR_HMAC_SIZE), this.resultOffset);
|
|
92
|
+
this.resultOffset += constants_1.SIDECAR_HMAC_SIZE;
|
|
93
|
+
this.nextChunkStart += constants_1.SIDECAR_CHUNK_SIZE;
|
|
94
|
+
const overlapSrc = this.window.subarray(this.windowOffset - constants_1.IV_SIZE, this.windowOffset);
|
|
95
|
+
this.window.set(overlapSrc, 0);
|
|
96
|
+
this.windowOffset = constants_1.IV_SIZE;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
18
99
|
class WaMediaCrypto {
|
|
19
100
|
static async generateMediaKey() {
|
|
20
101
|
return (0, random_1.randomBytesAsync)(32);
|
|
@@ -30,24 +111,41 @@ class WaMediaCrypto {
|
|
|
30
111
|
refKey: expanded.subarray(constants_1.MAC_KEY_END, constants_1.MEDIA_HKDF_SIZE)
|
|
31
112
|
};
|
|
32
113
|
}
|
|
33
|
-
static async encryptBytes(mediaType, mediaKey, plaintext) {
|
|
114
|
+
static async encryptBytes(mediaType, mediaKey, plaintext, options) {
|
|
34
115
|
const keys = await WaMediaCrypto.deriveKeys(mediaType, mediaKey);
|
|
35
|
-
const [aesKey,
|
|
116
|
+
const [aesKey, hmacKey] = await Promise.all([
|
|
36
117
|
(0, primitives_1.importAesCbcKey)(keys.encKey),
|
|
37
118
|
(0, primitives_1.importHmacKey)(keys.macKey)
|
|
38
119
|
]);
|
|
39
120
|
const ciphertext = await (0, primitives_1.aesCbcEncrypt)(aesKey, keys.iv, plaintext);
|
|
40
121
|
const ivCiphertext = (0, bytes_1.concatBytes)([keys.iv, ciphertext]);
|
|
41
|
-
const mac = await (0, primitives_1.hmacSign)(
|
|
122
|
+
const mac = await (0, primitives_1.hmacSign)(hmacKey, ivCiphertext);
|
|
42
123
|
const signature = mac.subarray(0, constants_1.HMAC_TRUNCATED_SIZE);
|
|
43
124
|
const ciphertextHmac = (0, bytes_1.concatBytes)([ciphertext, signature]);
|
|
125
|
+
let streamingSidecar;
|
|
126
|
+
if (options?.sidecar !== false) {
|
|
127
|
+
const acc = new SidecarAccumulator(keys.macKey);
|
|
128
|
+
acc.push(keys.iv);
|
|
129
|
+
acc.push(ciphertext);
|
|
130
|
+
acc.push(signature);
|
|
131
|
+
streamingSidecar = acc.finish();
|
|
132
|
+
}
|
|
133
|
+
const firstFrameSidecar = options?.firstFrameLength !== undefined
|
|
134
|
+
? await computeFirstFrameSidecar(keys.macKey, ivCiphertext, options.firstFrameLength)
|
|
135
|
+
: undefined;
|
|
44
136
|
const [fileSha256, fileEncSha256] = await Promise.all([
|
|
45
137
|
(0, primitives_1.sha256)(plaintext),
|
|
46
138
|
(0, primitives_1.sha256)(ciphertextHmac)
|
|
47
139
|
]);
|
|
48
|
-
return {
|
|
140
|
+
return {
|
|
141
|
+
ciphertextHmac,
|
|
142
|
+
fileSha256,
|
|
143
|
+
fileEncSha256,
|
|
144
|
+
streamingSidecar,
|
|
145
|
+
firstFrameSidecar
|
|
146
|
+
};
|
|
49
147
|
}
|
|
50
|
-
static async decryptBytes(mediaType, mediaKey, ciphertextHmac, expectedFileSha256, expectedFileEncSha256) {
|
|
148
|
+
static async decryptBytes(mediaType, mediaKey, ciphertextHmac, expectedFileSha256, expectedFileEncSha256, skipMacVerification = false) {
|
|
51
149
|
if (ciphertextHmac.byteLength < constants_1.HMAC_TRUNCATED_SIZE) {
|
|
52
150
|
throw new Error(`ciphertext too short: ${ciphertextHmac.byteLength}`);
|
|
53
151
|
}
|
|
@@ -58,17 +156,19 @@ class WaMediaCrypto {
|
|
|
58
156
|
}
|
|
59
157
|
}
|
|
60
158
|
const keys = await WaMediaCrypto.deriveKeys(mediaType, mediaKey);
|
|
159
|
+
const [aesKey, hmacKey] = await Promise.all([
|
|
160
|
+
(0, primitives_1.importAesCbcKey)(keys.encKey),
|
|
161
|
+
(0, primitives_1.importHmacKey)(keys.macKey)
|
|
162
|
+
]);
|
|
61
163
|
const ciphertext = ciphertextHmac.subarray(0, ciphertextHmac.byteLength - constants_1.HMAC_TRUNCATED_SIZE);
|
|
62
164
|
const expectedMac = ciphertextHmac.subarray(ciphertextHmac.byteLength - constants_1.HMAC_TRUNCATED_SIZE);
|
|
63
165
|
const ivCiphertext = (0, bytes_1.concatBytes)([keys.iv, ciphertext]);
|
|
64
|
-
|
|
65
|
-
(0, primitives_1.
|
|
66
|
-
(0,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!(0, bytes_1.uint8TimingSafeEqual)(signature, expectedMac)) {
|
|
71
|
-
throw new Error('media MAC mismatch');
|
|
166
|
+
if (!skipMacVerification) {
|
|
167
|
+
const mac = await (0, primitives_1.hmacSign)(hmacKey, ivCiphertext);
|
|
168
|
+
const signature = mac.subarray(0, constants_1.HMAC_TRUNCATED_SIZE);
|
|
169
|
+
if (!(0, bytes_1.uint8TimingSafeEqual)(signature, expectedMac)) {
|
|
170
|
+
throw new Error('media MAC mismatch');
|
|
171
|
+
}
|
|
72
172
|
}
|
|
73
173
|
const plaintext = await (0, primitives_1.aesCbcDecrypt)(aesKey, keys.iv, ciphertext);
|
|
74
174
|
const fileSha256 = await (0, primitives_1.sha256)(plaintext);
|
|
@@ -78,18 +178,18 @@ class WaMediaCrypto {
|
|
|
78
178
|
const fileEncSha256 = expectedFileEncSha256 ?? (await (0, primitives_1.sha256)(ciphertextHmac));
|
|
79
179
|
return { plaintext, fileSha256, fileEncSha256 };
|
|
80
180
|
}
|
|
81
|
-
static async encryptReadable(mediaType, mediaKey, plaintext) {
|
|
181
|
+
static async encryptReadable(mediaType, mediaKey, plaintext, options) {
|
|
82
182
|
const keys = await WaMediaCrypto.deriveKeys(mediaType, mediaKey);
|
|
83
183
|
const encrypted = new node_stream_1.PassThrough();
|
|
84
|
-
const metadata = pumpEncryption(plaintext, encrypted, keys);
|
|
184
|
+
const metadata = pumpEncryption(plaintext, encrypted, keys, options?.sidecar !== false, options?.firstFrameLength);
|
|
85
185
|
return { encrypted, metadata };
|
|
86
186
|
}
|
|
87
|
-
static async encryptToFile(mediaType, mediaKey, plaintext) {
|
|
187
|
+
static async encryptToFile(mediaType, mediaKey, plaintext, options) {
|
|
88
188
|
const keys = await WaMediaCrypto.deriveKeys(mediaType, mediaKey);
|
|
89
189
|
const filePath = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `zapo-enc-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
90
190
|
const output = (0, node_fs_1.createWriteStream)(filePath);
|
|
91
191
|
try {
|
|
92
|
-
const metadata = await pumpEncryptionToWritable(plaintext, output, keys);
|
|
192
|
+
const metadata = await pumpEncryptionToWritable(plaintext, output, keys, options?.sidecar !== false, options?.firstFrameLength);
|
|
93
193
|
const fileSize = (await (0, promises_1.stat)(filePath)).size;
|
|
94
194
|
return { filePath, fileSize, ...metadata };
|
|
95
195
|
}
|
|
@@ -116,42 +216,80 @@ class WaMediaCrypto {
|
|
|
116
216
|
}
|
|
117
217
|
}
|
|
118
218
|
exports.WaMediaCrypto = WaMediaCrypto;
|
|
119
|
-
async function pumpEncryption(plaintext, encrypted, keys) {
|
|
219
|
+
async function pumpEncryption(plaintext, encrypted, keys, computeSidecar, firstFrameLength) {
|
|
220
|
+
const aesKey = await (0, primitives_1.importAesCbcKey)(keys.encKey);
|
|
120
221
|
const plainHash = (0, node_crypto_1.createHash)('sha256');
|
|
121
222
|
const encHash = (0, node_crypto_1.createHash)('sha256');
|
|
122
223
|
const hmac = (0, node_crypto_1.createHmac)('sha256', keys.macKey);
|
|
123
|
-
const
|
|
224
|
+
const sidecar = computeSidecar ? new SidecarAccumulator(keys.macKey) : null;
|
|
225
|
+
const ffTarget = firstFrameLength !== undefined
|
|
226
|
+
? constants_1.IV_SIZE + Math.ceil(firstFrameLength / AES_BLOCK_SIZE) * AES_BLOCK_SIZE
|
|
227
|
+
: 0;
|
|
228
|
+
let ffCollected = 0;
|
|
229
|
+
const ffChunks = ffTarget > 0 ? [keys.iv] : [];
|
|
230
|
+
if (ffTarget > 0)
|
|
231
|
+
ffCollected = constants_1.IV_SIZE;
|
|
124
232
|
let plaintextLength = 0;
|
|
233
|
+
let currentIv = keys.iv;
|
|
234
|
+
let pending = bytes_1.EMPTY_BYTES;
|
|
125
235
|
hmac.update(keys.iv);
|
|
236
|
+
sidecar?.push(keys.iv);
|
|
126
237
|
try {
|
|
127
238
|
for await (const chunk of plaintext) {
|
|
128
239
|
const plainChunk = (0, bytes_1.toChunkBytes)(chunk);
|
|
129
|
-
if (plainChunk.byteLength === 0)
|
|
240
|
+
if (plainChunk.byteLength === 0)
|
|
130
241
|
continue;
|
|
131
|
-
}
|
|
132
242
|
plaintextLength += plainChunk.byteLength;
|
|
133
243
|
plainHash.update(plainChunk);
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
244
|
+
const combined = pending.byteLength > 0
|
|
245
|
+
? (0, bytes_1.concatBytes)([pending, plainChunk])
|
|
246
|
+
: (0, bytes_1.toBytesView)(plainChunk);
|
|
247
|
+
const aligned = combined.byteLength - (combined.byteLength % AES_BLOCK_SIZE);
|
|
248
|
+
if (aligned === 0) {
|
|
249
|
+
pending = combined;
|
|
250
|
+
continue;
|
|
139
251
|
}
|
|
252
|
+
const toEncrypt = (0, bytes_1.toBytesView)(combined.subarray(0, aligned));
|
|
253
|
+
pending = (0, bytes_1.toBytesView)(combined.subarray(aligned));
|
|
254
|
+
const { ciphertext, nextIv } = await aesCbcEncryptChunk(aesKey, currentIv, toEncrypt, false);
|
|
255
|
+
currentIv = nextIv;
|
|
256
|
+
hmac.update(ciphertext);
|
|
257
|
+
encHash.update(ciphertext);
|
|
258
|
+
sidecar?.push(ciphertext);
|
|
259
|
+
if (ffCollected < ffTarget) {
|
|
260
|
+
const need = ffTarget - ffCollected;
|
|
261
|
+
ffChunks.push(ciphertext.subarray(0, Math.min(need, ciphertext.byteLength)));
|
|
262
|
+
ffCollected += ciphertext.byteLength;
|
|
263
|
+
}
|
|
264
|
+
await writeChunk(encrypted, ciphertext);
|
|
140
265
|
}
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
266
|
+
const { ciphertext: finalCiphertext } = await aesCbcEncryptChunk(aesKey, currentIv, pending, true);
|
|
267
|
+
hmac.update(finalCiphertext);
|
|
268
|
+
encHash.update(finalCiphertext);
|
|
269
|
+
sidecar?.push(finalCiphertext);
|
|
270
|
+
if (ffCollected < ffTarget) {
|
|
271
|
+
const need = ffTarget - ffCollected;
|
|
272
|
+
ffChunks.push(finalCiphertext.subarray(0, Math.min(need, finalCiphertext.byteLength)));
|
|
146
273
|
}
|
|
147
|
-
|
|
274
|
+
await writeChunk(encrypted, finalCiphertext);
|
|
275
|
+
const signature = (0, bytes_1.toBytesView)(hmac.digest().subarray(0, constants_1.HMAC_TRUNCATED_SIZE));
|
|
148
276
|
encHash.update(signature);
|
|
277
|
+
sidecar?.push(signature);
|
|
149
278
|
await writeChunk(encrypted, signature);
|
|
150
279
|
encrypted.end();
|
|
280
|
+
let firstFrameSidecar;
|
|
281
|
+
if (ffTarget > 0) {
|
|
282
|
+
const ivCiphertextSlice = (0, bytes_1.concatBytes)(ffChunks);
|
|
283
|
+
const ffKey = await (0, primitives_1.importHmacKey)(keys.macKey);
|
|
284
|
+
const ffDigest = await (0, primitives_1.hmacSign)(ffKey, ivCiphertextSlice);
|
|
285
|
+
firstFrameSidecar = ffDigest.subarray(0, constants_1.SIDECAR_HMAC_SIZE);
|
|
286
|
+
}
|
|
151
287
|
return {
|
|
152
288
|
fileSha256: (0, bytes_1.toBytesView)(plainHash.digest()),
|
|
153
289
|
fileEncSha256: (0, bytes_1.toBytesView)(encHash.digest()),
|
|
154
|
-
plaintextLength
|
|
290
|
+
plaintextLength,
|
|
291
|
+
streamingSidecar: sidecar?.finish(),
|
|
292
|
+
firstFrameSidecar
|
|
155
293
|
};
|
|
156
294
|
}
|
|
157
295
|
catch (error) {
|
|
@@ -186,42 +324,80 @@ async function endWritable(stream) {
|
|
|
186
324
|
stream.end(() => resolve());
|
|
187
325
|
});
|
|
188
326
|
}
|
|
189
|
-
async function pumpEncryptionToWritable(plaintext, output, keys) {
|
|
327
|
+
async function pumpEncryptionToWritable(plaintext, output, keys, computeSidecar, firstFrameLength) {
|
|
328
|
+
const aesKey = await (0, primitives_1.importAesCbcKey)(keys.encKey);
|
|
190
329
|
const plainHash = (0, node_crypto_1.createHash)('sha256');
|
|
191
330
|
const encHash = (0, node_crypto_1.createHash)('sha256');
|
|
192
331
|
const hmac = (0, node_crypto_1.createHmac)('sha256', keys.macKey);
|
|
193
|
-
const
|
|
332
|
+
const sidecar = computeSidecar ? new SidecarAccumulator(keys.macKey) : null;
|
|
333
|
+
const ffTarget = firstFrameLength !== undefined
|
|
334
|
+
? constants_1.IV_SIZE + Math.ceil(firstFrameLength / AES_BLOCK_SIZE) * AES_BLOCK_SIZE
|
|
335
|
+
: 0;
|
|
336
|
+
let ffCollected = 0;
|
|
337
|
+
const ffChunks = ffTarget > 0 ? [keys.iv] : [];
|
|
338
|
+
if (ffTarget > 0)
|
|
339
|
+
ffCollected = constants_1.IV_SIZE;
|
|
194
340
|
let plaintextLength = 0;
|
|
341
|
+
let currentIv = keys.iv;
|
|
342
|
+
let pending = bytes_1.EMPTY_BYTES;
|
|
195
343
|
hmac.update(keys.iv);
|
|
344
|
+
sidecar?.push(keys.iv);
|
|
196
345
|
try {
|
|
197
346
|
for await (const chunk of plaintext) {
|
|
198
347
|
const plainChunk = (0, bytes_1.toChunkBytes)(chunk);
|
|
199
|
-
if (plainChunk.byteLength === 0)
|
|
348
|
+
if (plainChunk.byteLength === 0)
|
|
200
349
|
continue;
|
|
201
|
-
}
|
|
202
350
|
plaintextLength += plainChunk.byteLength;
|
|
203
351
|
plainHash.update(plainChunk);
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
352
|
+
const combined = pending.byteLength > 0
|
|
353
|
+
? (0, bytes_1.concatBytes)([pending, plainChunk])
|
|
354
|
+
: (0, bytes_1.toBytesView)(plainChunk);
|
|
355
|
+
const aligned = combined.byteLength - (combined.byteLength % AES_BLOCK_SIZE);
|
|
356
|
+
if (aligned === 0) {
|
|
357
|
+
pending = combined;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const toEncrypt = (0, bytes_1.toBytesView)(combined.subarray(0, aligned));
|
|
361
|
+
pending = (0, bytes_1.toBytesView)(combined.subarray(aligned));
|
|
362
|
+
const { ciphertext, nextIv } = await aesCbcEncryptChunk(aesKey, currentIv, toEncrypt, false);
|
|
363
|
+
currentIv = nextIv;
|
|
364
|
+
hmac.update(ciphertext);
|
|
365
|
+
encHash.update(ciphertext);
|
|
366
|
+
sidecar?.push(ciphertext);
|
|
367
|
+
if (ffCollected < ffTarget) {
|
|
368
|
+
const need = ffTarget - ffCollected;
|
|
369
|
+
ffChunks.push(ciphertext.subarray(0, Math.min(need, ciphertext.byteLength)));
|
|
370
|
+
ffCollected += ciphertext.byteLength;
|
|
209
371
|
}
|
|
372
|
+
await writeChunkToWritable(output, ciphertext);
|
|
210
373
|
}
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
374
|
+
const { ciphertext: finalCiphertext } = await aesCbcEncryptChunk(aesKey, currentIv, pending, true);
|
|
375
|
+
hmac.update(finalCiphertext);
|
|
376
|
+
encHash.update(finalCiphertext);
|
|
377
|
+
sidecar?.push(finalCiphertext);
|
|
378
|
+
if (ffCollected < ffTarget) {
|
|
379
|
+
const need = ffTarget - ffCollected;
|
|
380
|
+
ffChunks.push(finalCiphertext.subarray(0, Math.min(need, finalCiphertext.byteLength)));
|
|
216
381
|
}
|
|
217
|
-
|
|
382
|
+
await writeChunkToWritable(output, finalCiphertext);
|
|
383
|
+
const signature = (0, bytes_1.toBytesView)(hmac.digest().subarray(0, constants_1.HMAC_TRUNCATED_SIZE));
|
|
218
384
|
encHash.update(signature);
|
|
385
|
+
sidecar?.push(signature);
|
|
219
386
|
await writeChunkToWritable(output, signature);
|
|
220
387
|
await endWritable(output);
|
|
388
|
+
let firstFrameSidecar;
|
|
389
|
+
if (ffTarget > 0) {
|
|
390
|
+
const ivCiphertextSlice = (0, bytes_1.concatBytes)(ffChunks);
|
|
391
|
+
const ffKey = await (0, primitives_1.importHmacKey)(keys.macKey);
|
|
392
|
+
const ffDigest = await (0, primitives_1.hmacSign)(ffKey, ivCiphertextSlice);
|
|
393
|
+
firstFrameSidecar = ffDigest.subarray(0, constants_1.SIDECAR_HMAC_SIZE);
|
|
394
|
+
}
|
|
221
395
|
return {
|
|
222
396
|
fileSha256: (0, bytes_1.toBytesView)(plainHash.digest()),
|
|
223
397
|
fileEncSha256: (0, bytes_1.toBytesView)(encHash.digest()),
|
|
224
|
-
plaintextLength
|
|
398
|
+
plaintextLength,
|
|
399
|
+
streamingSidecar: sidecar?.finish(),
|
|
400
|
+
firstFrameSidecar
|
|
225
401
|
};
|
|
226
402
|
}
|
|
227
403
|
catch (error) {
|
|
@@ -231,10 +407,12 @@ async function pumpEncryptionToWritable(plaintext, output, keys) {
|
|
|
231
407
|
}
|
|
232
408
|
}
|
|
233
409
|
async function pumpDecryption(encrypted, plaintext, keys, options) {
|
|
410
|
+
const aesKey = await (0, primitives_1.importAesCbcKey)(keys.encKey);
|
|
234
411
|
const plainHash = (0, node_crypto_1.createHash)('sha256');
|
|
235
412
|
const encHash = (0, node_crypto_1.createHash)('sha256');
|
|
236
413
|
const hmac = (0, node_crypto_1.createHmac)('sha256', keys.macKey);
|
|
237
|
-
|
|
414
|
+
let currentIv = keys.iv;
|
|
415
|
+
let pending = bytes_1.EMPTY_BYTES;
|
|
238
416
|
hmac.update(keys.iv);
|
|
239
417
|
try {
|
|
240
418
|
let trailing = bytes_1.EMPTY_BYTES;
|
|
@@ -252,20 +430,37 @@ async function pumpDecryption(encrypted, plaintext, keys, options) {
|
|
|
252
430
|
const ciphertextChunk = merged.subarray(0, merged.byteLength - constants_1.HMAC_TRUNCATED_SIZE);
|
|
253
431
|
trailing = merged.subarray(merged.byteLength - constants_1.HMAC_TRUNCATED_SIZE);
|
|
254
432
|
hmac.update(ciphertextChunk);
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
433
|
+
const combined = pending.byteLength > 0
|
|
434
|
+
? (0, bytes_1.concatBytes)([pending, ciphertextChunk])
|
|
435
|
+
: (0, bytes_1.toBytesView)(ciphertextChunk);
|
|
436
|
+
const aligned = combined.byteLength - (combined.byteLength % AES_BLOCK_SIZE);
|
|
437
|
+
if (aligned > AES_BLOCK_SIZE) {
|
|
438
|
+
const toDecrypt = combined.subarray(0, aligned - AES_BLOCK_SIZE);
|
|
439
|
+
const { plaintext: plainChunk, nextIv } = await aesCbcDecryptChunk(aesKey, currentIv, toDecrypt, false);
|
|
440
|
+
currentIv = nextIv;
|
|
441
|
+
if (plainChunk.byteLength > 0) {
|
|
442
|
+
plainHash.update(plainChunk);
|
|
443
|
+
await writeChunk(plaintext, plainChunk);
|
|
444
|
+
}
|
|
445
|
+
pending = (0, bytes_1.toBytesView)(combined.subarray(aligned - AES_BLOCK_SIZE));
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
pending = combined;
|
|
259
449
|
}
|
|
260
450
|
}
|
|
261
451
|
if (trailing.byteLength !== constants_1.HMAC_TRUNCATED_SIZE) {
|
|
262
452
|
throw new Error(`ciphertext too short: ${trailing.byteLength}`);
|
|
263
453
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
454
|
+
if (!options.skipMacVerification) {
|
|
455
|
+
const signature = hmac.digest().subarray(0, constants_1.HMAC_TRUNCATED_SIZE);
|
|
456
|
+
if (!(0, bytes_1.uint8TimingSafeEqual)(signature, trailing)) {
|
|
457
|
+
throw new Error('media MAC mismatch');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (pending.byteLength < AES_BLOCK_SIZE || pending.byteLength % AES_BLOCK_SIZE !== 0) {
|
|
461
|
+
throw new Error(`invalid ciphertext length: ${pending.byteLength}`);
|
|
267
462
|
}
|
|
268
|
-
const plainFinal =
|
|
463
|
+
const { plaintext: plainFinal } = await aesCbcDecryptChunk(aesKey, currentIv, pending, true);
|
|
269
464
|
if (plainFinal.byteLength > 0) {
|
|
270
465
|
plainHash.update(plainFinal);
|
|
271
466
|
await writeChunk(plaintext, plainFinal);
|