@1shotapi/ows-signer 0.3.4 → 0.4.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 CHANGED
@@ -88,6 +88,7 @@ Ceremony UI params (optional on WebAuthn-triggering methods): `explanationHeader
88
88
 
89
89
  ```
90
90
  html/index.html Entry document
91
+ html/signer.css Ceremony / reveal UI styles (linked; no inline CSS)
91
92
  src/main.js Bootstrap + message listener
92
93
  src/handlers.js RPC dispatch
93
94
  src/webauthn.js Passkey ceremonies
@@ -96,6 +97,20 @@ src/crypto/vendor/ Vendored @noble/secp256k1 + ed25519 (MIT)
96
97
  reference/ 1ShotPay reference (not published)
97
98
  ```
98
99
 
100
+ ## Content Security Policy
101
+
102
+ Serve the Signing Layer document (and its `/src/*` modules) with a **fail-closed** CSP. The signer has no legitimate network I/O — only same-origin scripts/styles and `postMessage` / WebAuthn / WebCrypto.
103
+
104
+ Canonical header (single line in production):
105
+
106
+ ```http
107
+ Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'none'; img-src 'none'; font-src 'none'; media-src 'none'; object-src 'none'; child-src 'none'; frame-src 'none'; worker-src 'none'; manifest-src 'none'; base-uri 'none'; form-action 'none'; upgrade-insecure-requests
108
+ ```
109
+
110
+ - Apply on the **Signing Layer origin path** (e.g. nginx `location /signer/`), not on the Branding Layer SPA.
111
+ - **Omit `frame-ancestors`** so arbitrary Branding Layers may embed the signer. Nesting safety remains in JS (`event.source === window.parent`, `window.parent !== window.top`).
112
+ - Keep iframe Permissions Policy `allow` for WebAuthn / clipboard (set before `src` in `createSignerIframe`) — that is not CSP.
113
+
99
114
  ## On-chain
100
115
 
101
116
  See `@1shotapi/ows-onchain`.
package/html/index.html CHANGED
@@ -4,105 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>OWS Custody Signer</title>
7
- <style>
8
- :root {
9
- color-scheme: light dark;
10
- font-family: system-ui, sans-serif;
11
- }
12
- html,
13
- body {
14
- height: 100%;
15
- margin: 0;
16
- overflow: hidden;
17
- }
18
- body {
19
- box-sizing: border-box;
20
- }
21
- .ows-signer-ui {
22
- display: flex;
23
- flex-direction: column;
24
- gap: 8px;
25
- box-sizing: border-box;
26
- width: 100%;
27
- height: 100%;
28
- max-width: 100%;
29
- padding: 12px;
30
- overflow: hidden;
31
- }
32
- .ows-signer-ui--ceremony {
33
- gap: 0;
34
- }
35
- .ows-key {
36
- margin: 0;
37
- padding: 8px;
38
- border: 1px solid #8884;
39
- border-radius: 4px;
40
- font-family: ui-monospace, monospace;
41
- font-size: 12px;
42
- word-break: break-all;
43
- white-space: pre-wrap;
44
- overflow: auto;
45
- flex: 1 1 auto;
46
- min-height: 0;
47
- }
48
- .ows-password {
49
- padding: 8px;
50
- border: 1px solid #8884;
51
- border-radius: 4px;
52
- font: inherit;
53
- }
54
- .ows-ceremony-header {
55
- flex: 0 0 auto;
56
- margin: 0 0 8px;
57
- font-size: 1.1rem;
58
- font-weight: 700;
59
- line-height: 1.3;
60
- }
61
- .ows-ceremony-body {
62
- flex: 1 1 auto;
63
- min-height: 0;
64
- overflow-x: hidden;
65
- overflow-y: auto;
66
- margin: 0 0 12px;
67
- }
68
- .ows-ceremony-text {
69
- margin: 0;
70
- font-size: 0.95rem;
71
- line-height: 1.4;
72
- opacity: 0.9;
73
- }
74
- .ows-ceremony-actions {
75
- flex: 0 0 auto;
76
- display: flex;
77
- flex-wrap: wrap;
78
- gap: 8px;
79
- }
80
- .ows-actions {
81
- flex: 0 0 auto;
82
- display: flex;
83
- flex-wrap: wrap;
84
- gap: 8px;
85
- }
86
- .ows-copy,
87
- .ows-confirm,
88
- .ows-deny {
89
- padding: 8px 12px;
90
- border: 1px solid #8884;
91
- border-radius: 4px;
92
- background: transparent;
93
- font: inherit;
94
- cursor: pointer;
95
- align-self: flex-start;
96
- }
97
- .ows-confirm {
98
- font-weight: 600;
99
- }
100
- .ows-confirm:disabled,
101
- .ows-deny:disabled {
102
- opacity: 0.45;
103
- cursor: default;
104
- }
105
- </style>
7
+ <link rel="stylesheet" href="./signer.css" />
106
8
  </head>
107
9
  <body>
108
10
  <div id="ows-signer-root"></div>
@@ -0,0 +1,190 @@
1
+ :root {
2
+ color-scheme: light;
3
+ --ows-bg: #f3f4f6;
4
+ --ows-surface: #ffffff;
5
+ --ows-border: #e5e7eb;
6
+ --ows-text: #111827;
7
+ --ows-text-muted: #6b7280;
8
+ --ows-primary: #2a9aa8;
9
+ --ows-primary-hover: #238a97;
10
+ --ows-radius-card: 20px;
11
+ --ows-radius-control: 10px;
12
+ font-family:
13
+ system-ui,
14
+ -apple-system,
15
+ "Segoe UI",
16
+ Roboto,
17
+ Helvetica,
18
+ Arial,
19
+ sans-serif;
20
+ }
21
+
22
+ html,
23
+ body {
24
+ height: 100%;
25
+ margin: 0;
26
+ overflow: hidden;
27
+ }
28
+
29
+ body {
30
+ box-sizing: border-box;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ background: var(--ows-bg);
35
+ color: var(--ows-text);
36
+ }
37
+
38
+ .ows-signer-ui {
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 12px;
42
+ box-sizing: border-box;
43
+ align-items: stretch;
44
+ justify-content: center;
45
+ margin: auto;
46
+ width: calc(100% - 24px);
47
+ max-width: 360px;
48
+ height: auto;
49
+ max-height: calc(100% - 24px);
50
+ padding: 28px 24px;
51
+ overflow: auto;
52
+ background: var(--ows-surface);
53
+ border: 1px solid var(--ows-border);
54
+ border-radius: var(--ows-radius-card);
55
+ box-shadow: 0 1px 2px rgb(0 0 0 / 4%);
56
+ text-align: center;
57
+ }
58
+
59
+ .ows-signer-ui--ceremony {
60
+ gap: 0;
61
+ }
62
+
63
+ .ows-signer-ui:empty {
64
+ display: none;
65
+ }
66
+
67
+ .ows-key {
68
+ margin: 0;
69
+ padding: 12px;
70
+ border: 1px solid var(--ows-border);
71
+ border-radius: var(--ows-radius-control);
72
+ background: var(--ows-surface);
73
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
74
+ font-size: 12px;
75
+ color: var(--ows-text);
76
+ word-break: break-all;
77
+ white-space: pre-wrap;
78
+ overflow: auto;
79
+ flex: 1 1 auto;
80
+ min-height: 0;
81
+ text-align: left;
82
+ }
83
+
84
+ .ows-password {
85
+ box-sizing: border-box;
86
+ width: 100%;
87
+ padding: 12px 14px;
88
+ border: 1px solid var(--ows-border);
89
+ border-radius: var(--ows-radius-control);
90
+ background: var(--ows-surface);
91
+ color: var(--ows-text);
92
+ font: inherit;
93
+ }
94
+
95
+ .ows-password:focus {
96
+ outline: 2px solid color-mix(in srgb, var(--ows-primary) 45%, transparent);
97
+ outline-offset: 1px;
98
+ }
99
+
100
+ .ows-ceremony-header {
101
+ flex: 0 0 auto;
102
+ margin: 0 0 8px;
103
+ font-size: 1.25rem;
104
+ font-weight: 700;
105
+ line-height: 1.3;
106
+ color: var(--ows-text);
107
+ }
108
+
109
+ .ows-ceremony-body {
110
+ flex: 0 1 auto;
111
+ min-height: 0;
112
+ overflow-x: hidden;
113
+ overflow-y: auto;
114
+ margin: 0 0 24px;
115
+ }
116
+
117
+ .ows-ceremony-text {
118
+ margin: 0;
119
+ font-size: 1rem;
120
+ font-weight: 400;
121
+ line-height: 1.45;
122
+ color: var(--ows-text-muted);
123
+ }
124
+
125
+ .ows-ceremony-actions,
126
+ .ows-actions {
127
+ flex: 0 0 auto;
128
+ display: flex;
129
+ flex-direction: column;
130
+ flex-wrap: nowrap;
131
+ gap: 12px;
132
+ width: 100%;
133
+ }
134
+
135
+ .ows-copy,
136
+ .ows-confirm,
137
+ .ows-deny {
138
+ box-sizing: border-box;
139
+ width: 100%;
140
+ min-height: 44px;
141
+ padding: 12px 16px;
142
+ border-radius: var(--ows-radius-control);
143
+ font: inherit;
144
+ font-size: 1rem;
145
+ font-weight: 600;
146
+ line-height: 1.2;
147
+ cursor: pointer;
148
+ align-self: stretch;
149
+ }
150
+
151
+ .ows-confirm {
152
+ border: none;
153
+ background: var(--ows-primary);
154
+ color: #ffffff;
155
+ }
156
+
157
+ .ows-confirm:hover:not(:disabled) {
158
+ background: var(--ows-primary-hover);
159
+ }
160
+
161
+ .ows-confirm:focus-visible {
162
+ outline: 2px solid var(--ows-primary);
163
+ outline-offset: 2px;
164
+ }
165
+
166
+ .ows-deny,
167
+ .ows-copy {
168
+ border: 1px solid var(--ows-border);
169
+ background: var(--ows-surface);
170
+ color: #4b5563;
171
+ font-weight: 500;
172
+ }
173
+
174
+ .ows-deny:hover:not(:disabled),
175
+ .ows-copy:hover:not(:disabled) {
176
+ background: #f9fafb;
177
+ }
178
+
179
+ .ows-deny:focus-visible,
180
+ .ows-copy:focus-visible {
181
+ outline: 2px solid color-mix(in srgb, var(--ows-primary) 50%, transparent);
182
+ outline-offset: 2px;
183
+ }
184
+
185
+ .ows-confirm:disabled,
186
+ .ows-deny:disabled,
187
+ .ows-copy:disabled {
188
+ opacity: 0.45;
189
+ cursor: default;
190
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1shotapi/ows-signer",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "OWS Signing Layer — plain JavaScript WebAuthn PRF key custody with curve-based signing (on-chain via EIP-8244)",
5
5
  "type": "module",
6
6
  "files": [
@@ -10,6 +10,7 @@
10
10
  ],
11
11
  "exports": {
12
12
  "./html": "./html/index.html",
13
+ "./html/signer.css": "./html/signer.css",
13
14
  "./src/*": "./src/*"
14
15
  },
15
16
  "scripts": {
package/src/handlers.js CHANGED
@@ -41,7 +41,7 @@ import {
41
41
  } from "./ui.js";
42
42
  import {
43
43
  createPasskeyCredential,
44
- getAssertionSignatureBase64Url,
44
+ getAssertionFieldsBase64Url,
45
45
  getCredentialId,
46
46
  getPasskeyAssertion,
47
47
  getCosePublicKeyBase64Url,
@@ -885,8 +885,8 @@ async function handleGetPublicKey(params, correlationId, targetOrigin) {
885
885
  });
886
886
 
887
887
  if (challenge) {
888
- const signature = getAssertionSignatureBase64Url(credential);
889
- if (signature) {
888
+ const assertion = getAssertionFieldsBase64Url(credential);
889
+ if (assertion) {
890
890
  emitEvent(
891
891
  window.parent,
892
892
  targetOrigin,
@@ -894,7 +894,7 @@ async function handleGetPublicKey(params, correlationId, targetOrigin) {
894
894
  correlationId,
895
895
  {
896
896
  challenge: to0xHex(challenge),
897
- signature,
897
+ assertion,
898
898
  },
899
899
  );
900
900
  }
@@ -1014,9 +1014,9 @@ async function handleExecuteBatch(params, correlationId, targetOrigin) {
1014
1014
  };
1015
1015
  }
1016
1016
  if (challenge && credential) {
1017
- const signature = getAssertionSignatureBase64Url(credential);
1018
- if (signature) {
1019
- data.challengeSignature = signature;
1017
+ const assertion = getAssertionFieldsBase64Url(credential);
1018
+ if (assertion) {
1019
+ data.assertion = assertion;
1020
1020
  }
1021
1021
  }
1022
1022
 
package/src/webauthn.js CHANGED
@@ -245,3 +245,33 @@ export function getAssertionSignatureBase64Url(credential) {
245
245
  }
246
246
  return null;
247
247
  }
248
+
249
+ /**
250
+ * Full WebAuthn assertion fields for host/relayer auth (base64url).
251
+ * @param {PublicKeyCredential} credential
252
+ * @returns {{
253
+ * authenticatorData: string,
254
+ * clientDataJSON: string,
255
+ * signature: string,
256
+ * credentialId: string,
257
+ * } | null}
258
+ */
259
+ export function getAssertionFieldsBase64Url(credential) {
260
+ const response = credential.response;
261
+ if (
262
+ !("authenticatorData" in response) ||
263
+ !response.authenticatorData ||
264
+ !("clientDataJSON" in response) ||
265
+ !response.clientDataJSON ||
266
+ !("signature" in response) ||
267
+ !response.signature
268
+ ) {
269
+ return null;
270
+ }
271
+ return {
272
+ authenticatorData: bufferToBase64Url(response.authenticatorData),
273
+ clientDataJSON: bufferToBase64Url(response.clientDataJSON),
274
+ signature: bufferToBase64Url(response.signature),
275
+ credentialId: getCredentialId(credential),
276
+ };
277
+ }