All files session-client.ts

100% Statements 38/38
100% Branches 14/14
100% Functions 10/10
100% Lines 38/38

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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151        1x 35x                     35x 2x   33x 1x     32x 32x                 1x   33x       33x       33x                         32x 1x     31x 1x     30x 30x 30x 30x 30x 30x 30x   30x       1x             1x                           1x   31x       31x       31x       31x       27x 1x     26x           26x 1x         1x                     1x                
import { SRPRoutines } from "./routines";
 
// Variable names match the RFC (I, IH, S, b, B, salt, b, A, M1, M2...)
 
export class SRPClientSession {
  constructor(private readonly routines: SRPRoutines) {}
  public async step1(
    /**
     * User identity
     */
    userId: string,
    /**
     * User password (not kept in state)
     */
    userPassword: string,
  ): Promise<SRPClientSessionStep1> {
    if (!userId || !userId.trim()) {
      throw new Error("User identity must not be null nor empty");
    }
    if (!userPassword) {
      throw new Error("User password must not be null");
    }
 
    const IH = await this.routines.computeIdentityHash(userId, userPassword);
    return new SRPClientSessionStep1(this.routines, userId, IH);
  }
}
 
export type SRPClientSessionStep1State = {
  I: string;
  IH: Array<number>; // standard Array representation of the Uint8Array ArrayBuffer
};
 
export class SRPClientSessionStep1 {
  constructor(
    private readonly routines: SRPRoutines,
    /**
     * User identity
     */
    private readonly I: string,
    /**
     * User identity/password hash
     */
    public readonly IH: ArrayBuffer,
  ) {}
 
  public async step2(
    /**
     * Some generated salt (see createVerifierAndSalt)
     */
    salt: bigint,
    /**
     * Server public key "B"
     */
    B: bigint,
  ): Promise<SRPClientSessionStep2> {
    if (!salt) {
      throw new Error("Salt (s) must not be null");
    }
 
    if (!B) {
      throw new Error("Public server value (B) must not be null");
    }
    // TODO can we run any of these promises in parallel?
    const x = await this.routines.computeXStep2(salt, this.IH);
    const a = this.routines.generatePrivateValue();
    const A = this.routines.computeClientPublicValue(a);
    const k = await this.routines.computeK();
    const u = await this.routines.computeU(A, B);
    const S = this.routines.computeClientSessionKey(k, x, u, a, B);
    const M1 = await this.routines.computeClientEvidence(this.I, salt, A, B, S);
 
    return new SRPClientSessionStep2(this.routines, A, M1, S);
  }
 
  public toJSON(): SRPClientSessionStep1State {
    return { I: this.I, IH: Array.from(new Uint8Array(this.IH)) };
  }
 
  public static fromState(
    routines: SRPRoutines,
    state: SRPClientSessionStep1State,
  ) {
    return new SRPClientSessionStep1(
      routines,
      state.I,
      new Uint8Array(state.IH).buffer,
    );
  }
}
 
export type SRPClientSessionStep2State = {
  A: string; // hex representation of bigint
  M1: string;
  S: string;
};
 
export class SRPClientSessionStep2 {
  constructor(
    private readonly routines: SRPRoutines,
    /**
     * Client public value "A"
     */
    public readonly A: bigint,
    /**
     * Client evidence message "M1"
     */
    public readonly M1: bigint,
    /**
     * Shared session key "S"
     */
    public readonly S: bigint,
  ) {}
 
  public async step3(M2: bigint): Promise<void> {
    if (!M2) {
      throw new Error("Server evidence (M2) must not be null");
    }
 
    const computedM2 = await this.routines.computeServerEvidence(
      this.A,
      this.M1,
      this.S,
    );
 
    if (computedM2 !== M2) {
      throw new Error("Bad server credentials");
    }
  }
 
  public toJSON(): SRPClientSessionStep2State {
    return {
      A: this.A.toString(16),
      M1: this.M1.toString(16),
      S: this.S.toString(16),
    };
  }
 
  public static fromState(
    routines: SRPRoutines,
    state: SRPClientSessionStep2State,
  ) {
    return new SRPClientSessionStep2(
      routines,
      BigInt("0x" + state.A),
      BigInt("0x" + state.M1),
      BigInt("0x" + state.S),
    );
  }
}