@originator-profile/opvc 0.4.0-beta.1
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 +485 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.ts +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/chunk-Bp6m_JJh.js +13 -0
- package/dist/commands/ca/sign.d.ts +29 -0
- package/dist/commands/ca/sign.js +71 -0
- package/dist/commands/ca/unsigned.d.ts +17 -0
- package/dist/commands/ca/unsigned.js +74 -0
- package/dist/commands/key-gen/index.d.ts +13 -0
- package/dist/commands/key-gen/index.js +24 -0
- package/dist/commands/sign.d.ts +30 -0
- package/dist/commands/sign.js +151 -0
- package/dist/commands/wsp/unsigned.d.ts +17 -0
- package/dist/commands/wsp/unsigned.js +59 -0
- package/dist/content-attestation-2NuarC85.js +75 -0
- package/dist/flags-BuWTYwYw.js +38 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +4 -0
- package/dist/website-profile-D43BL7p3.js +27 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
# opvc - Originator Profile Verifiable Credential command line tool
|
|
2
|
+
|
|
3
|
+
Originator Profile (OP) 仕様に準拠した Verifiable Credential (VC) を作成・管理するためのツールです。
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### From source
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
git clone https://github.com/originator-profile/profile-share.git
|
|
11
|
+
cd profile-share/packages/opvc
|
|
12
|
+
pnpm install
|
|
13
|
+
npm i -g .
|
|
14
|
+
opvc
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Using `npx` / `npm`
|
|
18
|
+
|
|
19
|
+
GitHub Packages にアクセスするため .npmrc 設定が必要です。
|
|
20
|
+
|
|
21
|
+
1. GitHub の [Personal Access Token (classic)](https://github.com/settings/tokens) を発行
|
|
22
|
+
- 必要なスコープ:
|
|
23
|
+
- `read:packages`
|
|
24
|
+
- `repo`
|
|
25
|
+
2. GitHub Packages のレジストリと認証トークンを設定
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm config set @originator-profile:registry https://npm.pkg.github.com
|
|
29
|
+
npm config set //npm.pkg.github.com/:_authToken YOUR_PERSONAL_ACCESS_TOKEN
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
# npx
|
|
34
|
+
npx -y @originator-profile/opvc
|
|
35
|
+
|
|
36
|
+
# npm
|
|
37
|
+
npm i -g @originator-profile/opvc
|
|
38
|
+
opvc
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
<!-- prettier-ignore-start -->
|
|
44
|
+
<!-- commands -->
|
|
45
|
+
* [`opvc ca:sign`](#opvc-casign)
|
|
46
|
+
* [`opvc ca:unsigned`](#opvc-caunsigned)
|
|
47
|
+
* [`opvc help [COMMAND]`](#opvc-help-command)
|
|
48
|
+
* [`opvc key-gen`](#opvc-key-gen)
|
|
49
|
+
* [`opvc sign`](#opvc-sign)
|
|
50
|
+
* [`opvc wsp:unsigned`](#opvc-wspunsigned)
|
|
51
|
+
|
|
52
|
+
## `opvc ca:sign`
|
|
53
|
+
|
|
54
|
+
Content Attestation の作成
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
USAGE
|
|
58
|
+
$ opvc ca:sign -i <value> --input <filepath> [--issued-at <value>] [--expired-at <value>]
|
|
59
|
+
|
|
60
|
+
FLAGS
|
|
61
|
+
-i, --identity=<value> (required) プライベート鍵のファイルパス
|
|
62
|
+
--expired-at=<value> 有効期限 (ISO 8601)
|
|
63
|
+
--input=<filepath> (required) 入力ファイルのパス (JSON 形式)
|
|
64
|
+
--issued-at=<value> 発行日時 (ISO 8601)
|
|
65
|
+
|
|
66
|
+
DESCRIPTION
|
|
67
|
+
Content Attestation の作成
|
|
68
|
+
|
|
69
|
+
標準出力に Content Attestation を出力します。
|
|
70
|
+
|
|
71
|
+
EXAMPLES
|
|
72
|
+
$ opvc ca:sign \
|
|
73
|
+
-i account-key.example.priv.json \
|
|
74
|
+
--input article-content-attestation.example.json
|
|
75
|
+
|
|
76
|
+
FLAG DESCRIPTIONS
|
|
77
|
+
-i, --identity=<value> プライベート鍵のファイルパス
|
|
78
|
+
|
|
79
|
+
プライベート鍵のファイルパスを渡してください。プライベート鍵は JWK 形式か、PEM base64 でエンコードされた PKCS #8
|
|
80
|
+
形式にしてください。
|
|
81
|
+
|
|
82
|
+
--expired-at=<value> 有効期限 (ISO 8601)
|
|
83
|
+
|
|
84
|
+
日付のみの場合、その日の 24:00:00.000 より前まで有効、それ以外の場合、期限切れとなる日付・時刻・秒を指定します。
|
|
85
|
+
|
|
86
|
+
--input=<filepath> 入力ファイルのパス (JSON 形式)
|
|
87
|
+
|
|
88
|
+
Article Content Attestation の例:
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
"@context": [
|
|
92
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
93
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
94
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
95
|
+
{
|
|
96
|
+
"@language": "ja"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"type": [
|
|
100
|
+
"VerifiableCredential",
|
|
101
|
+
"ContentAttestation"
|
|
102
|
+
],
|
|
103
|
+
"issuer": "dns:example.com",
|
|
104
|
+
"credentialSubject": {
|
|
105
|
+
"id": "urn:uuid:78550fa7-f846-4e0f-ad5c-8d34461cb95b",
|
|
106
|
+
"type": "Article",
|
|
107
|
+
"headline": "<Webページのタイトル>",
|
|
108
|
+
"image": {
|
|
109
|
+
"id": "<サムネイル画像URL>",
|
|
110
|
+
"content": [
|
|
111
|
+
"<コンテンツ (data:// 形式URL)>"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"description": "<Webページの説明>",
|
|
115
|
+
"author": [
|
|
116
|
+
"山田花子"
|
|
117
|
+
],
|
|
118
|
+
"editor": [
|
|
119
|
+
"山田太郎"
|
|
120
|
+
],
|
|
121
|
+
"datePublished": "2023-07-04T19:14:00Z",
|
|
122
|
+
"dateModified": "2023-07-04T19:14:00Z",
|
|
123
|
+
"genre": "Arts & Entertainment"
|
|
124
|
+
},
|
|
125
|
+
"allowedUrl": "https://media.example.com/articles/2024-06-30",
|
|
126
|
+
"target": [
|
|
127
|
+
{
|
|
128
|
+
"type": "<Target Integrityの種別>",
|
|
129
|
+
"content": [
|
|
130
|
+
"<コンテンツ本体 (text/html or URL)>"
|
|
131
|
+
],
|
|
132
|
+
"cssSelector": "<CSS セレクター (optional)>"
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## `opvc ca:unsigned`
|
|
139
|
+
|
|
140
|
+
未署名 Content Attestation の取得
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
USAGE
|
|
144
|
+
$ opvc ca:unsigned --input <filepath> [--issued-at <value>] [--expired-at <value>]
|
|
145
|
+
|
|
146
|
+
FLAGS
|
|
147
|
+
--expired-at=<value> 有効期限 (ISO 8601)
|
|
148
|
+
--input=<filepath> (required) 入力ファイルのパス (JSON 形式)
|
|
149
|
+
--issued-at=<value> 発行日時 (ISO 8601)
|
|
150
|
+
|
|
151
|
+
DESCRIPTION
|
|
152
|
+
未署名 Content Attestation の取得
|
|
153
|
+
|
|
154
|
+
標準出力に未署名 Content Attestation を出力します。
|
|
155
|
+
target[].integrity を省略した場合、type に準じて content から integrity を計算します。
|
|
156
|
+
一方、target[].integrity が含まれる場合、その値をそのまま使用します。
|
|
157
|
+
なお、いずれも target[].content プロパティが削除される点にご注意ください。
|
|
158
|
+
これにより入力ファイルの target[] と異なる結果が含まれますが、これは正しい動作です。
|
|
159
|
+
|
|
160
|
+
EXAMPLES
|
|
161
|
+
$ opvc ca:unsigned \
|
|
162
|
+
--input article-content-attestation.example.json
|
|
163
|
+
|
|
164
|
+
FLAG DESCRIPTIONS
|
|
165
|
+
--expired-at=<value> 有効期限 (ISO 8601)
|
|
166
|
+
|
|
167
|
+
日付のみの場合、その日の 24:00:00.000 より前まで有効、それ以外の場合、期限切れとなる日付・時刻・秒を指定します。
|
|
168
|
+
|
|
169
|
+
--input=<filepath> 入力ファイルのパス (JSON 形式)
|
|
170
|
+
|
|
171
|
+
Article Content Attestation の例:
|
|
172
|
+
|
|
173
|
+
{
|
|
174
|
+
"@context": [
|
|
175
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
176
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
177
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
178
|
+
{
|
|
179
|
+
"@language": "<言語・地域コード>"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"type": [
|
|
183
|
+
"VerifiableCredential",
|
|
184
|
+
"ContentAttestation"
|
|
185
|
+
],
|
|
186
|
+
"issuer": "<OP ID>",
|
|
187
|
+
"credentialSubject": {
|
|
188
|
+
"id": "<CA ID>",
|
|
189
|
+
"type": "Article",
|
|
190
|
+
"headline": "<コンテンツのタイトル>",
|
|
191
|
+
"description": "<コンテンツの説明>",
|
|
192
|
+
"image": {
|
|
193
|
+
"id": "<サムネイル画像URL>",
|
|
194
|
+
"content": [
|
|
195
|
+
"<コンテンツ (data:// 形式URL)>"
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
"datePublished": "<公開日時>",
|
|
199
|
+
"dateModified": "<最終更新日時>",
|
|
200
|
+
"author": [
|
|
201
|
+
"<著者名>"
|
|
202
|
+
],
|
|
203
|
+
"editor": [
|
|
204
|
+
"<編集者名>"
|
|
205
|
+
],
|
|
206
|
+
"genre": "<ジャンル>"
|
|
207
|
+
},
|
|
208
|
+
"allowedUrl": "<CAの使用を許可するWebページのURL Pattern>",
|
|
209
|
+
"target": [
|
|
210
|
+
{
|
|
211
|
+
"type": "<Target Integrityの種別>",
|
|
212
|
+
"content": [
|
|
213
|
+
"<コンテンツ本体 (text/html or URL)>"
|
|
214
|
+
],
|
|
215
|
+
"cssSelector": "<CSS セレクター (optional)>"
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## `opvc help [COMMAND]`
|
|
222
|
+
|
|
223
|
+
Display help for opvc.
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
USAGE
|
|
227
|
+
$ opvc help [COMMAND...] [-n]
|
|
228
|
+
|
|
229
|
+
ARGUMENTS
|
|
230
|
+
COMMAND... Command to show help for.
|
|
231
|
+
|
|
232
|
+
FLAGS
|
|
233
|
+
-n, --nested-commands Include all nested commands in the output.
|
|
234
|
+
|
|
235
|
+
DESCRIPTION
|
|
236
|
+
Display help for opvc.
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.29/src/commands/help.ts)_
|
|
240
|
+
|
|
241
|
+
## `opvc key-gen`
|
|
242
|
+
|
|
243
|
+
鍵ペアの生成
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
USAGE
|
|
247
|
+
$ opvc key-gen -o <value>
|
|
248
|
+
|
|
249
|
+
FLAGS
|
|
250
|
+
-o, --output=<value> (required) 鍵を保存するファイル名(拡張子除く)。<output>.priv.json と <output>.pub.json
|
|
251
|
+
を出力します。
|
|
252
|
+
|
|
253
|
+
DESCRIPTION
|
|
254
|
+
鍵ペアの生成
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## `opvc sign`
|
|
258
|
+
|
|
259
|
+
VC の作成
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
USAGE
|
|
263
|
+
$ opvc sign -i <value> --input <filepath> [--id <value>] [--issued-at <value>] [--expired-at <value>]
|
|
264
|
+
|
|
265
|
+
FLAGS
|
|
266
|
+
-i, --identity=<value> (required) プライベート鍵のファイルパス
|
|
267
|
+
--expired-at=<value> 有効期限 (ISO 8601)
|
|
268
|
+
--id=<value> OP ID (ドメイン名)
|
|
269
|
+
--input=<filepath> (required) 入力ファイルのパス (JSON 形式)
|
|
270
|
+
--issued-at=<value> 発行日時 (ISO 8601)
|
|
271
|
+
|
|
272
|
+
DESCRIPTION
|
|
273
|
+
VC の作成
|
|
274
|
+
|
|
275
|
+
VC に署名します。
|
|
276
|
+
標準出力に VC を出力します。
|
|
277
|
+
|
|
278
|
+
EXAMPLES
|
|
279
|
+
$ opvc sign \
|
|
280
|
+
-i example.priv.json \
|
|
281
|
+
--id example.com \
|
|
282
|
+
--input core-profile.json
|
|
283
|
+
|
|
284
|
+
$ opvc sign \
|
|
285
|
+
-i example.priv.json \
|
|
286
|
+
--id example.org \
|
|
287
|
+
--input web-media-profile.json
|
|
288
|
+
|
|
289
|
+
$ opvc sign \
|
|
290
|
+
-i account-key.example.priv.json \
|
|
291
|
+
--input website-profile.example.json
|
|
292
|
+
|
|
293
|
+
FLAG DESCRIPTIONS
|
|
294
|
+
-i, --identity=<value> プライベート鍵のファイルパス
|
|
295
|
+
|
|
296
|
+
プライベート鍵のファイルパスを渡してください。プライベート鍵は JWK 形式か、PEM base64 でエンコードされた PKCS #8
|
|
297
|
+
形式にしてください。
|
|
298
|
+
|
|
299
|
+
--expired-at=<value> 有効期限 (ISO 8601)
|
|
300
|
+
|
|
301
|
+
日付のみの場合、その日の 24:00:00.000 より前まで有効、それ以外の場合、期限切れとなる日付・時刻・秒を指定します。
|
|
302
|
+
|
|
303
|
+
--id=<value> OP ID (ドメイン名)
|
|
304
|
+
|
|
305
|
+
ドメイン名 (RFC 4501) を指定します。
|
|
306
|
+
|
|
307
|
+
--input=<filepath> 入力ファイルのパス (JSON 形式)
|
|
308
|
+
|
|
309
|
+
コアプロファイル (CP) の例:
|
|
310
|
+
|
|
311
|
+
{
|
|
312
|
+
"@context": [
|
|
313
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
314
|
+
"https://originator-profile.org/ns/credentials/v1"
|
|
315
|
+
],
|
|
316
|
+
"type": [
|
|
317
|
+
"VerifiableCredential",
|
|
318
|
+
"CoreProfile"
|
|
319
|
+
],
|
|
320
|
+
"issuer": "dns:example.org",
|
|
321
|
+
"credentialSubject": {
|
|
322
|
+
"id": "dns:example.jp",
|
|
323
|
+
"type": "Core",
|
|
324
|
+
"jwks": {
|
|
325
|
+
"keys": [
|
|
326
|
+
{
|
|
327
|
+
"kid": "LIstkoCvByn4jk8oZPvigQkzTzO9UwnGnE-VMlkZvYQ",
|
|
328
|
+
"kty": "EC",
|
|
329
|
+
"crv": "P-256",
|
|
330
|
+
"x": "QiVI-I-3gv-17KN0RFLHKh5Vj71vc75eSOkyMsxFxbE",
|
|
331
|
+
"y": "bEzRDEy41bihcTnpSILImSVymTQl9BQZq36QpCpJQnI"
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
ウェブメディアプロファイル (WMP) の例:
|
|
339
|
+
|
|
340
|
+
{
|
|
341
|
+
"@context": [
|
|
342
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
343
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
344
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
345
|
+
{
|
|
346
|
+
"@language": "ja"
|
|
347
|
+
}
|
|
348
|
+
],
|
|
349
|
+
"type": [
|
|
350
|
+
"VerifiableCredential",
|
|
351
|
+
"WebMediaProfile"
|
|
352
|
+
],
|
|
353
|
+
"issuer": "dns:wmp-issuer.example.org",
|
|
354
|
+
"credentialSubject": {
|
|
355
|
+
"id": "dns:wmp-holder.example.jp",
|
|
356
|
+
"type": "OnlineBusiness",
|
|
357
|
+
"url": "https://www.wmp-holder.example.jp/",
|
|
358
|
+
"name": "○○メディア (※開発用サンプル)",
|
|
359
|
+
"logo": {
|
|
360
|
+
"id": "https://www.wmp-holder.example.jp/image.png",
|
|
361
|
+
"digestSRI": "sha256-Upwn7gYMuRmJlD1ZivHk876vXHzokXrwXj50VgfnMnY="
|
|
362
|
+
},
|
|
363
|
+
"email": "contact@wmp-holder.example.jp",
|
|
364
|
+
"telephone": "0000000000",
|
|
365
|
+
"contactPoint": {
|
|
366
|
+
"id": "https://wmp-holder.example.jp/contact",
|
|
367
|
+
"name": "お問い合わせ"
|
|
368
|
+
},
|
|
369
|
+
"informationTransmissionPolicy": {
|
|
370
|
+
"id": "https://wmp-holder.example.jp/statement",
|
|
371
|
+
"name": "情報発信ポリシー"
|
|
372
|
+
},
|
|
373
|
+
"privacyPolicy": {
|
|
374
|
+
"id": "https://wmp-holder.example.jp/privacy",
|
|
375
|
+
"name": "プライバシーポリシー"
|
|
376
|
+
},
|
|
377
|
+
"description": "この文章はこの Web メディアに関する補足情報です。"
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
Website Profile (WSP) の例:
|
|
382
|
+
|
|
383
|
+
{
|
|
384
|
+
"@context": [
|
|
385
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
386
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
387
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
388
|
+
{
|
|
389
|
+
"@language": "ja"
|
|
390
|
+
}
|
|
391
|
+
],
|
|
392
|
+
"type": [
|
|
393
|
+
"VerifiableCredential",
|
|
394
|
+
"WebsiteProfile"
|
|
395
|
+
],
|
|
396
|
+
"issuer": "dns:example.com",
|
|
397
|
+
"credentialSubject": {
|
|
398
|
+
"id": "https://media.example.com/",
|
|
399
|
+
"type": "WebSite",
|
|
400
|
+
"name": "<Webサイトのタイトル>",
|
|
401
|
+
"description": "<Webサイトの説明>",
|
|
402
|
+
"image": {
|
|
403
|
+
"id": "https://media.example.com/image.png",
|
|
404
|
+
"digestSRI": "sha256-Upwn7gYMuRmJlD1ZivHk876vXHzokXrwXj50VgfnMnY="
|
|
405
|
+
},
|
|
406
|
+
"allowedOrigin": [
|
|
407
|
+
"https://media.example.com"
|
|
408
|
+
]
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## `opvc wsp:unsigned`
|
|
414
|
+
|
|
415
|
+
未署名 Website Profile の取得
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
USAGE
|
|
419
|
+
$ opvc wsp:unsigned --input <filepath> [--issued-at <value>] [--expired-at <value>]
|
|
420
|
+
|
|
421
|
+
FLAGS
|
|
422
|
+
--expired-at=<value> 有効期限 (ISO 8601)
|
|
423
|
+
--input=<filepath> (required) 入力ファイルのパス (JSON 形式)
|
|
424
|
+
--issued-at=<value> 発行日時 (ISO 8601)
|
|
425
|
+
|
|
426
|
+
DESCRIPTION
|
|
427
|
+
未署名 Website Profile の取得
|
|
428
|
+
|
|
429
|
+
標準出力に未署名 Website Profile を出力します。
|
|
430
|
+
|
|
431
|
+
EXAMPLES
|
|
432
|
+
$ opvc wsp:unsigned \
|
|
433
|
+
--input website-profile.example.json
|
|
434
|
+
|
|
435
|
+
FLAG DESCRIPTIONS
|
|
436
|
+
--expired-at=<value> 有効期限 (ISO 8601)
|
|
437
|
+
|
|
438
|
+
日付のみの場合、その日の 24:00:00.000 より前まで有効、それ以外の場合、期限切れとなる日付・時刻・秒を指定します。
|
|
439
|
+
|
|
440
|
+
--input=<filepath> 入力ファイルのパス (JSON 形式)
|
|
441
|
+
|
|
442
|
+
Website Profile の例:
|
|
443
|
+
|
|
444
|
+
{
|
|
445
|
+
"@context": [
|
|
446
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
447
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
448
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
449
|
+
{
|
|
450
|
+
"@language": "<言語・地域コード>"
|
|
451
|
+
}
|
|
452
|
+
],
|
|
453
|
+
"type": [
|
|
454
|
+
"VerifiableCredential",
|
|
455
|
+
"WebsiteProfile"
|
|
456
|
+
],
|
|
457
|
+
"issuer": "<OP ID>",
|
|
458
|
+
"credentialSubject": {
|
|
459
|
+
"id": "<Web サイトのオリジン (形式: https://<ホスト名>)>",
|
|
460
|
+
"type": "WebSite",
|
|
461
|
+
"name": "<Web サイトの名称>",
|
|
462
|
+
"description": "<Web サイトの説明>",
|
|
463
|
+
"image": {
|
|
464
|
+
"id": "<サムネイル画像URL>",
|
|
465
|
+
"content": [
|
|
466
|
+
"<コンテンツ (data:// 形式URL)>"
|
|
467
|
+
]
|
|
468
|
+
},
|
|
469
|
+
"allowedOrigin": [
|
|
470
|
+
"<Web サイトのオリジン (形式: https://<ホスト名>)>"
|
|
471
|
+
]
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
<!-- commandsstop -->
|
|
476
|
+
<!-- prettier-ignore-end -->
|
|
477
|
+
|
|
478
|
+
## Development
|
|
479
|
+
|
|
480
|
+
```sh
|
|
481
|
+
git clone https://github.com/originator-profile/profile-share.git
|
|
482
|
+
cd profile-share/packages/opvc
|
|
483
|
+
pnpm install
|
|
484
|
+
bin/dev.ts
|
|
485
|
+
```
|
package/bin/dev.cmd
ADDED
package/bin/dev.ts
ADDED
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (all) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
return target;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __export as t };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import * as _oclif_core_interfaces15 from "@oclif/core/interfaces";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/ca/sign.d.ts
|
|
5
|
+
declare class CaSign extends Command {
|
|
6
|
+
static summary: string;
|
|
7
|
+
static description: string;
|
|
8
|
+
static flags: {
|
|
9
|
+
identity: _oclif_core_interfaces15.OptionFlag<{
|
|
10
|
+
[x: string]: unknown;
|
|
11
|
+
use?: string | undefined;
|
|
12
|
+
key_ops?: string[] | undefined;
|
|
13
|
+
alg?: string | undefined;
|
|
14
|
+
x5u?: string | undefined;
|
|
15
|
+
x5c?: string[] | undefined;
|
|
16
|
+
x5t?: string | undefined;
|
|
17
|
+
"x5t#S256"?: string | undefined;
|
|
18
|
+
kty: string;
|
|
19
|
+
kid: string;
|
|
20
|
+
}, _oclif_core_interfaces15.CustomOptions>;
|
|
21
|
+
input: _oclif_core_interfaces15.OptionFlag<string, _oclif_core_interfaces15.CustomOptions>;
|
|
22
|
+
"issued-at": _oclif_core_interfaces15.OptionFlag<string | undefined, _oclif_core_interfaces15.CustomOptions>;
|
|
23
|
+
"expired-at": _oclif_core_interfaces15.OptionFlag<Date | undefined, _oclif_core_interfaces15.CustomOptions>;
|
|
24
|
+
};
|
|
25
|
+
static examples: string[];
|
|
26
|
+
run(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { CaSign };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { n as sign } from "../../content-attestation-2NuarC85.js";
|
|
2
|
+
import { r as privateKey, t as expirationDate } from "../../flags-BuWTYwYw.js";
|
|
3
|
+
import { Command, Flags } from "@oclif/core";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
|
|
6
|
+
//#region src/commands/ca/sign.ts
|
|
7
|
+
const exampleArticleContentAttestation = {
|
|
8
|
+
"@context": [
|
|
9
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
10
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
11
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
12
|
+
{ "@language": "ja" }
|
|
13
|
+
],
|
|
14
|
+
type: ["VerifiableCredential", "ContentAttestation"],
|
|
15
|
+
issuer: "dns:example.com",
|
|
16
|
+
credentialSubject: {
|
|
17
|
+
id: "urn:uuid:78550fa7-f846-4e0f-ad5c-8d34461cb95b",
|
|
18
|
+
type: "Article",
|
|
19
|
+
headline: "<Webページのタイトル>",
|
|
20
|
+
image: {
|
|
21
|
+
id: "<サムネイル画像URL>",
|
|
22
|
+
content: ["<コンテンツ (data:// 形式URL)>"]
|
|
23
|
+
},
|
|
24
|
+
description: "<Webページの説明>",
|
|
25
|
+
author: ["山田花子"],
|
|
26
|
+
editor: ["山田太郎"],
|
|
27
|
+
datePublished: "2023-07-04T19:14:00Z",
|
|
28
|
+
dateModified: "2023-07-04T19:14:00Z",
|
|
29
|
+
genre: "Arts & Entertainment"
|
|
30
|
+
},
|
|
31
|
+
allowedUrl: "https://media.example.com/articles/2024-06-30",
|
|
32
|
+
target: [{
|
|
33
|
+
type: "<Target Integrityの種別>",
|
|
34
|
+
content: ["<コンテンツ本体 (text/html or URL)>"],
|
|
35
|
+
cssSelector: "<CSS セレクター (optional)>"
|
|
36
|
+
}]
|
|
37
|
+
};
|
|
38
|
+
var CaSign = class CaSign extends Command {
|
|
39
|
+
static summary = "Content Attestation の作成";
|
|
40
|
+
static description = "標準出力に Content Attestation を出力します。";
|
|
41
|
+
static flags = {
|
|
42
|
+
identity: privateKey({ required: true }),
|
|
43
|
+
input: Flags.string({
|
|
44
|
+
summary: "入力ファイルのパス (JSON 形式)",
|
|
45
|
+
helpValue: "<filepath>",
|
|
46
|
+
description: `\
|
|
47
|
+
Article Content Attestation の例:
|
|
48
|
+
|
|
49
|
+
${JSON.stringify(exampleArticleContentAttestation, null, " ")}`,
|
|
50
|
+
required: true
|
|
51
|
+
}),
|
|
52
|
+
"issued-at": Flags.string({ description: "発行日時 (ISO 8601)" }),
|
|
53
|
+
"expired-at": expirationDate()
|
|
54
|
+
};
|
|
55
|
+
static examples = [`\
|
|
56
|
+
$ <%= config.bin %> <%= command.id %> \\
|
|
57
|
+
-i account-key.example.priv.json \\
|
|
58
|
+
--input article-content-attestation.example.json`];
|
|
59
|
+
async run() {
|
|
60
|
+
const { flags } = await this.parse(CaSign);
|
|
61
|
+
const inputBuffer = await fs.readFile(flags.input);
|
|
62
|
+
const ca = await sign(JSON.parse(inputBuffer.toString()), flags.identity, {
|
|
63
|
+
issuedAt: flags["issued-at"],
|
|
64
|
+
expiredAt: flags["expired-at"]
|
|
65
|
+
});
|
|
66
|
+
this.log(ca);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { CaSign };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import * as _oclif_core_interfaces0 from "@oclif/core/interfaces";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/ca/unsigned.d.ts
|
|
5
|
+
declare class CaUnsigned extends Command {
|
|
6
|
+
static summary: string;
|
|
7
|
+
static description: string;
|
|
8
|
+
static flags: {
|
|
9
|
+
input: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
|
|
10
|
+
"issued-at": _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
11
|
+
"expired-at": _oclif_core_interfaces0.OptionFlag<Date | undefined, _oclif_core_interfaces0.CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
static examples: string[];
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { CaUnsigned };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { r as unsignedCa } from "../../content-attestation-2NuarC85.js";
|
|
2
|
+
import { t as expirationDate } from "../../flags-BuWTYwYw.js";
|
|
3
|
+
import { Command, Flags } from "@oclif/core";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
|
|
6
|
+
//#region src/commands/ca/unsigned.ts
|
|
7
|
+
const exampleArticleContentAttestation = {
|
|
8
|
+
"@context": [
|
|
9
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
10
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
11
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
12
|
+
{ "@language": "<言語・地域コード>" }
|
|
13
|
+
],
|
|
14
|
+
type: ["VerifiableCredential", "ContentAttestation"],
|
|
15
|
+
issuer: "<OP ID>",
|
|
16
|
+
credentialSubject: {
|
|
17
|
+
id: "<CA ID>",
|
|
18
|
+
type: "Article",
|
|
19
|
+
headline: "<コンテンツのタイトル>",
|
|
20
|
+
description: "<コンテンツの説明>",
|
|
21
|
+
image: {
|
|
22
|
+
id: "<サムネイル画像URL>",
|
|
23
|
+
content: ["<コンテンツ (data:// 形式URL)>"]
|
|
24
|
+
},
|
|
25
|
+
datePublished: "<公開日時>",
|
|
26
|
+
dateModified: "<最終更新日時>",
|
|
27
|
+
author: ["<著者名>"],
|
|
28
|
+
editor: ["<編集者名>"],
|
|
29
|
+
genre: "<ジャンル>"
|
|
30
|
+
},
|
|
31
|
+
allowedUrl: "<CAの使用を許可するWebページのURL Pattern>",
|
|
32
|
+
target: [{
|
|
33
|
+
type: "<Target Integrityの種別>",
|
|
34
|
+
content: ["<コンテンツ本体 (text/html or URL)>"],
|
|
35
|
+
cssSelector: "<CSS セレクター (optional)>"
|
|
36
|
+
}]
|
|
37
|
+
};
|
|
38
|
+
var CaUnsigned = class CaUnsigned extends Command {
|
|
39
|
+
static summary = "未署名 Content Attestation の取得";
|
|
40
|
+
static description = `\
|
|
41
|
+
標準出力に未署名 Content Attestation を出力します。
|
|
42
|
+
target[].integrity を省略した場合、type に準じて content から integrity を計算します。
|
|
43
|
+
一方、target[].integrity が含まれる場合、その値をそのまま使用します。
|
|
44
|
+
なお、いずれも target[].content プロパティが削除される点にご注意ください。
|
|
45
|
+
これにより入力ファイルの target[] と異なる結果が含まれますが、これは正しい動作です。`;
|
|
46
|
+
static flags = {
|
|
47
|
+
input: Flags.string({
|
|
48
|
+
summary: "入力ファイルのパス (JSON 形式)",
|
|
49
|
+
helpValue: "<filepath>",
|
|
50
|
+
description: `\
|
|
51
|
+
Article Content Attestation の例:
|
|
52
|
+
|
|
53
|
+
${JSON.stringify(exampleArticleContentAttestation, null, " ")}`,
|
|
54
|
+
required: true
|
|
55
|
+
}),
|
|
56
|
+
"issued-at": Flags.string({ description: "発行日時 (ISO 8601)" }),
|
|
57
|
+
"expired-at": expirationDate()
|
|
58
|
+
};
|
|
59
|
+
static examples = [`\
|
|
60
|
+
$ <%= config.bin %> <%= command.id %> \\
|
|
61
|
+
--input article-content-attestation.example.json`];
|
|
62
|
+
async run() {
|
|
63
|
+
const { flags } = await this.parse(CaUnsigned);
|
|
64
|
+
const inputBuffer = await fs.readFile(flags.input);
|
|
65
|
+
const uca = await unsignedCa(JSON.parse(inputBuffer.toString()), {
|
|
66
|
+
issuedAt: flags["issued-at"],
|
|
67
|
+
expiredAt: flags["expired-at"]
|
|
68
|
+
});
|
|
69
|
+
this.logJson(uca);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { CaUnsigned };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import * as _oclif_core_interfaces23 from "@oclif/core/interfaces";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/key-gen/index.d.ts
|
|
5
|
+
declare class KeyGen extends Command {
|
|
6
|
+
static description: string;
|
|
7
|
+
static flags: {
|
|
8
|
+
output: _oclif_core_interfaces23.OptionFlag<string, _oclif_core_interfaces23.CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { KeyGen };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command, Flags } from "@oclif/core";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { generateKey } from "@originator-profile/cryptography";
|
|
4
|
+
|
|
5
|
+
//#region src/commands/key-gen/index.ts
|
|
6
|
+
var KeyGen = class KeyGen extends Command {
|
|
7
|
+
static description = "鍵ペアの生成";
|
|
8
|
+
static flags = { output: Flags.string({
|
|
9
|
+
char: "o",
|
|
10
|
+
description: "鍵を保存するファイル名(拡張子除く)。<output>.priv.json と <output>.pub.json を出力します。",
|
|
11
|
+
required: true
|
|
12
|
+
}) };
|
|
13
|
+
async run() {
|
|
14
|
+
const { flags } = await this.parse(KeyGen);
|
|
15
|
+
const { publicKey, privateKey } = await generateKey();
|
|
16
|
+
const privateKeyFilename = `${flags.output}.priv.json`;
|
|
17
|
+
const publicKeyFilename = `${flags.output}.pub.json`;
|
|
18
|
+
await fs.writeFile(publicKeyFilename, JSON.stringify(publicKey, null, 2));
|
|
19
|
+
await fs.writeFile(privateKeyFilename, JSON.stringify(privateKey, null, 2));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { KeyGen };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import * as _oclif_core_interfaces5 from "@oclif/core/interfaces";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/sign.d.ts
|
|
5
|
+
declare class VcSign extends Command {
|
|
6
|
+
static summary: string;
|
|
7
|
+
static description: string;
|
|
8
|
+
static flags: {
|
|
9
|
+
identity: _oclif_core_interfaces5.OptionFlag<{
|
|
10
|
+
[x: string]: unknown;
|
|
11
|
+
use?: string | undefined;
|
|
12
|
+
key_ops?: string[] | undefined;
|
|
13
|
+
alg?: string | undefined;
|
|
14
|
+
x5u?: string | undefined;
|
|
15
|
+
x5c?: string[] | undefined;
|
|
16
|
+
x5t?: string | undefined;
|
|
17
|
+
"x5t#S256"?: string | undefined;
|
|
18
|
+
kty: string;
|
|
19
|
+
kid: string;
|
|
20
|
+
}, _oclif_core_interfaces5.CustomOptions>;
|
|
21
|
+
id: _oclif_core_interfaces5.OptionFlag<string | undefined, _oclif_core_interfaces5.CustomOptions>;
|
|
22
|
+
input: _oclif_core_interfaces5.OptionFlag<string, _oclif_core_interfaces5.CustomOptions>;
|
|
23
|
+
"issued-at": _oclif_core_interfaces5.OptionFlag<string | undefined, _oclif_core_interfaces5.CustomOptions>;
|
|
24
|
+
"expired-at": _oclif_core_interfaces5.OptionFlag<Date | undefined, _oclif_core_interfaces5.CustomOptions>;
|
|
25
|
+
};
|
|
26
|
+
static examples: string[];
|
|
27
|
+
run(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { VcSign };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { n as opId, r as privateKey, t as expirationDate } from "../flags-BuWTYwYw.js";
|
|
2
|
+
import { fetchAndSetDigestSri } from "@originator-profile/sign";
|
|
3
|
+
import { addYears } from "date-fns";
|
|
4
|
+
import { Command, Flags } from "@oclif/core";
|
|
5
|
+
import { signJwtVc } from "@originator-profile/securing-mechanism";
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/sign.ts
|
|
9
|
+
function isValidVc(vc) {
|
|
10
|
+
return typeof vc === "object" && vc !== null && "credentialSubject" in vc && typeof vc.credentialSubject === "object" && vc.credentialSubject !== null;
|
|
11
|
+
}
|
|
12
|
+
async function addDigestSri(vc) {
|
|
13
|
+
if (!isValidVc(vc)) throw new Error("Invalid VC");
|
|
14
|
+
const credentialSubject = vc.credentialSubject;
|
|
15
|
+
await fetchAndSetDigestSri("sha256", credentialSubject.logo);
|
|
16
|
+
await fetchAndSetDigestSri("sha256", credentialSubject.image);
|
|
17
|
+
return Object.assign(vc, { credentialSubject });
|
|
18
|
+
}
|
|
19
|
+
const exampleCoreProfile = {
|
|
20
|
+
"@context": ["https://www.w3.org/ns/credentials/v2", "https://originator-profile.org/ns/credentials/v1"],
|
|
21
|
+
type: ["VerifiableCredential", "CoreProfile"],
|
|
22
|
+
issuer: "dns:example.org",
|
|
23
|
+
credentialSubject: {
|
|
24
|
+
id: "dns:example.jp",
|
|
25
|
+
type: "Core",
|
|
26
|
+
jwks: { keys: [{
|
|
27
|
+
kid: "LIstkoCvByn4jk8oZPvigQkzTzO9UwnGnE-VMlkZvYQ",
|
|
28
|
+
kty: "EC",
|
|
29
|
+
crv: "P-256",
|
|
30
|
+
x: "QiVI-I-3gv-17KN0RFLHKh5Vj71vc75eSOkyMsxFxbE",
|
|
31
|
+
y: "bEzRDEy41bihcTnpSILImSVymTQl9BQZq36QpCpJQnI"
|
|
32
|
+
}] }
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const exampleWebMediaProfile = {
|
|
36
|
+
"@context": [
|
|
37
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
38
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
39
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
40
|
+
{ "@language": "ja" }
|
|
41
|
+
],
|
|
42
|
+
type: ["VerifiableCredential", "WebMediaProfile"],
|
|
43
|
+
issuer: "dns:wmp-issuer.example.org",
|
|
44
|
+
credentialSubject: {
|
|
45
|
+
id: "dns:wmp-holder.example.jp",
|
|
46
|
+
type: "OnlineBusiness",
|
|
47
|
+
url: "https://www.wmp-holder.example.jp/",
|
|
48
|
+
name: "○○メディア (※開発用サンプル)",
|
|
49
|
+
logo: {
|
|
50
|
+
id: "https://www.wmp-holder.example.jp/image.png",
|
|
51
|
+
digestSRI: "sha256-Upwn7gYMuRmJlD1ZivHk876vXHzokXrwXj50VgfnMnY="
|
|
52
|
+
},
|
|
53
|
+
email: "contact@wmp-holder.example.jp",
|
|
54
|
+
telephone: "0000000000",
|
|
55
|
+
contactPoint: {
|
|
56
|
+
id: "https://wmp-holder.example.jp/contact",
|
|
57
|
+
name: "お問い合わせ"
|
|
58
|
+
},
|
|
59
|
+
informationTransmissionPolicy: {
|
|
60
|
+
id: "https://wmp-holder.example.jp/statement",
|
|
61
|
+
name: "情報発信ポリシー"
|
|
62
|
+
},
|
|
63
|
+
privacyPolicy: {
|
|
64
|
+
id: "https://wmp-holder.example.jp/privacy",
|
|
65
|
+
name: "プライバシーポリシー"
|
|
66
|
+
},
|
|
67
|
+
description: "この文章はこの Web メディアに関する補足情報です。"
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const exampleWebsiteProfile = {
|
|
71
|
+
"@context": [
|
|
72
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
73
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
74
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
75
|
+
{ "@language": "ja" }
|
|
76
|
+
],
|
|
77
|
+
type: ["VerifiableCredential", "WebsiteProfile"],
|
|
78
|
+
issuer: "dns:example.com",
|
|
79
|
+
credentialSubject: {
|
|
80
|
+
id: "https://media.example.com/",
|
|
81
|
+
type: "WebSite",
|
|
82
|
+
name: "<Webサイトのタイトル>",
|
|
83
|
+
description: "<Webサイトの説明>",
|
|
84
|
+
image: {
|
|
85
|
+
id: "https://media.example.com/image.png",
|
|
86
|
+
digestSRI: "sha256-Upwn7gYMuRmJlD1ZivHk876vXHzokXrwXj50VgfnMnY="
|
|
87
|
+
},
|
|
88
|
+
allowedOrigin: ["https://media.example.com"]
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var VcSign = class VcSign extends Command {
|
|
92
|
+
static summary = "VC の作成";
|
|
93
|
+
static description = `\
|
|
94
|
+
VC に署名します。
|
|
95
|
+
標準出力に VC を出力します。`;
|
|
96
|
+
static flags = {
|
|
97
|
+
identity: privateKey({ required: true }),
|
|
98
|
+
id: opId(),
|
|
99
|
+
input: Flags.string({
|
|
100
|
+
summary: "入力ファイルのパス (JSON 形式)",
|
|
101
|
+
helpValue: "<filepath>",
|
|
102
|
+
description: `\
|
|
103
|
+
コアプロファイル (CP) の例:
|
|
104
|
+
|
|
105
|
+
${JSON.stringify(exampleCoreProfile, null, " ")}
|
|
106
|
+
|
|
107
|
+
ウェブメディアプロファイル (WMP) の例:
|
|
108
|
+
|
|
109
|
+
${JSON.stringify(exampleWebMediaProfile, null, " ")}
|
|
110
|
+
|
|
111
|
+
Website Profile (WSP) の例:
|
|
112
|
+
|
|
113
|
+
${JSON.stringify(exampleWebsiteProfile, null, " ")}`,
|
|
114
|
+
required: true
|
|
115
|
+
}),
|
|
116
|
+
"issued-at": Flags.string({ description: "発行日時 (ISO 8601)" }),
|
|
117
|
+
"expired-at": expirationDate()
|
|
118
|
+
};
|
|
119
|
+
static examples = [
|
|
120
|
+
`\
|
|
121
|
+
$ <%= config.bin %> <%= command.id %> \\
|
|
122
|
+
-i example.priv.json \\
|
|
123
|
+
--id example.com \\
|
|
124
|
+
--input core-profile.json`,
|
|
125
|
+
`\
|
|
126
|
+
$ <%= config.bin %> <%= command.id %> \\
|
|
127
|
+
-i example.priv.json \\
|
|
128
|
+
--id example.org \\
|
|
129
|
+
--input web-media-profile.json`,
|
|
130
|
+
`\
|
|
131
|
+
$ <%= config.bin %> <%= command.id %> \\
|
|
132
|
+
-i account-key.example.priv.json \\
|
|
133
|
+
--input website-profile.example.json`
|
|
134
|
+
];
|
|
135
|
+
async run() {
|
|
136
|
+
const { flags } = await this.parse(VcSign);
|
|
137
|
+
const inputBuffer = await fs.readFile(flags.input);
|
|
138
|
+
const input = JSON.parse(inputBuffer.toString());
|
|
139
|
+
const issuedAt = new Date(flags["issued-at"] ?? Date.now());
|
|
140
|
+
const expiredAt = flags["expired-at"] ?? addYears(/* @__PURE__ */ new Date(), 1);
|
|
141
|
+
if (flags.id) input.credentialSubject.id = flags.id;
|
|
142
|
+
const vc = await signJwtVc(await addDigestSri(input), flags.identity, {
|
|
143
|
+
issuedAt,
|
|
144
|
+
expiredAt
|
|
145
|
+
});
|
|
146
|
+
this.log(vc);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
//#endregion
|
|
151
|
+
export { VcSign };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import * as _oclif_core_interfaces25 from "@oclif/core/interfaces";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/wsp/unsigned.d.ts
|
|
5
|
+
declare class WspUnsigned extends Command {
|
|
6
|
+
static summary: string;
|
|
7
|
+
static description: string;
|
|
8
|
+
static flags: {
|
|
9
|
+
input: _oclif_core_interfaces25.OptionFlag<string, _oclif_core_interfaces25.CustomOptions>;
|
|
10
|
+
"issued-at": _oclif_core_interfaces25.OptionFlag<string | undefined, _oclif_core_interfaces25.CustomOptions>;
|
|
11
|
+
"expired-at": _oclif_core_interfaces25.OptionFlag<Date | undefined, _oclif_core_interfaces25.CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
static examples: string[];
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { WspUnsigned };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { t as unsignedWsp } from "../../website-profile-D43BL7p3.js";
|
|
2
|
+
import { t as expirationDate } from "../../flags-BuWTYwYw.js";
|
|
3
|
+
import { Command, Flags } from "@oclif/core";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
|
|
6
|
+
//#region src/commands/wsp/unsigned.ts
|
|
7
|
+
const exampleWebsiteProfile = {
|
|
8
|
+
"@context": [
|
|
9
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
10
|
+
"https://originator-profile.org/ns/credentials/v1",
|
|
11
|
+
"https://originator-profile.org/ns/cip/v1",
|
|
12
|
+
{ "@language": "<言語・地域コード>" }
|
|
13
|
+
],
|
|
14
|
+
type: ["VerifiableCredential", "WebsiteProfile"],
|
|
15
|
+
issuer: "<OP ID>",
|
|
16
|
+
credentialSubject: {
|
|
17
|
+
id: "<Web サイトのオリジン (形式: https://<ホスト名>)>",
|
|
18
|
+
type: "WebSite",
|
|
19
|
+
name: "<Web サイトの名称>",
|
|
20
|
+
description: "<Web サイトの説明>",
|
|
21
|
+
image: {
|
|
22
|
+
id: "<サムネイル画像URL>",
|
|
23
|
+
content: ["<コンテンツ (data:// 形式URL)>"]
|
|
24
|
+
},
|
|
25
|
+
allowedOrigin: ["<Web サイトのオリジン (形式: https://<ホスト名>)>"]
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var WspUnsigned = class WspUnsigned extends Command {
|
|
29
|
+
static summary = "未署名 Website Profile の取得";
|
|
30
|
+
static description = "標準出力に未署名 Website Profile を出力します。";
|
|
31
|
+
static flags = {
|
|
32
|
+
input: Flags.string({
|
|
33
|
+
summary: "入力ファイルのパス (JSON 形式)",
|
|
34
|
+
helpValue: "<filepath>",
|
|
35
|
+
description: `\
|
|
36
|
+
Website Profile の例:
|
|
37
|
+
|
|
38
|
+
${JSON.stringify(exampleWebsiteProfile, null, " ")}`,
|
|
39
|
+
required: true
|
|
40
|
+
}),
|
|
41
|
+
"issued-at": Flags.string({ description: "発行日時 (ISO 8601)" }),
|
|
42
|
+
"expired-at": expirationDate()
|
|
43
|
+
};
|
|
44
|
+
static examples = [`\
|
|
45
|
+
$ <%= config.bin %> <%= command.id %> \\
|
|
46
|
+
--input website-profile.example.json`];
|
|
47
|
+
async run() {
|
|
48
|
+
const { flags } = await this.parse(WspUnsigned);
|
|
49
|
+
const inputBuffer = await fs.readFile(flags.input);
|
|
50
|
+
const uwsp = await unsignedWsp(JSON.parse(inputBuffer.toString()), {
|
|
51
|
+
issuedAt: flags["issued-at"],
|
|
52
|
+
expiredAt: flags["expired-at"]
|
|
53
|
+
});
|
|
54
|
+
this.logJson(uwsp);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
export { WspUnsigned };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
+
import { JSDOM } from "jsdom";
|
|
3
|
+
import { parseExpirationDate } from "@originator-profile/core";
|
|
4
|
+
import { fetchAndSetDigestSri, fetchAndSetTargetIntegrity, signCa } from "@originator-profile/sign";
|
|
5
|
+
import { addYears, getUnixTime } from "date-fns";
|
|
6
|
+
import { BadRequestError } from "http-errors-enhanced";
|
|
7
|
+
|
|
8
|
+
//#region src/document-provider.ts
|
|
9
|
+
async function documentProvider({ type, content = "" }) {
|
|
10
|
+
if (Array.isArray(content) && content.length > 1) throw new Error("Multiple contents are not supported in this context.");
|
|
11
|
+
[content] = [content].flat();
|
|
12
|
+
let url;
|
|
13
|
+
let html = "";
|
|
14
|
+
if (type === "ExternalResourceTargetIntegrity") {
|
|
15
|
+
url = URL.canParse(content) ? content : void 0;
|
|
16
|
+
html = "";
|
|
17
|
+
} else if (URL.canParse(content)) {
|
|
18
|
+
url = content;
|
|
19
|
+
html = await fetch(url).then((res) => res.text());
|
|
20
|
+
} else {
|
|
21
|
+
url = void 0;
|
|
22
|
+
html = content;
|
|
23
|
+
}
|
|
24
|
+
return new JSDOM(html, { url }).window.document;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/content-attestation.ts
|
|
29
|
+
var content_attestation_exports = /* @__PURE__ */ __export({
|
|
30
|
+
sign: () => sign,
|
|
31
|
+
unsignedCa: () => unsignedCa
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Content Attestation への署名
|
|
35
|
+
* @param uca 未署名 Content Attestation オブジェクト
|
|
36
|
+
* @param privateKey プライベート鍵
|
|
37
|
+
* @return Content Attestation
|
|
38
|
+
*/
|
|
39
|
+
async function sign(uca, privateKey, { issuedAt: issuedAtDateOrString = /* @__PURE__ */ new Date(), expiredAt: expiredAtDateOrString = addYears(/* @__PURE__ */ new Date(), 1) }) {
|
|
40
|
+
const issuedAt = new Date(issuedAtDateOrString);
|
|
41
|
+
const expiredAt = typeof expiredAtDateOrString === "string" ? parseExpirationDate(expiredAtDateOrString) : expiredAtDateOrString;
|
|
42
|
+
uca.credentialSubject.id ??= `urn:uuid:${crypto.randomUUID()}`;
|
|
43
|
+
return await signCa(uca, privateKey, {
|
|
44
|
+
issuedAt,
|
|
45
|
+
expiredAt,
|
|
46
|
+
documentProvider
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 未署名 Content Attestation の取得
|
|
51
|
+
* @param uca 未署名 Content Attestation オブジェクト
|
|
52
|
+
* @throws {BadRequestError} 検証対象のコンテンツが存在しない/コンテンツにアクセスできない/Integrityの計算に失敗
|
|
53
|
+
* @return 未署名 Content Attestation オブジェクト
|
|
54
|
+
*/
|
|
55
|
+
async function unsignedCa(uca, { issuedAt: issuedAtDateOrString = /* @__PURE__ */ new Date(), expiredAt: expiredAtDateOrString = addYears(/* @__PURE__ */ new Date(), 1) }) {
|
|
56
|
+
const issuedAt = new Date(issuedAtDateOrString);
|
|
57
|
+
const expiredAt = typeof expiredAtDateOrString === "string" ? parseExpirationDate(expiredAtDateOrString) : expiredAtDateOrString;
|
|
58
|
+
uca.credentialSubject.id ??= `urn:uuid:${crypto.randomUUID()}`;
|
|
59
|
+
try {
|
|
60
|
+
await fetchAndSetDigestSri("sha256", uca.credentialSubject.image);
|
|
61
|
+
await fetchAndSetTargetIntegrity("sha256", uca, documentProvider);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
throw new BadRequestError(e.message);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
iss: uca.issuer,
|
|
67
|
+
sub: uca.credentialSubject.id,
|
|
68
|
+
iat: getUnixTime(issuedAt),
|
|
69
|
+
exp: getUnixTime(expiredAt),
|
|
70
|
+
...uca
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
export { documentProvider as i, sign as n, unsignedCa as r, content_attestation_exports as t };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parseExpirationDate } from "@originator-profile/core";
|
|
2
|
+
import { Flags } from "@oclif/core";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { exportJWK, importPKCS8 } from "jose";
|
|
5
|
+
|
|
6
|
+
//#region src/flags.ts
|
|
7
|
+
const opId = Flags.custom({
|
|
8
|
+
summary: "OP ID (ドメイン名)",
|
|
9
|
+
description: `\
|
|
10
|
+
ドメイン名 (RFC 4501) を指定します。`,
|
|
11
|
+
async parse(domainName) {
|
|
12
|
+
const id = domainName.toLowerCase();
|
|
13
|
+
return domainName.startsWith("dns:") ? id : `dns:${id}`;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const privateKey = Flags.custom({
|
|
17
|
+
char: "i",
|
|
18
|
+
summary: "プライベート鍵のファイルパス",
|
|
19
|
+
description: "プライベート鍵のファイルパスを渡してください。プライベート鍵は JWK 形式か、PEM base64 でエンコードされた PKCS #8 形式にしてください。",
|
|
20
|
+
async parse(filepath) {
|
|
21
|
+
const fileContent = (await fs.readFile(filepath)).toString();
|
|
22
|
+
try {
|
|
23
|
+
return await exportJWK(await importPKCS8(fileContent, "ES256", { extractable: true }));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return JSON.parse(fileContent);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const expirationDate = Flags.custom({
|
|
30
|
+
summary: "有効期限 (ISO 8601)",
|
|
31
|
+
description: "日付のみの場合、その日の 24:00:00.000 より前まで有効、それ以外の場合、期限切れとなる日付・時刻・秒を指定します。",
|
|
32
|
+
async parse(input) {
|
|
33
|
+
return parseExpirationDate(input);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { opId as n, privateKey as r, expirationDate as t };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
+
import { Jwk, RawTarget, UnsignedContentAttestation, WebsiteProfile } from "@originator-profile/model";
|
|
3
|
+
|
|
4
|
+
//#region src/document-provider.d.ts
|
|
5
|
+
declare function documentProvider({
|
|
6
|
+
type,
|
|
7
|
+
content
|
|
8
|
+
}: RawTarget): Promise<Document>;
|
|
9
|
+
/**
|
|
10
|
+
* Content Attestation への署名
|
|
11
|
+
* @param uca 未署名 Content Attestation オブジェクト
|
|
12
|
+
* @param privateKey プライベート鍵
|
|
13
|
+
* @return Content Attestation
|
|
14
|
+
*/
|
|
15
|
+
declare function sign(uca: UnsignedContentAttestation, privateKey: Jwk, {
|
|
16
|
+
issuedAt: issuedAtDateOrString,
|
|
17
|
+
expiredAt: expiredAtDateOrString
|
|
18
|
+
}: {
|
|
19
|
+
issuedAt?: Date | string;
|
|
20
|
+
expiredAt?: Date | string;
|
|
21
|
+
}): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* 未署名 Content Attestation の取得
|
|
24
|
+
* @param uca 未署名 Content Attestation オブジェクト
|
|
25
|
+
* @throws {BadRequestError} 検証対象のコンテンツが存在しない/コンテンツにアクセスできない/Integrityの計算に失敗
|
|
26
|
+
* @return 未署名 Content Attestation オブジェクト
|
|
27
|
+
*/
|
|
28
|
+
declare function unsignedCa(uca: UnsignedContentAttestation, {
|
|
29
|
+
issuedAt: issuedAtDateOrString,
|
|
30
|
+
expiredAt: expiredAtDateOrString
|
|
31
|
+
}: {
|
|
32
|
+
issuedAt?: Date | string;
|
|
33
|
+
expiredAt?: Date | string;
|
|
34
|
+
}): Promise<UnsignedContentAttestation>;
|
|
35
|
+
/**
|
|
36
|
+
* 未署名 Website Profile の取得
|
|
37
|
+
* @param uwsp 未署名 Website Profile オブジェクト
|
|
38
|
+
* @return 未署名 Website Profile オブジェクト
|
|
39
|
+
*/
|
|
40
|
+
declare function unsignedWsp(uwsp: WebsiteProfile, {
|
|
41
|
+
issuedAt: issuedAtDateOrString,
|
|
42
|
+
expiredAt: expiredAtDateOrString
|
|
43
|
+
}: {
|
|
44
|
+
issuedAt?: Date | string;
|
|
45
|
+
expiredAt?: Date | string;
|
|
46
|
+
}): Promise<WebsiteProfile>;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { content_attestation_d_exports as ContentAttestation, website_profile_d_exports as WebsiteProfile, documentProvider };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { i as documentProvider, t as content_attestation_exports } from "./content-attestation-2NuarC85.js";
|
|
2
|
+
import { n as website_profile_exports } from "./website-profile-D43BL7p3.js";
|
|
3
|
+
|
|
4
|
+
export { content_attestation_exports as ContentAttestation, website_profile_exports as WebsiteProfile, documentProvider };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
+
import { parseExpirationDate } from "@originator-profile/core";
|
|
3
|
+
import { fetchAndSetDigestSri } from "@originator-profile/sign";
|
|
4
|
+
import { addYears, getUnixTime } from "date-fns";
|
|
5
|
+
|
|
6
|
+
//#region src/website-profile.ts
|
|
7
|
+
var website_profile_exports = /* @__PURE__ */ __export({ unsignedWsp: () => unsignedWsp });
|
|
8
|
+
/**
|
|
9
|
+
* 未署名 Website Profile の取得
|
|
10
|
+
* @param uwsp 未署名 Website Profile オブジェクト
|
|
11
|
+
* @return 未署名 Website Profile オブジェクト
|
|
12
|
+
*/
|
|
13
|
+
async function unsignedWsp(uwsp, { issuedAt: issuedAtDateOrString = /* @__PURE__ */ new Date(), expiredAt: expiredAtDateOrString = addYears(/* @__PURE__ */ new Date(), 1) }) {
|
|
14
|
+
const issuedAt = new Date(issuedAtDateOrString);
|
|
15
|
+
const expiredAt = typeof expiredAtDateOrString === "string" ? parseExpirationDate(expiredAtDateOrString) : expiredAtDateOrString;
|
|
16
|
+
await fetchAndSetDigestSri("sha256", uwsp.credentialSubject.image);
|
|
17
|
+
return {
|
|
18
|
+
iss: uwsp.issuer,
|
|
19
|
+
sub: uwsp.credentialSubject.id,
|
|
20
|
+
iat: getUnixTime(issuedAt),
|
|
21
|
+
exp: getUnixTime(expiredAt),
|
|
22
|
+
...uwsp
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { website_profile_exports as n, unsignedWsp as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@originator-profile/opvc",
|
|
3
|
+
"version": "0.4.0-beta.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"bin",
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"bin": "bin/run.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"oclif": {
|
|
16
|
+
"bin": "opvc",
|
|
17
|
+
"commands": "dist/commands",
|
|
18
|
+
"plugins": [
|
|
19
|
+
"@oclif/plugin-help"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@oclif/core": "^4.4.0",
|
|
24
|
+
"@oclif/plugin-help": "^6.2.29",
|
|
25
|
+
"date-fns": "^4.1.0",
|
|
26
|
+
"http-errors-enhanced": "^3.0.2",
|
|
27
|
+
"jose": "^6.0.11",
|
|
28
|
+
"jsdom": "^27.0.0",
|
|
29
|
+
"@originator-profile/core": "0.4.0-beta.1",
|
|
30
|
+
"@originator-profile/cryptography": "0.4.0-beta.1",
|
|
31
|
+
"@originator-profile/sign": "0.4.0-beta.1",
|
|
32
|
+
"@originator-profile/securing-mechanism": "0.4.0-beta.1",
|
|
33
|
+
"@originator-profile/model": "0.4.0-beta.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^24.3.1",
|
|
37
|
+
"eslint": "^9.25.1",
|
|
38
|
+
"oclif": "^4.20.1",
|
|
39
|
+
"tsdown": "^0.15.0",
|
|
40
|
+
"typescript": "^5.8.3",
|
|
41
|
+
"@originator-profile/tsconfig": "0.4.0-beta.1",
|
|
42
|
+
"eslint-config-originator-profile": "0.4.0-beta.1"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsdown && oclif manifest && oclif readme",
|
|
46
|
+
"lint": "eslint --fix .",
|
|
47
|
+
"type-check": "tsc",
|
|
48
|
+
"test": "node --experimental-strip-types --no-warnings=ExperimentalWarning --test"
|
|
49
|
+
}
|
|
50
|
+
}
|