SHA256
Wallet-session pairing и browser plugin wallet, оплаты пока не работают
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
base64ToBytes,
|
||||
bytesToBase64,
|
||||
bytesToHex,
|
||||
exportEd25519PublicKeyB64,
|
||||
exportPkcs8B64,
|
||||
generateEd25519Pair,
|
||||
sha256Bytes,
|
||||
sha256Text,
|
||||
utf8Bytes,
|
||||
} from './crypto-utils.js';
|
||||
import { edwardsToMontgomeryPriv, x25519 } from './vendor/noble-ed25519-bundle.js';
|
||||
|
||||
const PAIRING_ENVELOPE_PREFIX = 'shine-esp-pairing-v1:';
|
||||
const PAIRING_HASH_PREFIX = 'sha256$';
|
||||
const PAIRING_HASH_VERSION = 'shine-pairing';
|
||||
const ED25519_PKCS8_PREFIX = new Uint8Array([
|
||||
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
|
||||
]);
|
||||
|
||||
function getCryptoApi() {
|
||||
const api = globalThis.crypto;
|
||||
if (!api?.subtle || typeof api.getRandomValues !== 'function') {
|
||||
throw new Error('WebCrypto недоступен.');
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
async function importAesKeyFromSharedSecret(sharedSecretBytes) {
|
||||
const digest = await sha256Bytes(sharedSecretBytes);
|
||||
return getCryptoApi().subtle.importKey('raw', digest, { name: 'AES-GCM' }, false, ['decrypt']);
|
||||
}
|
||||
|
||||
function base64UrlToBytes(value) {
|
||||
const normalized = String(value || '').trim().replace(/-/g, '+').replace(/_/g, '/');
|
||||
const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
|
||||
return base64ToBytes(padded);
|
||||
}
|
||||
|
||||
function extractSeedFromPkcs8(pkcs8B64) {
|
||||
const raw = base64ToBytes(pkcs8B64);
|
||||
if (raw.length !== ED25519_PKCS8_PREFIX.length + 32) {
|
||||
throw new Error('Некорректный приватный Ed25519 ключ');
|
||||
}
|
||||
for (let i = 0; i < ED25519_PKCS8_PREFIX.length; i += 1) {
|
||||
if (raw[i] !== ED25519_PKCS8_PREFIX[i]) {
|
||||
throw new Error('Неподдерживаемый формат приватного Ed25519 ключа');
|
||||
}
|
||||
}
|
||||
return raw.slice(ED25519_PKCS8_PREFIX.length);
|
||||
}
|
||||
|
||||
export async function createRequesterPairingMaterial() {
|
||||
const sessionPair = await generateEd25519Pair();
|
||||
const sessionPublicB64 = await exportEd25519PublicKeyB64(sessionPair.publicKey);
|
||||
return {
|
||||
sessionKey: `ed25519/${sessionPublicB64}`,
|
||||
sessionPrivPkcs8: await exportPkcs8B64(sessionPair.privateKey),
|
||||
};
|
||||
}
|
||||
|
||||
export async function deriveEspPairingPasswordHash(login, password) {
|
||||
const loginLower = String(login || '').trim().toLowerCase();
|
||||
const preimage = `${PAIRING_HASH_VERSION}|${loginLower}|${String(password ?? '')}`;
|
||||
const digest = await sha256Text(preimage);
|
||||
return `${PAIRING_HASH_PREFIX}${bytesToHex(digest)}`;
|
||||
}
|
||||
|
||||
export async function decryptPairingPayloadFromEnvelope(encryptedPayload, requesterPairingMaterial) {
|
||||
const raw = String(encryptedPayload || '').trim();
|
||||
if (!raw.startsWith(PAIRING_ENVELOPE_PREFIX)) {
|
||||
throw new Error('Неподдерживаемый формат pairing payload');
|
||||
}
|
||||
const jsonBytes = base64UrlToBytes(raw.slice(PAIRING_ENVELOPE_PREFIX.length));
|
||||
const envelope = JSON.parse(new TextDecoder().decode(jsonBytes));
|
||||
if (Number(envelope?.v) !== 1 || String(envelope?.alg || '') !== 'x25519-aes256-gcm') {
|
||||
throw new Error('Неподдерживаемая версия pairing payload');
|
||||
}
|
||||
|
||||
const requesterSeed = extractSeedFromPkcs8(String(requesterPairingMaterial?.sessionPrivPkcs8 || ''));
|
||||
const requesterMontPriv = edwardsToMontgomeryPriv(requesterSeed);
|
||||
const sharedSecret = x25519.getSharedSecret(requesterMontPriv, base64ToBytes(String(envelope?.ephPubB64 || '')));
|
||||
const aesKey = await importAesKeyFromSharedSecret(sharedSecret);
|
||||
const plain = await getCryptoApi().subtle.decrypt(
|
||||
{ name: 'AES-GCM', iv: base64ToBytes(String(envelope?.ivB64 || '')) },
|
||||
aesKey,
|
||||
base64ToBytes(String(envelope?.cipherB64 || '')),
|
||||
);
|
||||
return JSON.parse(new TextDecoder().decode(plain));
|
||||
}
|
||||
Reference in New Issue
Block a user