All files / src/storage/get-file sign.ts

70.58% Statements 24/34
44.44% Branches 8/18
100% Functions 1/1
72.72% Lines 24/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 943x   3x 3x                           3x                   2x 2x   2x   2x 2x 2x                 2x           2x 2x                         2x 2x 2x 2x   2x       2x           2x           2x         2x 2x 2x    
import { getPublicKey } from 'micro-stacks/crypto';
import { publicKeyToBase58Address, verifyECDSA, decryptContent } from 'micro-stacks/crypto';
import { SignatureVerificationError } from '../gaia/errors';
import { getGaiaAddress } from './getters';
 
import type { GaiaHubConfig } from '../gaia/types';
import type { SignedCipherObject } from 'micro-stacks/crypto';
 
/**
 *  Handle signature verification and decryption for contents which are
 *  expected to be signed and encrypted. This works for single and
 *  multiplayer reads. In the case of multiplayer reads, it uses the
 *  gaia address for verification of the claimed public key.
 *
 *  @private
 */
 
export async function handleSignedEncryptedContents(options: {
  path: string;
  storedContents: string;
  app: string;
  privateKey?: string;
  username?: string;
  zoneFileLookupURL?: string;
  gaiaHubConfig: GaiaHubConfig;
}): Promise<string | Uint8Array> {
  const { path, storedContents, app, privateKey, username, zoneFileLookupURL, gaiaHubConfig } =
    options;
  const appPrivateKey = privateKey;
 
  const appPublicKey = appPrivateKey ? getPublicKey(appPrivateKey, true) : null;
 
  let address: string | null = null;
  if (username || gaiaHubConfig) {
    address = await getGaiaAddress({
      app,
      username,
      zoneFileLookupURL,
      gaiaHubConfig,
    });
  } else IEif (appPublicKey) {
    address = publicKeyToBase58Address(appPublicKey);
  }
  Iif (!address) {
    throw new SignatureVerificationError(
      'Failed to get gaia address for verification of: ' + `${path}`
    );
  }
  let signedCipherObject: SignedCipherObject;
  try {
    signedCipherObject = JSON.parse(storedContents);
  } catch (err) {
    if (err instanceof SyntaxError) {
      throw new Error(
        'Failed to parse encrypted, signed content JSON. The content may not ' +
          'be encrypted. If using getFile, try passing' +
          ' { verify: false, decrypt: false }.'
      );
    } else {
      throw err;
    }
  }
 
  const signature = signedCipherObject.signature;
  const signerPublicKey = signedCipherObject.publicKey;
  const cipherText = signedCipherObject.cipherText;
  const signerAddress = publicKeyToBase58Address(signerPublicKey);
 
  Iif (!signerPublicKey || !cipherText || !signature) {
    throw new SignatureVerificationError(
      'Failed to get signature verification data from file:' + ` ${path}`
    );
  } else Iif (signerAddress !== address) {
    throw new SignatureVerificationError(
      `Signer pubkey address (${signerAddress}) doesn't` + ` match gaia address (${address})`
    );
  }
 
  const verified = verifyECDSA({
    signature,
    contents: cipherText,
    publicKey: signerPublicKey,
  });
 
  Iif (!verified)
    throw new SignatureVerificationError(
      'Contents do not match ECDSA signature in file:' + ` ${path}`
    );
 
  Iif (!privateKey) throw Error('Private key needs to be passed in order to decrypt content');
  const decryptOpt = { privateKey };
  return decryptContent(cipherText, decryptOpt);
}