0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

node.jsからMS Authenticatorをいろいろ制御してみる

Last updated at Posted at 2023-12-02

はじめに

 VC(verifiable credential)を試してみようと考えて、スマホの中に入っているアプリの中で一番手軽そうにみえたMicrosoft Authenticatorに手を出したというのが、いじるきっかけ。またMS Authenticatorを取り扱っている記事が多くあるのも魅力の1つ。例えば以下のサイトでは、VCを発行するやりとりが詳しく解説されている。

 ただ多くの記事にあるように、AzureのApp serviceでmy webサーバを立ち上げたり、.netを使ってVCを発行するアプリを作るのは、結構敷居が高い。

 簡単に試す事ができるように、nodejsだけでms autheticatorにvcを発行できるようコードを書き起こした。ちょうど下の図にあるAPI群をもったコードである。
1.png

いじってみた結果

  • パターンA: 何もいじらない。
    • 追加ボタンを押すとVCがスマホに入る
  • パターンB: ④vc issue requestにpinというフィールドを入れる。
    • 確認コードボタンが表示され、入力したあとサイトAでのPIN検証後に、VCがスマホに入る
  • パターンC: ⑥manifestをいじって、"idTokens"ではなく"accessTokens"にする。
    • アカウントにサインインボタンが表示され、サインイン後Azure directoryの認証結果を取得し、その結果をサイトAで検証した後にVCが追加される
      2.png

1. いじった箇所(パターンB)

 vc issuer requestにpinフィールドを設けることで、MS authenticatorにpin入力を促すことができる。仕様はhttps://learn.microsoft.com/en-us/entra/verified-id/issuance-request-apiに公開されているが、あまり詳しくない。実際には仕様にないhashなるものが送られていたりする。(※ただ調査してみると、hashはPIN認証の際に使われていないので、実質仕様どおりかもしれない。)

  {
    "jti":uuid.v4().toUpperCase(),
    ...
    "claims":{},
    "id_token_hint":hint_jwt
    "pin":{"length":6,"type":"numeric","alg":"sha256","iterations":1,"salt":xxx, "hash":zzz}
    ...
  };

ここでrequestすると、MS authentocatorはユーザが入力したpinの値をもとに base64( sha256(salt+pin) )を計算し、⑦の処理の際に、その値をissue/..に返してくる。

2. いじった箇所(パターンC)

  1. manifestをいじる前に、A guided tour of Microsoft Entra Verified IDにあるように、Azure上にtenantを作って、そこにユーザを作り、そのユーザとMS authenticatorを結びつけておく必要がある。
  • create your Azure AD tenant(p23)
  • create a test user(p25)
  • Set up the user for Microsft Authenticator(p26)

結びつけられたら、そのtenant idをconst directory="https://login.microsoftonline.com/<tenant id>/v2.0"に設定する。
2. あとは送信するmanifestをidTokensからaccessTokensに変更する。

  • 変更前のmanifest
{
    ...
    "display": {
      "locale": "en-US",
      ...
      },
      "consent": {},
      "claims": {},
      },
      "id": "display"
    },
    "input": {
      "issuer": DID,
      "id": "input",
      attestations:{
        "idTokens": [{
          "id": "https://self-issued.me",
          "encrypted": false,
          "claims": [{..},{..}],
          "configuration": "https://self-issued.me",
          ...
        }]
     }
    }
} 
  • MS authenticatorにサインインを促すために、menifestに変更を加える部分
 attestations = {
  "accessTokens": [{
    "id": directory,
     "encrypted": false,
     "claims": [],
     "required": true,
      "configuration": directory,
      "resourceId": "bb2a64ee-5d29-4b07-a491-25806dc854d3",
      "oboScope": "User.Read.All" 
  }]
 };
  • この変更を加えて、manifestを送ると、⑦でM authenticatorから入力があるときに、Azure DirectoryからのTokenをもらうことができる(※この値で、発行しようとしているVC内容のダブルチェックも可能)
{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "T1St-dLTvyWRgxB_676u8krXS-I",
  "kid": "T1St-dLTvyWRgxB_676u8krXS-I"
}.{
  "aud": "bb2a64ee-5d29-4b07-a491-25806dc854d3",
  "iss": "https://sts.windows.net/<tenant id>/",
  "iat": 1701503608,
  "nbf": 1701503608,
  "exp": 1701508690,
  "acr": "1",
  "aio": "ATQAy/8VAAAAcJbmKE6C0R6TG2Fk7KpIMk+pah+RFU0Vd3QZWthaXOtyC+n6aAVkEuDb7gdbzvIY",
  "amr": [
    "pwd"
  ],
  "appid": "4813382a-8fa7-425e-ab75-3b753aab3abb",
  "appidacr": "0",
  "family_name": "afo afo man",
  "given_name": "hoge hoge",
  "ipaddr": "180.53.77.202",
  "name": "Test User 01",
  "oid": "7194b49b-8a45-4dac-80f7-4dc67dae8892",
  "puid": "100320031DE1E391",
  "rh": "0.AWoAJ1cUGcbFuEWSo2otvxZB--5kKrspXQdLpJElgG3IVNNqAEk.",
  "scp": "user_impersonation",
  "sub": "87sg7Rre57ZujnBSn4i5OpRV4tN6fzss-Q6vn90JMB8",
  "tid": "<tenant id>",
  "unique_name": "testuser1@xxx.onmicrosoft.com",
  "upn": "testuser1@xxx.onmicrosoft.com",
  "uti": "-gO-2Ta2f0CswFcXtscCAA",
  "ver": "1.0"
}.signature

動作確認

1. index.html, server.js, package.jsを以下のフォルダ構成のように配置する

├── server.js
├── package.json
└── views/
   ├── index.html
index.htmlのコードを見る
index.html
<!DOCTYPE html>
<html lang='en'>

<head><meta charset='utf-8'></head>
<body>
  <div align=center>
    <div id="title">[ngrok registration]</div>
    <div id="parameter">given name:<input type="text" id="given" value="hogehoge"><br>
    family name:<input type="text" id="family" value="deadbeaf"><br>
    <input type="button" value="発行" onclick="issuerQR()"/><br>
    <p><div id=qrmsg></div>
    <p><div id=qr></div>
  </div>

</body>
<script type="text/javascript">

  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);
  }

  function issuerQR() {
    let setting={
      "clientName": "ngrok registration",
      "type": "ngrokVerifiedCredential",
      "claims": {"lastName": document.getElementById('family').value, "firstName": document.getElementById('given').value}
    }
    sendData("./.issuer_init", JSON.stringify(setting), 'application/json', 
      function(data){
        target = document.getElementById("qr");
        target.innerHTML = "<img src="+JSON.parse(data).qrcode+" />";
        let interval_id = setInterval(
          function(){
            console.log("request check status");
            istatusCheck(interval_id, JSON.parse(data).sess);
          }, 3000);
      }
    );
  }

  function istatusCheck(clearid, sess){
    getData("./.istatus/"+sess, 'application/json', 
      function(data){
        console.log("status:"+JSON.parse(data).status);
        if (JSON.parse(data).status == "request_retrieved"){
          let msg = "QR code sccand. waiting..";
          if (typeof JSON.parse(data).pin !=="undefined"){ msg +="PIN:"+JSON.parse(data).pin;}
          document.getElementById("qrmsg").innerHTML =msg;
        }else if(JSON.parse(data).status == "issuance_successful"){
          document.getElementById("qrmsg").innerHTML = "finish";
          document.getElementById("qr").innerHTML = "";
          clearInterval(clearid);
        }
      }
    );
  }

  </script>
</html>
server.jsのコードを見る
server.js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const http = require('http');
const QRCode = require('qrcode');
const crypto = require('crypto');
const elliptic = require('elliptic');
const didJWT = require('did-jwt');
const fs = require('fs');
const uuid = require('uuid');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/', express.static(__dirname + '/views/'));
app.use(bodyParser.text({type: '*/*'}));
app.use(bodyParser.raw({type: '*/*'}));

//*******setting********
const port=8888;
let hostname = '127.0.0.1'+":"+port;
const pinenable=false;
const method="self" // "attest"
if (process.argv.length > 2){ hostname = process.argv[2];}
// authentication by azure directory
const directory = "https://login.microsoftonline.com/<tenant id>/v2.0";
//**********************

//*******global variable*******
let DID="did:web:"+hostname;
let task= new Array();
let taskstatus= new Array();
let storageJson=[];
let didweb, didwebkey;
const main = async()=>{
  didwebkey = await init(hostname);
  didweb = didjson(DID, didwebkey, hostname)
}
main();
//******************************

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

app.get('/.*', async function(req,res){
  console.log("\n====GET====");
  console.log("cmd:"+JSON.stringify(req.params));
  console.log("====GET====");

  let ret="";
  let cmd = req.params[0].split('/');

  if (cmd[0] =="well-known" && cmd[1] == "did.json"){
    console.log("access to did.json")
    res.send(didweb);

  } else if (cmd[0] =="well-known" && cmd[1] == "did-configuration.json"){
    console.log("access to did-configuration.json")
    res.send(await makedidconfig());

  } else if (cmd[0] == "issuanceRequests"){
    console.log("start session:"+cmd[1]+", issuanceRequests")
    taskstatus[cmd[1]].vcstatus = "request_retrieved";
    res.send(await makevcrequest(task[cmd[1]]));

  } else if(cmd[0] == "contracts" && cmd[2] == "manifest"){
    console.log("manifest for :"+cmd[1]);
    res.send(await makemanifest(task[cmd[1]],method));

  } else if (cmd[0] =="istatus"){
    ret={"status":taskstatus[cmd[1]].vcstatus};
    if (taskstatus[cmd[1]].vcstatus =="request_retrieved"){
      if (pinenable && (method !="attest")){
         ret.pin=task[cmd[1]].pin;
      }
    }
    res.send(JSON.stringify(ret));

  }else{
    res.send(ret);
  }
});

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

  let ret="";
  let cmd = req.params[0].split('/');

  if (cmd[0] =="issuer_init"){
    let [redirecturi, sessNo] = await issuerInit(req.body);
    let inviteUrl = "openid-vc://?request_uri=" + redirecturi;
    QRCode.toDataURL(inviteUrl, function (err, url) {
      let sendData = {"qrcode":url, "sess":sessNo, "url":redirecturi};
      res.send(sendData);
    });

  }else if (cmd[0] == "issue"){
    console.log("post from ms authenticator:")
    let decoded = didJWT.decodeJWT(req.body);
    let token;
    if (method == "attest"){
      token = decoded.payload.attestations.accessTokens[directory]
    }else{
      token = decoded.payload.attestations.idTokens["https://self-issued.me"]
    }
    let sess = cmd[1];
    if ((pinenable && decoded.payload.pin == task[sess].extpin) || (method=="attest")||!pinenable){
      let ret = await createVc(decoded, task[sess]);
      if (task[sess].iss=="did"){
        await saveDid(task[sess].issdid, task[sess].isskey);
      }
      res.send(JSON.stringify(ret));
    }else{
      console.log("pin is wrong, return error!");
      let sendData = {
        "requestId":"","date":"","mscv":"",
        "error":{
        "code":"unauthorized",
        "message":"The requested resource requires authentication",
          "innererror":{
            "code":"Unauthorized",
            "message":"An unhandled error has occurred verifying a token."
          }
        }
      };
       res.send(JSON.stringify(sendData));
    }
  }else if (cmd[0] == "completeIssuance"){
    // server   <--- ms authenticator
    if (req.body.code == 'issuance_successful'){
      console.log("vc issue is success");
    }else{
      console.log("vc issue is failed");
    }
    console.log("state:"+req.body.state);
    taskstatus[req.body.state].vcstatus="issuance_successful";
    res.status(202).send('OK')
  }else{
    res.send(null);
  }
});


//////////////////////
//  support function
///////////////////////

async function init(_hostname){
  let _did = "did:web:"+_hostname;
  let _key = await loadDid(_did);
  if (_key ==null){
    console.log("\n this "+_did+"'s key not registered")
    let [_key, , _document] = await createDid("web", _hostname);
    await saveDid(_did, _key);
    return _key;
  }else{
    console.log("\n this "+_did+"'s key is registered")
    return _key;
  }
}

async function loadDid(_did){
  let key = null;
  try{
    storageJson = JSON.parse(fs.readFileSync('./storage.json'));
    storageJson.forEach((item) =>{
       if (item.did == _did) key = item.key
    })
    return key;
  }catch(err){
    return null;
  }
}

async function saveDid(_did, _key){
  storageJson.push({did:_did, key:_key})
  try{
    fs.writeFileSync("./storage.json", JSON.stringify(storageJson))
  }catch(err){
    console.error(err);
  }
}

async function createDid(_method, _hostname){
  const key = crypto.randomBytes(32).toString("hex");
  const ec = new elliptic.ec('secp256k1');
  const prv = ec.keyFromPrivate(key, 'hex');
  const pub = prv.getPublic();
  const ecjwk = {
    kty:"EC", 
    crv:"secp256k1", 
    "x":pub.x.toBuffer().toString('base64'),
    "y":pub.y.toBuffer().toString('base64')
  };
  const ecjwkpri = {
    kty:"EC", 
    crv:"secp256k1", 
    "x":pub.x.toBuffer().toString('base64'),
    "y":pub.y.toBuffer().toString('base64'),
    "d":prv.getPrivate().toBuffer().toString('base64')
  };
  console.log("--------generate key for did -------------------")
  console.log(` d : ${prv.getPrivate().toBuffer().toString('base64')}`);
  console.log(` x (b64): ${pub.x.toBuffer().toString('base64')}`);
  console.log(` y (b64): ${pub.y.toBuffer().toString('base64')}`);
  console.log("-----------------------------\n")
  let _key={"publicJwk":ecjwk,"privateJwk":ecjwkpri};
  let _document
  let _did
  if (_method == "web"){
    // web method
    _did = "did:web"+_hostname;
    _document =didjson(_did, _key, _hostname);
    console.log("-> "+JSON.stringify(_document));
  }
  return [_key, _did, _document];
}

function createSigner(_method, _key){
  let _signer = didJWT.ES256KSigner(didJWT.hexToBytes(Buffer.from(didwebkey.privateJwk.d, 'base64').toString('hex')))
  return _signer;
}


/////////////////////////
//   did:web
/////////////////////////
function didjson(_did, _key, _hostname){
  let _document = {
    "id":"",
    "@context":["https://www.w3.org/ns/did/v1",{"@base":""}],
    "service":[
      {"id":"#linkeddomains","type":"LinkedDomains","serviceEndpoint":{"origins":""}}
    ],
    "verificationMethod":[
      {"id":"#owner","controller":"",
        "type":"EcdsaSecp256k1VerificationKey2019",
        "publicKeyJwk":{"crv":"secp256k1","kty":"EC","x":"","y":""}
      }
    ],
    "authentication":["#owner"],"assertionMethod":["#owner"]
  };
  _document.id = _did;
  _document["@context"][1]["@base"]=_did;
  _document.service[0].serviceEndpoint.origins = ["https://"+_hostname+"/"];
  _document.verificationMethod[0].controller = _did;
  _document.verificationMethod[0].publicKeyJwk = _key.publicJwk;
  return _document;
}

async function makedidconfig(){
  let didconfig = {
    "@context":"https://identity.foundation/.well-known/contexts/did-configuration-v0.0.jsonld", 
    "linked_dids":[]
  };
  let configPayload = {
    "sub":didweb.id,
    "iss":didweb.id,
    "nbf":1674647331,
    "vc":{
      "@context":[
        "https://www.w3.org/2018/credentials/v1",
        "https://identity.foundation/.well-known/contexts/did-configuration-v0.0.jsonld"
      ],
      "issuer":didweb.id,
      "issuanceDate":"2023-01-25T11:48:51.998Z",
      "expirationDate":"2048-01-25T11:48:51.998Z",
      "type":["VerifiableCredential","DomainLinkageCredential"],
      "credentialSubject":{"id":didweb.id,"origin":didweb.service[0].serviceEndpoint.origins[0]}
    }
  };

  let signer = didJWT.ES256KSigner(didJWT.hexToBytes(Buffer.from(didwebkey.privateJwk.d, 'base64').toString('hex')))
  const configjwt = await didJWT.createJWT(
    configPayload,
      { issuer:DID,expiresIn:3600,signer },
      { alg: 'ES256K', kid: DID+"#owner" }
  )
  didconfig.linked_dids[0]=configjwt;
  return didconfig;
}

////////////////////////////
//  vc issuer
/////////////////////////////

async function issuerInit(_body){
  let sessNo = crypto.randomBytes(8).toString("hex");
  let serviceEndpoint = "https://"+hostname+"/";

  task[sessNo]= {
    "sess":sessNo,
    "userid":crypto.randomBytes(32).toString("base64"),
    "clientName":_body.clientName,
    "type":_body.type,
    "claims":_body.claims,
    "serviceEndpoint":serviceEndpoint,
    "isskey": didwebkey,
    "issdid": DID
  };

  if (pinenable){
    task[sessNo].pin = Math.random().toString(10).slice(-6);
    task[sessNo].pinsalt = crypto.randomBytes(32).toString("hex");
    task[sessNo].pinhash = "qXp47rTIcPN2rfAz/sCKDAVNgFzKG7bTzVnCjWg5suU=";
    let pinsha256 = crypto.createHash('sha256');
    pinsha256.update(task[sessNo].pinsalt+task[sessNo].pin);
    task[sessNo].extpin = pinsha256.digest('base64');
  }
  let redirecturi = task[sessNo].serviceEndpoint+".issuanceRequests/"+sessNo;
  taskstatus[sessNo]={"vcstatus":"wait"};
  return [redirecturi, sessNo];
}

async function makevcrequest(_param){
  let hintbody ={
    "id":uuid.v4(),
    "sub":_param.userid,
    "aud":_param.serviceEndpoint+".issue/"+_param.sess,
    "nonce":crypto.randomBytes(16).toString("base64"),
    "sub_jwk":{
      "crv":"secp256k1",
      "kid":_param.issdid+"#owner",
      "kty":"EC",
      "x":_param.isskey.publicJwk.x,
      "y":_param.isskey.publicJwk.y
    },
    "did":_param.issdid,
    "type":_param.type,
    "credentialSubject":{
      "lastName":_param.claims.lastName,
      "firstName":_param.claims.firstName
    },
    "iss":"https://self-issued.me",
    "jti":uuid.v4().toUpperCase()
  };
  const signer = createSigner(_param.iss, _param.isskey);
  const hint_jwt = await didJWT.createJWT(
    hintbody,
    { issuer:"https://self-issued.me", expiresIn:3600, signer },
    { alg: 'ES256K', kid: _param.issdid+"#owner" }
  );
  if (pinenable){
    hintbody.pin = {"length":6,"type":"numeric","alg":"sha256","iterations":1,"salt":_param.pinsalt, "hash":_param.pinhash};
  }
  let isbody ={
    "jti":uuid.v4().toUpperCase(),
    "response_type":"id_token",
    "response_mode":"post",
    "scope":"openid",
    "nonce":crypto.randomBytes(16).toString("base64"),
    "client_id":_param.issdid,
    "redirect_uri":_param.serviceEndpoint+".completeIssuance",
    "prompt":"create",
    "state":_param.sess,
    "registration":{
      "client_name":_param.clientName,
      "subject_syntax_types_supported":["did:ion"],
      "vp_formats":{
        "jwt_vp":{"alg":["ES256K"]},"jwt_vc":{"alg":["ES256K"]}
      }
    },
    "claims":{
      "vp_token":{
        "presentation_definition":{
          "id":"b1cb4efd-8e33-482f-8dad-4452cbc2a8d1",
          "input_descriptors":[{
             "id":"Sample",
             "schema":[{"uri":"Sample"}],
             "issuance":[{"manifest":_param.serviceEndpoint+".contracts/"+_param.sess+"/manifest"}]
          }]
        }
      }
    },
    "id_token_hint":hint_jwt
  };
  if (pinenable){
    isbody.pin = {"length":6,"type":"numeric","alg":"sha256","iterations":1,"salt":_param.pinsalt, "hash":_param.pinhash};
  }
  const jwt = await didJWT.createJWT(
    isbody,
    { issuer:_param.issdid+"#owner", expiresIn:3600, signer },
    { alg: 'ES256K', kid: _param.issdid+"#owner" }
  )
  return jwt;
}

async function createVc(_decoded, _param){
  let idToken
  if (typeof _decoded.payload.attestations.idTokens !=="undefined"){
    idToken = _decoded.payload.attestations.idTokens["https://self-issued.me"];
  }else{
    idToken = _decoded.payload.attestations.accessTokens[directory];
  }
  console.log("\n-> idToken:");
  const subject = didJWT.decodeJWT(idToken).payload
  let vcbody =  {
    "jti": _decoded.payload.jti,
    "vc": {
      "@context": ["https://www.w3.org/2018/credentials/v1"],
      "type": ["VerifiableCredential"],
      "credentialSubject": {},
      "exchangeService": {
        "id": _param.serviceEndpoint+".card/exchange",
        "type": "PortableIdentityCardServiceExchange2020"
      },
      "sub": _decoded.header.kid
    }
  };
  if (method=="attest"){
    vcbody.vc.credentialSubject.lastName = subject.family_name;
    vcbody.vc.credentialSubject.firstName = subject.given_name;
  }else{
    vcbody.vc.credentialSubject = subject.credentialSubject;
  }
  vcbody.vc.credentialStatus = await makeCredStatus(vcbody.vc, _param.issdid, new Date().toISOString());
  let signer = createSigner(_param.iss, _param.isskey);
  const jwt = await didJWT.createJWT(
    vcbody,
    { issuer:_param.issdid, expiresIn:3600, signer },
    { alg: 'ES256K', kid: _param.issdid+"#owner" }
  )
  const decoded = didJWT.decodeJWT(jwt)
  console.log('\n//// JWT Decoded:\n',decoded)
  let sendData = {"vc":jwt}
  return sendData;
}

async function makeCredStatus(_vc, _iss, _date){
  let credStatus = {
      "id": "https://xxxx.github.io/credential-status/3BOJ1LAFUS#26",
      "type": "StatusList2021Entry",
      "statusPurpose": "revocation",
      "statusListIndex": 26,
      "statusListCredential": "https://xxxx.github.io/credential-status/3BOJ1LAFUS"
    }
  return credStatus;
}

async function makemanifest(_param, _method){

  let manifestbody = {
    "id": _param.sess,
    "display": {
      "locale": "en-US",
      "contract": _param.serviceEndpoint+".contracts/"+_param.sess+"/manifest",
      "card": {
        "title": "ID card",
        "issuedBy": "ngrok regitration",
        "backgroundColor": "#ffffff",
        "textColor": "#000000",
        "logo": {
          "uri": "https://didcustomerplayground.blob.core.windows.net/public/VerifiedCredentialExpert_icon.png",
          "description": "Verified skill Card"
        },
        "description": "Use your verified credential to prove to anyone that you know all about verifiable credentials."
      },
      "consent": {
        "title": "Do you want to get your Verified Credential?",
        "instructions": "Sign in with your account to get your card."
      },
      "claims": {
        "vc.credentialSubject.firstName": {"type": "String","label": "first Name"},
        "vc.credentialSubject.lastName": {"type": "String","label": "last Name"},
      },
      "id": "display"
    },
    "input": {
      "credentialIssuer": _param.serviceEndpoint+".issue/"+_param.sess,
      "issuer": DID,
      "id": "input"
    }
  };

  if (_method == "attest"){
    manifestbody.input.attestations = {
      "accessTokens": [{
        "id": directory,
        "encrypted": false,
        "claims": [],
        "required": true,
        "configuration": directory,
        "resourceId": "bb2a64ee-5d29-4b07-a491-25806dc854d3",
        "oboScope": "User.Read.All" 
      }]
    };
  }else{
    manifestbody.input.attestations = {
      "idTokens": [{
        "id": "https://self-issued.me",
        "encrypted": false,
        "claims": [{
          "claim": "$.lastName",
          "required": true,
          "indexed": false
        },{
          "claim": "$.firstName",
          "required": true,
          "indexed": false
        }],
        "required": true,
        "configuration": "https://self-issued.me",
        "client_id": "",
        "redirect_uri": ""
      }]
    };
  }
  let signer = createSigner("web", didwebkey);
  const jwt = await didJWT.createJWT(
     manifestbody,
     { issuer:DID, expiresIn:3600, signer },
     { alg: 'ES256K', kid: DID+"#owner" }
  )
  const decoded = didJWT.decodeJWT(jwt)
  return JSON.stringify({"token":jwt})
}

package.jsonのコードを見る
package.json
{
  "dependencies": {
    "consolidate": "^0.16.0",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "crypto": "^1.0.1",
    "did-jwt": "^6.11.0",
    "did-resolver": "^4.0.1",
    "elliptic": "^6.5.4",
    "express": "^4.18.2",
    "jose": "^4.11.2",
    "ngrok": "^4.3.3",
    "qrcode": "^1.5.1",
    "web-did-resolver": "^2.0.21"
  }
}

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

> npm install

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

> ngrok http 8888
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://57dd-180-53-77-xxx.ngrok-free.app -> http://localhost:8888
Forwarding                    https://57dd-180-53-77-xxx.ngrok-free.app -> http://localhost:8888

4. 取得したドメイン名を引数に、サーバを起動する

> nodejs server.js 57dd-180-53-77-xxx.ngrok-free.app
 
 web page start: listening on port 8888...

 this did:web:57dd-180-53-77-xxx.ngrok-free.app's key not registered
--------generate key for did -------------------
 d : 0tr5cu9T9f6Xz0H6Mwv0gwGM3NxSGTAzcOMcHS/sRnk=
 x (b64): 0G5yn2kRKSsPWcWIPQCX9YFJY0k4tOlucU4XR932VA0=
 y (b64): LCk2FkTpjLpvhnfWGOb+sn0drRof2aJl56VFDp8pook=
-----------------------------

-> {"id":"did:web57dd-180-53-77-xxx.ngrok-free.app","@context":["https://www.w3.org/ns/did/v1",{"@base":"did:web57dd-180-53-77-202.ngrok-free.app"}],"service":[{"id":"#linkeddomains","type":"LinkedDomains","serviceEndpoint":{"origins":["https://57dd-180-53-77-202.ngrok-free.app/"]}}],"verificationMethod":[{"id":"#owner","controller":"did:web57dd-180-53-77-xxx.ngrok-free.app","type":"EcdsaSecp256k1VerificationKey2019","publicKeyJwk":{"kty":"EC","crv":"secp256k1","x":"0G5yn2kRKSsPWcWIPQCX9YFJY0k4tOlucU4XR932VA0=","y":"LCk2FkTpjLpvhnfWGOb+sn0drRof2aJl56VFDp8pook="}}],"authentication":["#owner"],"assertionMethod":["#owner"]}

5. ブラウザでhttps://57dd-180-53-77-xxx.ngrok-free.app/にアクセス

  1. 画面上にある「発行」ボタンを押してQRコードを表示
  2. 表示されたQRコードを、スマホでスキャンするとVC発行の手続きが始まる

6. いじる箇所

  1. pinを有効にする場合、server.jsのconst pinenable=false;const pinenable=true;
  2. ms loginを有効にする場合、server.jsのconst method="self";const method="attest";に。
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?