1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SD-JWTをスマホで体験してみる

Last updated at Posted at 2024-01-21

 最近SD-JWTの話をよく聞くようになってきた。選択的開示のためのデータフォーマット([参考])らしい。以前試したEUDIで、SD-JWTを用いたSelective Disclosureの実現を必須としていることから注目されているようである。

 「記事:Verifiable Credentialsの署名を壊さずに選択的開示を行うSD-JWTを体感してみる」によれば、Verifiable Credentailを発行するissuerが下記のようなcredentialを発行してくれるそうな。ユーザが選択開示できる対象が8個あれば、credential jwtのあとに8個分の~がつくようなフォーマットらしい。

eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3MzU2ODk2NjEsIl9zZCI6WyI1R19rclp0TVRNUGx0QVdXTk1oZER4Sk10MTVTQTFLRmtkMmdJbFN2THRRIiwiNXA2YnQ1M0Q5cDRNZmtWWVFjaUw0cFdYQWtJanowUGJMWEdEME5IaWkydyIsIjZKdDVseHJlaVRLOG9sVFJ6eUhvSFlZUG11MEctWlZ2V3BhY1ZtWVlUOE0iLCJFdy1kS1JtQTJ1U2d3OUVtR3dOSWZRa3NQcFBJczBxQTRPRS04NkRRM19ZIiwiSHotQllQUUptSHNGbElmWl85Q0o2SWJvcWlzb1pOdHd6a2IwWjNKQWxGNCIsIkozUF9nTkEzTEVDSXpUdHRCdVg2MmNMVklDREZ6Ui1ycm9pQ1FrYkJidVEiLCJPMmJEVUhhRUxaSXN5T1hUeXV0V3VuQWdtMzdRVGtiQ1RSdzczYmk1NXhFIiwiUTR2dHJhRHZwcnFCa2pyUG9MV185RlpEZXRGS0tBR2N5YTA1ckl3cTR4ZyJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.LAlXQnPK8rOhxBhbg_FU-hiBJJqCX5CYWYTp0YcbbIxnBZPN1ZhRgE_SLh0rEg1L559GxmG1WrpGMyOl1rDoAA
~WyJ5OWVMRmI3azdXZnd4ZkllQWp5eUt3Iiwic3ViIiwiMjQ4Mjg5NzYxMDAxIl0
~WyJUNkVlZzRsaVJyN3VtOUZxOFRYekR3IiwibmFtZSIsIkphbmUgRG9lIl0
~WyJUUnRocWdUc29kZzVJbnBBR2ZvNjJnIiwiZ2l2ZW5fbmFtZSIsIkpvbmUiXQ
~WyJmRWx5VzFIdlVjX1dYTnFLeUdRcEJ3IiwiZmFtaWx5X25hbWUiLCJEb2UiXQ
~WyI1blgwRmZTNHUtVjN3aFNORUpPQndnIiwiZ2VuZGVyIiwiZmVtYWxlIl0
~WyJhSzhJYWc2dzB1Mno1WVpxMWlXOC13IiwiYmlydGhkYXRlIiwiMDAwMC0xMC0zMSJd
~WyIwR0pLdnN2aDNpbnFkeGlUM3MxbVJRIiwiZW1haWwiLCJqb25lZG9lQGV4YW1wbGUuY29tIl0
~WyJXMHpCa1lfLThqUVhNY3JnYTJTak9BIiwicGljdHVyZSIsImh0dHA6Ly9leGFtcGxlLmNvbS9qYW5lZG9lL21lLmpwZyJd

 この~で始まる部分はbase64でエンコードされた文字列だという話なので、それを明に書き下すと、

eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwiYXVkIjoiczZCaGRSa3F0MyIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3MzU2ODk2NjEsIl9zZCI6WyI1R19rclp0TVRNUGx0QVdXTk1oZER4Sk10MTVTQTFLRmtkMmdJbFN2THRRIiwiNXA2YnQ1M0Q5cDRNZmtWWVFjaUw0cFdYQWtJanowUGJMWEdEME5IaWkydyIsIjZKdDVseHJlaVRLOG9sVFJ6eUhvSFlZUG11MEctWlZ2V3BhY1ZtWVlUOE0iLCJFdy1kS1JtQTJ1U2d3OUVtR3dOSWZRa3NQcFBJczBxQTRPRS04NkRRM19ZIiwiSHotQllQUUptSHNGbElmWl85Q0o2SWJvcWlzb1pOdHd6a2IwWjNKQWxGNCIsIkozUF9nTkEzTEVDSXpUdHRCdVg2MmNMVklDREZ6Ui1ycm9pQ1FrYkJidVEiLCJPMmJEVUhhRUxaSXN5T1hUeXV0V3VuQWdtMzdRVGtiQ1RSdzczYmk1NXhFIiwiUTR2dHJhRHZwcnFCa2pyUG9MV185RlpEZXRGS0tBR2N5YTA1ckl3cTR4ZyJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.LAlXQnPK8rOhxBhbg_FU-hiBJJqCX5CYWYTp0YcbbIxnBZPN1ZhRgE_SLh0rEg1L559GxmG1WrpGMyOl1rDoAA
~base64encode(["y9eLFb7k7WfwxfIeAjyyKw","sub","248289761001"]) ---------1
~base64encode(["T6Eeg4liRr7um9Fq8TXzDw","name","Jane Doe"]) ------------2
~base64encode(["TRthqgTsodg5InpAGfo62g","given_name","Jone"]) ----------3
~base64encode(["fElyW1HvUc_WXNqKyGQpBw","family_name","Doe"]) ----------4
~base64encode(["5nX0FfS4u-V3whSNEJOBwg","gender","female"]) ------------5
~base64encode(["aK8Iag6w0u2z5YZq1iW8-w","birthdate","0000-10-31"]) -----6
~base64encode(["0GJKvsvh3inqdxiT3s1mRQ","email","jonedoe@example.com"])-7
~base64encode(["W0zBkY_-8jQXMcrga2SjOA","picture","http://example.com/janedoe/me.jpg"]) -8

ということらしい。

 ユーザは隠したい情報、例えばgender、birthdateであれば、それに対応する5,6部分の~Wy..を消去して、相手に渡すことで選択開示が可能となる。本体のcredentialには~で始まるbase64でエンコードされた文字列のハッシュ値が埋め込まれているそうだ(墨塗り署名に似た仕掛けだなと感じた)

sd-jwt形式のvcを取得するまでの流れ

1. QRコードをスキャン

 sd-jwtを体験できるissuerを探してみた。animowaltと試したものの、まだsd-jwtへの対応は出来ておらず、authlete社のデモサイトが唯一sd-jwt形式のcredentialを発行してくれた(2024年1月時点)。
authlete.png

発行してもらうには、Login ID/Passwordの欄に、デモサイトの使い方ページに書かれた値を入れてsubmitボタンを押すだけでOKである。押すと下記のようなQRコードが表示される。

issue.png

2. Credential Issuer Metadata を取得

 QRコードを読み取ると、以下のようなCredential Offer情報が取得できる。

openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Ftrial.authlete.net%22%2C%22credential_configurations%22%3A%5B%22IdentityCredential%22%2C%22org.iso.18013.5.1.mDL%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22BrSjFNeIYQJkemFOAj4nk5RrDD2lo8lNgRnXLD-o540%22%7D%7D%7D

このCredential Offerをsphereon社のライブラリoid4vci-clientに読み込ませるとpreAuthorizedCodeや、token endpointを取得してくれる

#ライブラリの動作をみていると、.well-known/openid-credential-issuer、.well-known/oauth-authorization-server、.well-known/openid-configurationをチェックしてmetadataを生成している印象

 import { CredentialOfferClient, MetadataClient,} from '@sphereon/oid4vci-client';

const initiationRequestWithUrl 
   = await CredentialOfferClient.fromURI(_initiationURI);
const metadata 
   = await MetadataClient.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);

console.log(initiationRequestWithUrl.preAuthorizedCode)
console.log(metadata.token_endpoint)
console.log(metadata.issuer)

3.tokenの取得

issuer metadataからtoken endpoint情報をもらい、そこにQRコード経由で取得したpreAuthorizedCodeを用いてaccess_tokenを取得する。このデモサイトからaccess_tokenを取得する場合には、application/x-www-form-urlencoded形式でpreAuthorizedCodeを送る必要がある。

  const clientId = '218232426';
  const tokenReq = "client_id="+clientId
            + "&grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code&pre-authorized_code="
            + initiationRequestWithUrl.preAuthorizedCode;
  const accessTokenResponse = await fetch(metadata.token_endpoint, {
    method: 'post',
    body: tokenReq,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
  });
  const accessToken = await accessTokenResponse.json();

4. VCの取得

 VCを取得するには、metadata.credential_endpointに、さきほど取得したtokenをheaderにセットして依頼を行う。このとき

  • vctを設定する(いままでは、credentialTypesを設定)
  • formatをvc+sd-jwtに設定する(いままではjwt_vc_jsonを設定)。

function main(){
...
    const proofJwt = await generateKeyProof ("ES256",clientId, accessToken.c_nonce, metadata.issuer)

    const credentialReq = {
      "format": "vc+sd-jwt", // jwt_vc_json
      "vct": "https://credentials.example.com/identity_credential", // credentialTypes:..
      "proof": {
        "proof_type": "jwt",
        "jwt":proofJwt
       }
    };
    const credentialResponse = await fetch(metadata.credential_endpoint, {
      method: 'post',
      body: JSON.stringify(credentialReq),
      headers: {
       'Content-Type': 'application/json', 
        'Authorization': 'Bearer '+accessToken.access_token}
    });
    const credential = await credentialResponse.json();
    console.log(credential);
...
}
async function generateKeyProof (_alg, _clientid,  _nonce, _aud){
  const { privateKey, publicKey } = await jose.generateKeyPair(_alg);
  let ecjwk  = await jose.exportJWK(publicKey);
  let ecjwkpri  = await jose.exportJWK(privateKey);
  let _key={"publicJwk":ecjwk,"privateJwk":ecjwkpri};
  let args = {
     "payload":{
       "iat":Math.floor(new Date().getTime() / 1000),
       "iss":_clientid,
       "nonce": _nonce,
       "aud":_aud,
     },
     "header":{
        "typ":"openid4vci-proof+jwt",
        "alg":_alg,
        "jwk":_key.publicJwk
     }
  }
  const privateKeyObj  = await jose.importJWK(ecjwkpri, _alg);
  const jwt =  await new jose.SignJWT({ ...args.payload })
    .setProtectedHeader({...args.header})
    .sign(privateKeyObj);
  return jwt;
}

すると、以下のようなvc+sd-jwt形式のデータが返ってくる。

{
  format: 'vc+sd-jwt',
  credential: 'eyJraWQiOiJKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJfc2QiOlsiN3V5c0ZxWWdTWDVnWmFocmh0dE9ZMXlxeGg2QUFqQ242eE1mSWdOVnZBSSIsIjh6ekJWaVc5cl8wbFY0UlJYY01rYVVCRUNlZHduYTBPY0lNdEl5UVdqc0EiLCJCeEpfU0hSZ0lDWHFyOENGd0xFZlQ2b0VVR3A0YktrRlBVazF5dnBuekNNIiwiR2s4bjQxcHZ4WndtOWNvZmtTX01BLXdrZjY2bXZPVUVFV0FWUGFaenliayIsIlE1Z3Bjc1hvSHVZa3h3VHdhVXZ4YUR3VFpiZHE5OW5qbjRjNEJfZXFaU1EiLCJWSnVrU2NGQWFxUVhaUVhLR041MVJrcWVRN3FBUDVOc1JRMkpHeGpGMFBnIiwiVm5MWGNqXzRONVdXclVhNXp4LWRPWHhOMXVFSG1JUXEwU3UyQmNZd2R2TSIsImtUcmFMajdsbGN4eGxreVdrUHV2Tm1QZDc1ZU1DTlNOM0dPelpoNnh4YkUiLCJtaG9oSGc0Sm5qQzZfbi1uVXIwbzFkdVlPb2xISGFuc0RjTU9WYnIycTZvIiwib1E0b1IxZURmc1JQR0FSeHpxNkNsa0ZSbzc5dkQyRWJ6cVNYVUNUTDhUbyIsInFHel9pRlNKLVJKbG96NGdBUDZqOW5sekpCYW9Id0JsS0I2LVBSVlBXd1EiLCJ2eFVPalJnTmh4Vk1Hb292MHVFck4zbUtxdUxKN2pCR1RtdHhEbjFPU0Z3Il0sInZjdCI6Imh0dHBzOi8vY3JlZGVudGlhbHMuZXhhbXBsZS5jb20vaWRlbnRpdHlfY3JlZGVudGlhbCIsIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiaHR0cHM6Ly90cmlhbC5hdXRobGV0ZS5uZXQiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJRNFhHMEdnOVVDODVEcW9Zc3ZzX2RKTnFBVTJtMXliVEhQOW1GYVQzLW9RIiwieCI6IjFfVERIaHVvLXQ1WTZfaHBITFEzc3FmV0J3eGRlNDhpdHJDNllqaklVbWciLCJ5IjoiRkpxWmhsWVEzcVZmcl84NE13WWlRNmJBbDEtb1VzSkI5dHFMTXR2ZFhFMCJ9fSwiaWF0IjoxNzA1ODE5MjY4fQ.Hkgkh8ri8b-pQC0M-BJbPGcsFkC7iLT3plLcA-h-ubLEVa8gmdKfBlRYbqvoVV0DI8CWURrFFJ_Qbw8u-uMkXQ~WyIyVlo0M1dwSGVocllrLTN3QmZ5d3l3Iiwic3ViIiwiMTAwNCJd~WyJGa3VINk04amkwLXNodVRQWWRaRUVRIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ~WyJoRzUtdjlyU2FPVEczdC1mZ2lYVHJ3IiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd~WyJERGJhcXBpeUtmYXdSeTNONUpCUXp3IiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd~',
  c_nonce: 'gTTnGBUukwkVm1GfiLFBu4W5tEGRIfO6_ytzF9iZePg',
  c_nonce_expires_in: 86399
}

5. sd-jwtのパース

ライブラリ@eengineer1/sd-jwt-ts-nodeにあるparse関数に上記credentialを流し込めば、いい感じに分析してくれる

import { SDJwt } from '@eengineer1/sd-jwt-ts-node'

async function sdJwtParse(sdPayload){
  const sdJwt = SDJwt.parse(sdPayload)
  const parse = {undisclosure:sdJwt.sdPayload.undisclosedPayload, disclosure:sdJwt.fullPayload}
  console.log(JSON.stringify(parse, null,1))
  return parse
}

上記関数に、引数credential値を入れて実行すると、以下のように情報開示をする前の状態(undisclosure)と、後の状態(disclosure)を表示してくれる。

{
 "undisclosure": {
  "_sd": [
   "7uysFqYgSX5gZahrhttOY1yqxh6AAjCn6xMfIgNVvAI",
   "8zzBViW9r_0lV4RRXcMkaUBECedwna0OcIMtIyQWjsA",
   "BxJ_SHRgICXqr8CFwLEfT6oEUGp4bKkFPUk1yvpnzCM",
   "Gk8n41pvxZwm9cofkS_MA-wkf66mvOUEEWAVPaZzybk",
   "Q5gpcsXoHuYkxwTwaUvxaDwTZbdq99njn4c4B_eqZSQ",
   "VJukScFAaqQXZQXKGN51RkqeQ7qAP5NsRQ2JGxjF0Pg",
   "VnLXcj_4N5WWrUa5zx-dOXxN1uEHmIQq0Su2BcYwdvM",
   "kTraLj7llcxxlkyWkPuvNmPd75eMCNSN3GOzZh6xxbE",
   "mhohHg4JnjC6_n-nUr0o1duYOolHHansDcMOVbr2q6o",
   "oQ4oR1eDfsRPGARxzq6ClkFRo79vD2EbzqSXUCTL8To",
   "qGz_iFSJ-RJloz4gAP6j9nlzJBaoHwBlKB6-PRVPWwQ",
   "vxUOjRgNhxVMGoov0uErN3mKquLJ7jBGTmtxDn1OSFw"
  ],
  "vct": "https://credentials.example.com/identity_credential",
  "_sd_alg": "sha-256",
  "iss": "https://trial.authlete.net",
  "cnf": {
   "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "kid": "Q4XG0Gg9UC85DqoYsvs_dJNqAU2m1ybTHP9mFaT3-oQ",
    "x": "1_TDHhuo-t5Y6_hpHLQ3sqfWBwxde48itrC6YjjIUmg",
    "y": "FJqZhlYQ3qVfr_84MwYiQ6bAl1-oUsJB9tqLMtvdXE0"
   }
  },
  "iat": 1705819268
 },
"disclosure": {
 "birthdate": "1991-11-06",
 "family_name": "Silverstone",
 "sub": "1004",
 "given_name": "Inga",
 "vct": "https://credentials.example.com/identity_credential",
 "iss": "https://trial.authlete.net",
 "cnf": {
  "jwk": {
   "kty": "EC",
   "crv": "P-256",
   "kid": "Q4XG0Gg9UC85DqoYsvs_dJNqAU2m1ybTHP9mFaT3-oQ",
   "x": "1_TDHhuo-t5Y6_hpHLQ3sqfWBwxde48itrC6YjjIUmg",
   "y": "FJqZhlYQ3qVfr_84MwYiQ6bAl1-oUsJB9tqLMtvdXE0"
  }
 },
 "iat": 1705819268
}
}

体験してみる

 id walletサーバを立ち上げ、以下のようにAuthlete社のデモサイトにアクセスし、sd-jwt形式のcredentialをもらうまでを体験する

#本当はsd-jwt対応のid walletアプリを見つけて試したかったが、2024/1時点では見つからず..。za.png

1. id walletサーバを起ちあげ

 index.mjs, package.js, index.html, qr-scanner.min.js, qr-scanner-worker.min.jsを以下のフォルダ構成のように配置する.qr-scanner関係のjsは、https://github.com/nimiq/qr-scannerからdownloadする。

├── index.mjs
├── package.json
└── views/
   ├── index.html
   ├── qr-scanner.min.js
   ├── qr-scanner-worker.min.js

index.mjsのコードを見る

import { CredentialOfferClient, MetadataClient,} from '@sphereon/oid4vci-client';
import { SDJwt } from '@eengineer1/sd-jwt-ts-node'
import fetch from 'node-fetch';
import crypto from 'crypto';
import * as jose from 'jose';
import  express from 'express';
import  http from 'http';
import path from 'path';
import { fileURLToPath } from 'url';
import bodyParser from 'body-parser';


//************
let hostname = 'localhost';
const port=8800;
let keys={};
let aud={};
const ALG="ES256"; // ES256, ES256K, EdDSA
if (process.argv.length > 2){ hostname = process.argv[2];}
//*************
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
app.use('/', express.static(__dirname + '/views/'));
app.use(bodyParser.json({ extended: true, limit: '10mb' }));
app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' }));
app.use(express.json({ extended: true, limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

const httpServer = http.createServer(app);
const server = httpServer.listen(port, () => {
	console.log('\n web page start: listening on port %s...', server.address().port);
});

app.post('/.*', async function(req,res){
	console.log("\n====POST====");
	console.log("cmd:"+JSON.stringify(req.params));
	console.log("query:"+JSON.stringify(req.query));
	console.log("headers:"+JSON.stringify(req.headers));
	console.log(req.body);
	console.log("====POST====");

  let ret="";
  let cmd = req.params[0].split('/');
  if (cmd[0] =="issuer_init"){
      console.log("init")
      let cred =  await getCredential(req.body.qr)
      res.send(cred);
  }
})


async function getCredential(_initiationURI, _alg){
  const initiationRequestWithUrl = await CredentialOfferClient.fromURI(_initiationURI);
  const metadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);

  const clientId = '218232426';
  const tokenReq = "client_id="+clientId
            + "&grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code&pre-authorized_code="
            + initiationRequestWithUrl.preAuthorizedCode;
  const accessTokenResponse = await fetch(metadata.token_endpoint, {
    method: 'post',
    body: tokenReq,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
  });
  const accessToken = await accessTokenResponse.json();
  if (typeof accessToken.access_token !=="undefined"){
    console.log(accessToken);
    const proofJwt = await generateKeyProof (ALG,clientId, accessToken.c_nonce, metadata.issuer)

    const credentialReq = {
      "format": "vc+sd-jwt",
      "vct": "https://credentials.example.com/identity_credential",
      "proof": {
        "proof_type": "jwt",
        "jwt":proofJwt
       }
    };
    console.log(credentialReq)
    const credentialResponse = await fetch(metadata.credential_endpoint, {
      method: 'post',
      body: JSON.stringify(credentialReq),
      headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer '+accessToken.access_token}
    });
    const credential = await credentialResponse.json();
    console.log(credential);

// verify    // todo

// parse
    const decode = await sdJwtParse(credential.credential, -1);
    return {result:true, detail:credential.credential,  decode:decode};
  }else{
   return  {result:false, detail:"", decode:""};
  }
}

//////////////////////////////////////////////////

async function generateKeyProof (_alg, _clientid,  _nonce, _aud){
  const { privateKey, publicKey } = await jose.generateKeyPair(_alg);
  let ecjwk  = await jose.exportJWK(publicKey);
  let ecjwkpri  = await jose.exportJWK(privateKey);
  let _key={"publicJwk":ecjwk,"privateJwk":ecjwkpri};
  let args = {
     "payload":{
       "iat":Math.floor(new Date().getTime() / 1000),
       "iss":_clientid,
       "nonce": _nonce,
       "aud":_aud,
     },
     "header":{
        "typ":"openid4vci-proof+jwt",
        "alg":_alg,
        "jwk":_key.publicJwk
     }
  }
  const privateKeyObj  = await jose.importJWK(ecjwkpri, _alg);
  const jwt =  await new jose.SignJWT({ ...args.payload })
    .setProtectedHeader({...args.header})
    .sign(privateKeyObj);
  console.log("-> " + jwt)
  return jwt;
}

async function sdJwtParse(_sdPayload, _index){
  let sdPayload;
  const tmp = _sdPayload.split("~");
  sdPayload=tmp[0];
  for (let i=1; i< tmp.length; i++){
    if (i != _index){
        sdPayload += "~"+tmp[i];
    }
  }
  const sdJwt = SDJwt.parse(sdPayload)
  const parse = {undisclosure:sdJwt.sdPayload.undisclosedPayload, disclosure:sdJwt.fullPayload}
  console.log(JSON.stringify(parse, null,1))
  return parse
}

package.jsonのコードを見る
{
  "dependencies": {
    "@eengineer1/sd-jwt-ts-node": "^1.0.1",
    "@sphereon/oid4vci-client": "^0.8.2-next.34",
    "express": "^4.18.2",
    "jose": "^5.2.0",
    "uint8arrays": "^5.0.1"
  }
}
index.htmlのコードを見る
<!DOCTYPE html>
<html lang='en'>

<head><meta charset='utf-8'></head>
<body>
  <div align=center>
    <div id="title">[ID Wallet]</div><br>
    <button id="scanQR">scan QR code</button>, <button id="stop-button">Stop</button>
    <p><div id=msg></div>
    <p><div id=qr></div>
    <div id="video-container">
      <video id="qr-video"></video>
    </div>
  </div>
<b>Detected QR code: </b>
<span id="cam-qr-result">None</span>
<div id="credresult"></div>
<br>
<hr></body>
<script type="module">
    import QrScanner from "./qr-scanner.min.js";

    let credentials =[]
    const video = document.getElementById('qr-video');
    const videoContainer = document.getElementById('video-container');
    const camQrResult = document.getElementById('cam-qr-result');
    const credResult = document.getElementById('credresult');

    function setResult(label, result) {
        console.log(result.data);
        scanner.stop();
        label.textContent = result.data;
        label.style.color = 'teal';
        _init(result.data)
    }

    const scanner = new QrScanner(video, result => setResult(camQrResult, result), {
        onDecodeError: error => {
            camQrResult.textContent = error;
            camQrResult.style.color = 'inherit';
        },
        highlightScanRegion: true,
        highlightCodeOutline: true,
    });

    document.getElementById('scanQR').addEventListener('click', () => {
        scanner.start();
    });


    function _init(_qrcode) {
      let setting={
        qr:_qrcode
      }
      sendData("./.issuer_init", JSON.stringify(setting), 'application/json', 
        function(data){
          data = JSON.parse(data)
          if (data.result){
            credentials.push({type:data.type, detail:data.detail, decode:data.decode})
            credResult.innerHTML = "<pre>"+JSON.stringify(credentials[credentials.length-1].decode.undisclosure, null,"\t")+"</pre>---><pre>"+JSON.stringify(credentials[credentials.length-1].decode.disclosure, null,"\t")+"</pre>";
          }else{
            credResult.innerHTML = "<br>--> failed<br>";
          }
        }
      );
    }

    // for debugging
    window.scanner = scanner;

    document.getElementById('stop-button').addEventListener('click', () => {
        scanner.stop();
    });


</script>
<script type="text/javascript">
  window.addEventListener('load',function() {  
  });

  function getData(url, type, cb){
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
      switch(xhr.readyState){
        case 4:
          if (xhr.status == 200||xhr.status ==304){
            cb(xhr.response)
          }
      }
    };
    xhr.open("GET", url, false);
    xhr.setRequestHeader('Content-Type', type);
    xhr.send('');
  }

  function sendData(url, data, type, cb){
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
      switch(xhr.readyState){
        case 4:
          if (xhr.status == 200||xhr.status ==304){
            console.log("send success");
            cb(xhr.response)
          }
      }
    };
    xhr.open("POST", url, false);
    xhr.setRequestHeader('Content-Type', type);
    xhr.send(data);
  }
  </script>
</html>

2. 関連モジュールをインストールする

> npm install

3. id wallet サーバに割り当てるドメイン名を取得

> ngrok http 8800
Forwarding       https://fe86-153-214-37-81.ngrok-free.app -> http://localhost:8800 

4. id wallet サーバを起動する

> nodejs index.mjs

 web page start: listening on port 8800...

5. スマホ内のブラウザでhttps://fe86-153-214-37-81.ngrok-free.app/にアクセス

  1. スマホで表示されたwebページ内にある「scan QR code」ボタンを押す。
  2. PCなどで、authelete社のデモサイトにアクセスし、QRコードを表示
  3. スマホでそのQRコードをscanする
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?