oidc-spa 7.1.8 → 7.1.9

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/entrypoint.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"entrypoint.js","sourceRoot":"","sources":["src/entrypoint.ts"],"names":[],"mappings":";;AAKA,sCAwCC;AA7CD,gEAGmC;AAEnC,SAAgB,aAAa,CAAC,MAA+D;IACnF,IAAA,KAAwC,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,EAAE,EAAlD,WAAW,iBAAA,EAAE,oBAAoB,0BAAiB,CAAC;IAEnD,IAAA,SAAS,GAAK,IAAA,uCAAkB,GAAE,UAAzB,CAA0B;IAE3C,IAAM,aAAa,GAAG,CAAC,SAAS,CAAC;IAEjC,IAAI,aAAa,EAAE,CAAC;QAChB,IAAA,uEAAkD,GAAE,CAAC;QAErD,IAAI,oBAAoB,EAAE,CAAC;YACvB,IAAM,sBAAsB,GAAG,UAAU,CAAC,cAAc,CAAC;YAEzD,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;YAChD,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;YAEtC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,gBAAgB,EAAE;gBAChD,YAAY,EAAE,KAAK;gBACnB,QAAQ,EAAE,KAAK;gBACf,UAAU,EAAE,IAAI;gBAChB,KAAK,EAAE,sBAAsB;aAChC,CAAC,CAAC;QACP,CAAC;QAED,IAAI,WAAW,EAAE,CAAC;YACd,IAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC;YAEvC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YAE7B,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,EAAE;gBACvC,YAAY,EAAE,KAAK;gBACnB,QAAQ,EAAE,KAAK;gBACf,UAAU,EAAE,IAAI;gBAChB,KAAK,EAAE,aAAa;aACvB,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IAED,OAAO,EAAE,aAAa,eAAA,EAAE,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"entrypoint.js","sourceRoot":"","sources":["src/entrypoint.ts"],"names":[],"mappings":";;AAMA,sCA8DC;AApED,gEAGmC;AACnC,0EAAqG;AAErG,SAAgB,aAAa,CAAC,MAM7B;IACS,IAAA,KAAiE,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,EAAE,EAA3E,WAAW,iBAAA,EAAE,oBAAoB,0BAAA,EAAE,uBAAuB,EAAvB,eAAe,mBAAG,KAAK,KAAiB,CAAC;IAE5E,IAAA,SAAS,GAAK,IAAA,uCAAkB,GAAE,UAAzB,CAA0B;IAE3C,IAAM,aAAa,GAAG,CAAC,SAAS,CAAC;IAEjC,IAAI,aAAa,EAAE,CAAC;QAChB,IAAA,uEAAkD,GAAE,CAAC;QAErD,IAAI,oBAAoB,EAAE,CAAC;YACvB,IAAM,sBAAsB,GAAG,UAAU,CAAC,cAAc,CAAC;YAEzD,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;YAChD,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;YAEtC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,gBAAgB,EAAE;gBAChD,YAAY,EAAE,KAAK;gBACnB,QAAQ,EAAE,KAAK;gBACf,UAAU,EAAE,IAAI;gBAChB,KAAK,EAAE,sBAAsB;aAChC,CAAC,CAAC;QACP,CAAC;QAED,IAAI,WAAW,EAAE,CAAC;YACd,IAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC;YAEvC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YAE7B,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,EAAE;gBACvC,YAAY,EAAE,KAAK;gBACnB,QAAQ,EAAE,KAAK;gBACf,UAAU,EAAE,IAAI;gBAChB,KAAK,EAAE,aAAa;aACvB,CAAC,CAAC;QACP,CAAC;QAED,IAAI,eAAe,EAAE,CAAC;YAClB,IAAM,iBAAiB,GAAG,UAAU,CAAC,SAAS,CAAC;YAE/C,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;YAEjC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,WAAW,EAAE;gBAC3C,YAAY,EAAE,KAAK;gBACnB,QAAQ,EAAE,KAAK;gBACf,UAAU,EAAE,IAAI;gBAChB,KAAK,EAAE,iBAAiB;aAC3B,CAAC,CAAC;QACP,CAAC;QAED,IAAA,6EAAmD,GAAE,CAAC;IAC1D,CAAC;IAED,OAAO,EAAE,aAAa,eAAA,EAAE,CAAC;AAC7B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oidc-spa",
3
- "version": "7.1.8",
3
+ "version": "7.1.9",
4
4
  "description": "Openidconnect client for Single Page Applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,6 +41,7 @@
41
41
  "./src/core/createOidc.ts",
42
42
  "./src/core/evtIsUserActive.ts",
43
43
  "./src/core/handleOidcCallback.ts",
44
+ "./src/core/iframeMessageProtection.ts",
44
45
  "./src/core/index.ts",
45
46
  "./src/core/initialLocationHref.ts",
46
47
  "./src/core/isNewBrowserSession.ts",
@@ -64,6 +65,7 @@
64
65
  "./src/tools/Evt.ts",
65
66
  "./src/tools/StatefulEvt.ts",
66
67
  "./src/tools/ValueOrAsyncGetter.ts",
68
+ "./src/tools/asymmetricEncryption.ts",
67
69
  "./src/tools/base64.ts",
68
70
  "./src/tools/createObjectThatThrowsIfAccessed.ts",
69
71
  "./src/tools/decodeJwt.ts",
@@ -122,6 +124,9 @@
122
124
  "./core/handleOidcCallback.d.ts",
123
125
  "./core/handleOidcCallback.js",
124
126
  "./core/handleOidcCallback.js.map",
127
+ "./core/iframeMessageProtection.d.ts",
128
+ "./core/iframeMessageProtection.js",
129
+ "./core/iframeMessageProtection.js.map",
125
130
  "./core/index.d.ts",
126
131
  "./core/index.js",
127
132
  "./core/index.js.map",
@@ -191,6 +196,9 @@
191
196
  "./tools/ValueOrAsyncGetter.d.ts",
192
197
  "./tools/ValueOrAsyncGetter.js",
193
198
  "./tools/ValueOrAsyncGetter.js.map",
199
+ "./tools/asymmetricEncryption.d.ts",
200
+ "./tools/asymmetricEncryption.js",
201
+ "./tools/asymmetricEncryption.js.map",
194
202
  "./tools/base64.d.ts",
195
203
  "./tools/base64.js",
196
204
  "./tools/base64.js.map",
@@ -5,15 +5,6 @@ export type AuthResponse = {
5
5
  [key: string]: string | undefined;
6
6
  };
7
7
 
8
- export function getIsAuthResponse(data: any): data is AuthResponse {
9
- return (
10
- data instanceof Object &&
11
- "state" in data &&
12
- typeof data.state === "string" &&
13
- Object.values(data).every(value => value === undefined || typeof value === "string")
14
- );
15
- }
16
-
17
8
  export function authResponseToUrl(authResponse: AuthResponse): string {
18
9
  let authResponseUrl = "https://dummy.com";
19
10
 
@@ -8,6 +8,7 @@ import { assert, id } from "../vendor/frontend/tsafe";
8
8
  import type { AuthResponse } from "./AuthResponse";
9
9
  import { initialLocationHref } from "./initialLocationHref";
10
10
  import { captureFetch } from "./trustedFetch";
11
+ import { encryptAuthResponse } from "./iframeMessageProtection";
11
12
 
12
13
  captureFetch();
13
14
 
@@ -111,7 +112,8 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
111
112
  // NOTE: This is a "better than nothing" approach.
112
113
  // Under some circumstances it's possible to get stuck on this url
113
114
  // if there is no "next" page in the history for example, navigating
114
- // forward is a NoOp. So in that case it's better to navigate to the home.
115
+ // forward is a NoOp. So in that case it's better to reload the same route
116
+ // with just the authResponse removed from the url to avoid re-entering here.
115
117
  setTimeout(() => {
116
118
  const { protocol, host, pathname, hash } = window.location;
117
119
  window.location.href = `${protocol}//${host}${pathname}${hash}`;
@@ -131,9 +133,9 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
131
133
 
132
134
  switch (stateData.context) {
133
135
  case "iframe":
134
- setTimeout(() => {
135
- parent.postMessage(authResponse, location.origin);
136
- }, 0);
136
+ encryptAuthResponse({
137
+ authResponse
138
+ }).then(({ encryptedMessage }) => parent.postMessage(encryptedMessage, location.origin));
137
139
  break;
138
140
  case "redirect":
139
141
  markStateDataAsProcessedByCallback({ stateQueryParamValue });
@@ -0,0 +1,99 @@
1
+ import { assert } from "tsafe/assert";
2
+ import { asymmetricEncrypt, asymmetricDecrypt, generateKeys } from "../tools/asymmetricEncryption";
3
+ import { type AuthResponse } from "./AuthResponse";
4
+
5
+ const setItem_real = Storage.prototype.setItem;
6
+
7
+ const SESSION_STORAGE_PREFIX = "oidc-spa_iframe_authResponse_publicKey_";
8
+
9
+ export function preventSessionStorageSetItemOfPublicKeyByThirdParty() {
10
+ const setItem_protected = function setItem(this: any, key: string, value: string): void {
11
+ if (this !== sessionStorage) {
12
+ return setItem_real.call(this, key, value);
13
+ }
14
+
15
+ if (key.startsWith(SESSION_STORAGE_PREFIX)) {
16
+ throw new Error(
17
+ "Attack prevented by oidc-spa. You have malicious code running in your system"
18
+ );
19
+ }
20
+
21
+ return setItem_real.call(sessionStorage, key, value);
22
+ };
23
+
24
+ {
25
+ const pd = Object.getOwnPropertyDescriptor(Storage.prototype, "setItem");
26
+
27
+ assert(pd !== undefined);
28
+
29
+ Object.defineProperty(Storage.prototype, "setItem", {
30
+ enumerable: pd.enumerable,
31
+ writable: pd.writable,
32
+ value: setItem_protected
33
+ });
34
+ }
35
+ }
36
+
37
+ const ENCRYPTED_AUTH_RESPONSES_PREFIX = "oidc-spa_encrypted_authResponse_";
38
+
39
+ function getSessionStorageKey(params: { stateQueryParamValue: string }) {
40
+ const { stateQueryParamValue } = params;
41
+
42
+ return `${SESSION_STORAGE_PREFIX}${stateQueryParamValue}`;
43
+ }
44
+
45
+ export async function initIframeMessageProtection(params: { stateQueryParamValue: string }) {
46
+ const { stateQueryParamValue } = params;
47
+
48
+ const { publicKey, privateKey } = await generateKeys();
49
+
50
+ const sessionStorageKey = getSessionStorageKey({ stateQueryParamValue });
51
+
52
+ setItem_real.call(sessionStorage, sessionStorageKey, publicKey);
53
+
54
+ function getIsEncryptedAuthResponse(params: { message: unknown }): boolean {
55
+ const { message } = params;
56
+
57
+ return typeof message === "string" && message.startsWith(ENCRYPTED_AUTH_RESPONSES_PREFIX);
58
+ }
59
+
60
+ async function decodeEncryptedAuth(params: {
61
+ encryptedAuthResponse: string;
62
+ }): Promise<{ authResponse: AuthResponse }> {
63
+ const { encryptedAuthResponse } = params;
64
+
65
+ const { message: authResponse_str } = await asymmetricDecrypt({
66
+ encryptedMessage: encryptedAuthResponse.slice(ENCRYPTED_AUTH_RESPONSES_PREFIX.length),
67
+ privateKey
68
+ });
69
+
70
+ const authResponse: AuthResponse = JSON.parse(authResponse_str);
71
+
72
+ return { authResponse };
73
+ }
74
+
75
+ function clearSessionStoragePublicKey() {
76
+ sessionStorage.removeItem(sessionStorageKey);
77
+ }
78
+
79
+ return { getIsEncryptedAuthResponse, decodeEncryptedAuth, clearSessionStoragePublicKey };
80
+ }
81
+
82
+ export async function encryptAuthResponse(params: { authResponse: AuthResponse }) {
83
+ const { authResponse } = params;
84
+
85
+ const publicKey = sessionStorage.getItem(
86
+ getSessionStorageKey({ stateQueryParamValue: authResponse.state })
87
+ );
88
+
89
+ assert(publicKey !== null, "2293302");
90
+
91
+ const { encryptedMessage: encryptedMessage_withoutPrefix } = await asymmetricEncrypt({
92
+ publicKey,
93
+ message: JSON.stringify(authResponse)
94
+ });
95
+
96
+ const encryptedMessage = `${ENCRYPTED_AUTH_RESPONSES_PREFIX}${encryptedMessage_withoutPrefix}`;
97
+
98
+ return { encryptedMessage };
99
+ }
@@ -5,8 +5,9 @@ import { getStateData, clearStateStore, type StateData } from "./StateData";
5
5
  import { getDownlinkAndRtt } from "../tools/getDownlinkAndRtt";
6
6
  import { getIsDev } from "../tools/isDev";
7
7
  import type { User as OidcClientTsUser } from "../vendor/frontend/oidc-client-ts-and-jwt-decode";
8
- import { type AuthResponse, getIsAuthResponse } from "./AuthResponse";
8
+ import { type AuthResponse } from "./AuthResponse";
9
9
  import { addOrUpdateSearchParam } from "../tools/urlSearchParams";
10
+ import { initIframeMessageProtection } from "./iframeMessageProtection";
10
11
 
11
12
  type ResultOfLoginSilent =
12
13
  | {
@@ -74,6 +75,11 @@ export async function loginSilent(params: {
74
75
  return Math.max(BASE_DELAY_MS, dynamicDelay);
75
76
  })();
76
77
 
78
+ const { decodeEncryptedAuth, getIsEncryptedAuthResponse, clearSessionStoragePublicKey } =
79
+ await initIframeMessageProtection({
80
+ stateQueryParamValue: stateQueryParamValue_instance
81
+ });
82
+
77
83
  const timeout = setTimeout(async () => {
78
84
  dResult.resolve({
79
85
  outcome: "failure",
@@ -81,12 +87,20 @@ export async function loginSilent(params: {
81
87
  });
82
88
  }, timeoutDelayMs);
83
89
 
84
- const listener = (event: MessageEvent) => {
85
- if (!getIsAuthResponse(event.data)) {
90
+ const listener = async (event: MessageEvent) => {
91
+ if (event.origin !== window.location.origin) {
92
+ return;
93
+ }
94
+
95
+ if (
96
+ !getIsEncryptedAuthResponse({
97
+ message: event.data
98
+ })
99
+ ) {
86
100
  return;
87
101
  }
88
102
 
89
- const authResponse = event.data;
103
+ const { authResponse } = await decodeEncryptedAuth({ encryptedAuthResponse: event.data });
90
104
 
91
105
  const stateData = getStateData({ stateQueryParamValue: authResponse.state });
92
106
 
@@ -181,6 +195,8 @@ export async function loginSilent(params: {
181
195
  );
182
196
 
183
197
  dResult.pr.then(result => {
198
+ clearSessionStoragePublicKey();
199
+
184
200
  if (result.outcome === "failure") {
185
201
  clearStateStore({ stateQueryParamValue: stateQueryParamValue_instance });
186
202
  }
package/src/entrypoint.ts CHANGED
@@ -2,9 +2,16 @@ import {
2
2
  handleOidcCallback,
3
3
  moveRedirectAuthResponseFromSessionStorageToMemory
4
4
  } from "./core/handleOidcCallback";
5
+ import { preventSessionStorageSetItemOfPublicKeyByThirdParty } from "./core/iframeMessageProtection";
5
6
 
6
- export function oidcEarlyInit(params: { freezeFetch: boolean; freezeXMLHttpRequest: boolean }) {
7
- const { freezeFetch, freezeXMLHttpRequest } = params ?? {};
7
+ export function oidcEarlyInit(params: {
8
+ freezeFetch: boolean;
9
+ freezeXMLHttpRequest: boolean;
10
+ // NOTE: Made optional just to avoid breaking change.
11
+ // Will be made mandatory next major.
12
+ freezeWebSocket?: boolean;
13
+ }) {
14
+ const { freezeFetch, freezeXMLHttpRequest, freezeWebSocket = false } = params ?? {};
8
15
 
9
16
  const { isHandled } = handleOidcCallback();
10
17
 
@@ -40,6 +47,22 @@ export function oidcEarlyInit(params: { freezeFetch: boolean; freezeXMLHttpReque
40
47
  value: fetch_trusted
41
48
  });
42
49
  }
50
+
51
+ if (freezeWebSocket) {
52
+ const WebSocket_trusted = globalThis.WebSocket;
53
+
54
+ Object.freeze(WebSocket_trusted.prototype);
55
+ Object.freeze(WebSocket_trusted);
56
+
57
+ Object.defineProperty(globalThis, "WebSocket", {
58
+ configurable: false,
59
+ writable: false,
60
+ enumerable: true,
61
+ value: WebSocket_trusted
62
+ });
63
+ }
64
+
65
+ preventSessionStorageSetItemOfPublicKeyByThirdParty();
43
66
  }
44
67
 
45
68
  return { shouldLoadApp };
@@ -0,0 +1,184 @@
1
+ type AsymmetricKeys = {
2
+ publicKey: string; // base64-encoded JSON export of CryptoKey
3
+ privateKey: string; // base64-encoded JSON export of CryptoKey
4
+ };
5
+
6
+ const INFO_LABEL = "oidc-spa/tools/asymmetricEncryption";
7
+
8
+ export async function generateKeys(): Promise<AsymmetricKeys> {
9
+ const keyPair = await crypto.subtle.generateKey(
10
+ {
11
+ name: "ECDH",
12
+ namedCurve: "P-256"
13
+ },
14
+ true,
15
+ ["deriveKey", "deriveBits"]
16
+ );
17
+
18
+ const publicKeyRaw = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
19
+ const privateKeyRaw = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
20
+
21
+ return {
22
+ publicKey: btoa(JSON.stringify(publicKeyRaw)),
23
+ privateKey: btoa(JSON.stringify(privateKeyRaw))
24
+ };
25
+ }
26
+
27
+ export async function asymmetricEncrypt(params: {
28
+ publicKey: string;
29
+ message: string;
30
+ }): Promise<{ encryptedMessage: string }> {
31
+ const { publicKey, message } = params;
32
+
33
+ const importedPublicKey = await crypto.subtle.importKey(
34
+ "jwk",
35
+ JSON.parse(atob(publicKey)),
36
+ {
37
+ name: "ECDH",
38
+ namedCurve: "P-256"
39
+ },
40
+ false,
41
+ []
42
+ );
43
+
44
+ const ephemeralKeyPair = await crypto.subtle.generateKey(
45
+ {
46
+ name: "ECDH",
47
+ namedCurve: "P-256"
48
+ },
49
+ true,
50
+ ["deriveKey", "deriveBits"]
51
+ );
52
+
53
+ const sharedSecret = await crypto.subtle.deriveBits(
54
+ {
55
+ name: "ECDH",
56
+ public: importedPublicKey
57
+ },
58
+ ephemeralKeyPair.privateKey,
59
+ 256
60
+ );
61
+
62
+ const salt = crypto.getRandomValues(new Uint8Array(16));
63
+ const infoBytes = new TextEncoder().encode(INFO_LABEL);
64
+
65
+ const hkdfKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
66
+
67
+ const derivedKey = await crypto.subtle.deriveKey(
68
+ {
69
+ name: "HKDF",
70
+ hash: "SHA-256",
71
+ salt,
72
+ info: infoBytes
73
+ },
74
+ hkdfKey,
75
+ { name: "AES-GCM", length: 256 },
76
+ false,
77
+ ["encrypt"]
78
+ );
79
+
80
+ const iv = crypto.getRandomValues(new Uint8Array(12));
81
+ const encodedMessage = new TextEncoder().encode(message);
82
+
83
+ const ciphertext = await crypto.subtle.encrypt(
84
+ {
85
+ name: "AES-GCM",
86
+ iv
87
+ },
88
+ derivedKey,
89
+ encodedMessage
90
+ );
91
+
92
+ const ephemeralPubKeyRaw = await crypto.subtle.exportKey("jwk", ephemeralKeyPair.publicKey);
93
+
94
+ const payload = {
95
+ ephemeralPubKey: ephemeralPubKeyRaw,
96
+ iv: Array.from(iv),
97
+ salt: Array.from(salt),
98
+ ciphertext: Array.from(new Uint8Array(ciphertext))
99
+ };
100
+
101
+ return {
102
+ encryptedMessage: btoa(JSON.stringify(payload))
103
+ };
104
+ }
105
+
106
+ export async function asymmetricDecrypt(params: {
107
+ privateKey: string;
108
+ encryptedMessage: string;
109
+ }): Promise<{ message: string }> {
110
+ const { privateKey, encryptedMessage } = params;
111
+
112
+ const {
113
+ ephemeralPubKey,
114
+ iv,
115
+ salt,
116
+ ciphertext
117
+ }: {
118
+ ephemeralPubKey: JsonWebKey;
119
+ iv: number[];
120
+ salt: number[];
121
+ ciphertext: number[];
122
+ } = JSON.parse(atob(encryptedMessage));
123
+
124
+ const importedPrivateKey = await crypto.subtle.importKey(
125
+ "jwk",
126
+ JSON.parse(atob(privateKey)),
127
+ {
128
+ name: "ECDH",
129
+ namedCurve: "P-256"
130
+ },
131
+ false,
132
+ ["deriveKey", "deriveBits"]
133
+ );
134
+
135
+ const importedEphemeralPubKey = await crypto.subtle.importKey(
136
+ "jwk",
137
+ ephemeralPubKey,
138
+ {
139
+ name: "ECDH",
140
+ namedCurve: "P-256"
141
+ },
142
+ false,
143
+ []
144
+ );
145
+
146
+ const sharedSecret = await crypto.subtle.deriveBits(
147
+ {
148
+ name: "ECDH",
149
+ public: importedEphemeralPubKey
150
+ },
151
+ importedPrivateKey,
152
+ 256
153
+ );
154
+
155
+ const infoBytes = new TextEncoder().encode(INFO_LABEL);
156
+
157
+ const hkdfKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"]);
158
+
159
+ const derivedKey = await crypto.subtle.deriveKey(
160
+ {
161
+ name: "HKDF",
162
+ hash: "SHA-256",
163
+ salt: new Uint8Array(salt),
164
+ info: infoBytes
165
+ },
166
+ hkdfKey,
167
+ { name: "AES-GCM", length: 256 },
168
+ false,
169
+ ["decrypt"]
170
+ );
171
+
172
+ const decryptedBuffer = await crypto.subtle.decrypt(
173
+ {
174
+ name: "AES-GCM",
175
+ iv: new Uint8Array(iv)
176
+ },
177
+ derivedKey,
178
+ new Uint8Array(ciphertext)
179
+ );
180
+
181
+ return {
182
+ message: new TextDecoder().decode(decryptedBuffer)
183
+ };
184
+ }
@@ -0,0 +1,18 @@
1
+ type AsymmetricKeys = {
2
+ publicKey: string;
3
+ privateKey: string;
4
+ };
5
+ export declare function generateKeys(): Promise<AsymmetricKeys>;
6
+ export declare function asymmetricEncrypt(params: {
7
+ publicKey: string;
8
+ message: string;
9
+ }): Promise<{
10
+ encryptedMessage: string;
11
+ }>;
12
+ export declare function asymmetricDecrypt(params: {
13
+ privateKey: string;
14
+ encryptedMessage: string;
15
+ }): Promise<{
16
+ message: string;
17
+ }>;
18
+ export {};
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.generateKeys = generateKeys;
40
+ exports.asymmetricEncrypt = asymmetricEncrypt;
41
+ exports.asymmetricDecrypt = asymmetricDecrypt;
42
+ var INFO_LABEL = "oidc-spa/tools/asymmetricEncryption";
43
+ function generateKeys() {
44
+ return __awaiter(this, void 0, void 0, function () {
45
+ var keyPair, publicKeyRaw, privateKeyRaw;
46
+ return __generator(this, function (_a) {
47
+ switch (_a.label) {
48
+ case 0: return [4 /*yield*/, crypto.subtle.generateKey({
49
+ name: "ECDH",
50
+ namedCurve: "P-256"
51
+ }, true, ["deriveKey", "deriveBits"])];
52
+ case 1:
53
+ keyPair = _a.sent();
54
+ return [4 /*yield*/, crypto.subtle.exportKey("jwk", keyPair.publicKey)];
55
+ case 2:
56
+ publicKeyRaw = _a.sent();
57
+ return [4 /*yield*/, crypto.subtle.exportKey("jwk", keyPair.privateKey)];
58
+ case 3:
59
+ privateKeyRaw = _a.sent();
60
+ return [2 /*return*/, {
61
+ publicKey: btoa(JSON.stringify(publicKeyRaw)),
62
+ privateKey: btoa(JSON.stringify(privateKeyRaw))
63
+ }];
64
+ }
65
+ });
66
+ });
67
+ }
68
+ function asymmetricEncrypt(params) {
69
+ return __awaiter(this, void 0, void 0, function () {
70
+ var publicKey, message, importedPublicKey, ephemeralKeyPair, sharedSecret, salt, infoBytes, hkdfKey, derivedKey, iv, encodedMessage, ciphertext, ephemeralPubKeyRaw, payload;
71
+ return __generator(this, function (_a) {
72
+ switch (_a.label) {
73
+ case 0:
74
+ publicKey = params.publicKey, message = params.message;
75
+ return [4 /*yield*/, crypto.subtle.importKey("jwk", JSON.parse(atob(publicKey)), {
76
+ name: "ECDH",
77
+ namedCurve: "P-256"
78
+ }, false, [])];
79
+ case 1:
80
+ importedPublicKey = _a.sent();
81
+ return [4 /*yield*/, crypto.subtle.generateKey({
82
+ name: "ECDH",
83
+ namedCurve: "P-256"
84
+ }, true, ["deriveKey", "deriveBits"])];
85
+ case 2:
86
+ ephemeralKeyPair = _a.sent();
87
+ return [4 /*yield*/, crypto.subtle.deriveBits({
88
+ name: "ECDH",
89
+ public: importedPublicKey
90
+ }, ephemeralKeyPair.privateKey, 256)];
91
+ case 3:
92
+ sharedSecret = _a.sent();
93
+ salt = crypto.getRandomValues(new Uint8Array(16));
94
+ infoBytes = new TextEncoder().encode(INFO_LABEL);
95
+ return [4 /*yield*/, crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"])];
96
+ case 4:
97
+ hkdfKey = _a.sent();
98
+ return [4 /*yield*/, crypto.subtle.deriveKey({
99
+ name: "HKDF",
100
+ hash: "SHA-256",
101
+ salt: salt,
102
+ info: infoBytes
103
+ }, hkdfKey, { name: "AES-GCM", length: 256 }, false, ["encrypt"])];
104
+ case 5:
105
+ derivedKey = _a.sent();
106
+ iv = crypto.getRandomValues(new Uint8Array(12));
107
+ encodedMessage = new TextEncoder().encode(message);
108
+ return [4 /*yield*/, crypto.subtle.encrypt({
109
+ name: "AES-GCM",
110
+ iv: iv
111
+ }, derivedKey, encodedMessage)];
112
+ case 6:
113
+ ciphertext = _a.sent();
114
+ return [4 /*yield*/, crypto.subtle.exportKey("jwk", ephemeralKeyPair.publicKey)];
115
+ case 7:
116
+ ephemeralPubKeyRaw = _a.sent();
117
+ payload = {
118
+ ephemeralPubKey: ephemeralPubKeyRaw,
119
+ iv: Array.from(iv),
120
+ salt: Array.from(salt),
121
+ ciphertext: Array.from(new Uint8Array(ciphertext))
122
+ };
123
+ return [2 /*return*/, {
124
+ encryptedMessage: btoa(JSON.stringify(payload))
125
+ }];
126
+ }
127
+ });
128
+ });
129
+ }
130
+ function asymmetricDecrypt(params) {
131
+ return __awaiter(this, void 0, void 0, function () {
132
+ var privateKey, encryptedMessage, _a, ephemeralPubKey, iv, salt, ciphertext, importedPrivateKey, importedEphemeralPubKey, sharedSecret, infoBytes, hkdfKey, derivedKey, decryptedBuffer;
133
+ return __generator(this, function (_b) {
134
+ switch (_b.label) {
135
+ case 0:
136
+ privateKey = params.privateKey, encryptedMessage = params.encryptedMessage;
137
+ _a = JSON.parse(atob(encryptedMessage)), ephemeralPubKey = _a.ephemeralPubKey, iv = _a.iv, salt = _a.salt, ciphertext = _a.ciphertext;
138
+ return [4 /*yield*/, crypto.subtle.importKey("jwk", JSON.parse(atob(privateKey)), {
139
+ name: "ECDH",
140
+ namedCurve: "P-256"
141
+ }, false, ["deriveKey", "deriveBits"])];
142
+ case 1:
143
+ importedPrivateKey = _b.sent();
144
+ return [4 /*yield*/, crypto.subtle.importKey("jwk", ephemeralPubKey, {
145
+ name: "ECDH",
146
+ namedCurve: "P-256"
147
+ }, false, [])];
148
+ case 2:
149
+ importedEphemeralPubKey = _b.sent();
150
+ return [4 /*yield*/, crypto.subtle.deriveBits({
151
+ name: "ECDH",
152
+ public: importedEphemeralPubKey
153
+ }, importedPrivateKey, 256)];
154
+ case 3:
155
+ sharedSecret = _b.sent();
156
+ infoBytes = new TextEncoder().encode(INFO_LABEL);
157
+ return [4 /*yield*/, crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveKey"])];
158
+ case 4:
159
+ hkdfKey = _b.sent();
160
+ return [4 /*yield*/, crypto.subtle.deriveKey({
161
+ name: "HKDF",
162
+ hash: "SHA-256",
163
+ salt: new Uint8Array(salt),
164
+ info: infoBytes
165
+ }, hkdfKey, { name: "AES-GCM", length: 256 }, false, ["decrypt"])];
166
+ case 5:
167
+ derivedKey = _b.sent();
168
+ return [4 /*yield*/, crypto.subtle.decrypt({
169
+ name: "AES-GCM",
170
+ iv: new Uint8Array(iv)
171
+ }, derivedKey, new Uint8Array(ciphertext))];
172
+ case 6:
173
+ decryptedBuffer = _b.sent();
174
+ return [2 /*return*/, {
175
+ message: new TextDecoder().decode(decryptedBuffer)
176
+ }];
177
+ }
178
+ });
179
+ });
180
+ }
181
+ //# sourceMappingURL=asymmetricEncryption.js.map