-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathagent.js
341 lines (248 loc) · 10.8 KB
/
agent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/
'use strict';
const KeyVault = require('@azure/keyvault');
const MsRestAzure = require('@azure/ms-rest-nodeauth');
const request = require('request');
const CryptoJS = require('crypto-js');
const base64url = require('base64url');
const Azure = require("@azure/storage-blob");
// Constants
const didConfigFileName = 'did-config.json';
const kvKeyName = 'did-primary-signing-key';
// In-Memory Variables
var didConfig;
// Reads a DID configuration JSON object from Azure Blob Storage
// Azure blob storage location stored in environment variables
// Azure blob storage credentials provided by Azure managed identities
async function FetchDidConfig() {
// get a token from MSI to call Azure Blob Storage
const msiCred = await MsRestAzure.loginWithAppServiceMSI({resource: 'https://storage.azure.com/'});
const tokenResp = await msiCred.getToken();
// get pointers to Azure blob storage from environment variables
const azBlobAccount = process.env.AZURE_STORAGE_ACCOUNT;
const azBlobContainer = process.env.AZURE_STORAGE_CONTAINER;
// load Azure blob storage URLs
const azBlobTokenCredential = new Azure.TokenCredential(tokenResp.accessToken);
const pipeline = new Azure.StorageURL.newPipeline(azBlobTokenCredential);
const azAccountUrl = new Azure.ServiceURL(`https://${azBlobAccount}.blob.core.windows.net`, pipeline);
const azContainerUrl = Azure.ContainerURL.fromServiceURL(azAccountUrl, azBlobContainer);
const azBlobUrl = Azure.BlobURL.fromContainerURL(azContainerUrl, didConfigFileName);
// try to read a DID config file from Azure blob storage
const blobResponse = await azBlobUrl.download(Azure.Aborter.none, 0);
const didConfigData = await StreamToString(blobResponse.readableStreamBody);
return JSON.parse(didConfigData);
}
// Writes a DID configuration object to Azure Blob Storage
// Azure blob storage location stored in environment variables
// Azure blob storage credentials provided by Azure managed identities
async function UploadDidConfig(config) {
// get a token from MSI to call Azure Blob Storage
const msiCred = await MsRestAzure.loginWithAppServiceMSI({resource: 'https://storage.azure.com/'});
const tokenResp = await msiCred.getToken();
// get pointers to Azure blob storage from environment variables
const azBlobAccount = process.env.AZURE_STORAGE_ACCOUNT;
const azBlobContainer = process.env.AZURE_STORAGE_CONTAINER;
// load Azure blob storage URLs
const azBlobTokenCredential = new Azure.TokenCredential(tokenResp.accessToken);
const pipeline = new Azure.StorageURL.newPipeline(azBlobTokenCredential);
const azAccountUrl = new Azure.ServiceURL(`https://${azBlobAccount}.blob.core.windows.net`, pipeline);
const azContainerUrl = Azure.ContainerURL.fromServiceURL(azAccountUrl, azBlobContainer);
const azBlobUrl = Azure.BlobURL.fromContainerURL(azContainerUrl, didConfigFileName);
// try to write DID config file to Azure blob storage
const azBlockBlobUrl = Azure.BlockBlobURL.fromBlobURL(azBlobUrl);
const uploadBlobResponse = await azBlockBlobUrl.upload(Azure.Aborter.none, config, config.length);
return;
}
// Registers a DID for the enterprise, if one has not already been registered
// Creates a Secp256K1 key in Azure Key Vault
// Registers a DID with the created key using Microsoft DID services
// Stores DID & key details in DID configuration file in Azure blob storage
// Azure blob storage & key vault locations stored in environment variables
// Azure blob storage & key vault credentials provided by Azure managed identities
async function EnsureDidRegistered() {
try {
// try to read a DID config file from Azure blob storage
didConfig = await FetchDidConfig();
} catch(err) {
if (err.statusCode != 404)
throw err;
};
// if file exists with a registered DID, no need to re-register
if (didConfig && didConfig.did)
return;
console.log('No existing DID found, proceeding with DID registration');
// get pointer to KeyVault from environment variables
const kvVaultName = process.env.AZURE_KEY_VAULT;
var kvClient;
var kvKeyVersion;
var kvPubJwk;
const kvBaseUrl = `https://${kvVaultName}.vault.azure.net/`;
// get a token from the Azure CLI to call KeyVault
const token = await MsRestAzure.loginWithAppServiceMSI({resource: 'https://vault.azure.net'});
// provision a secp256k1 key in keyvault, remember the public key that is returned
kvClient = new KeyVault.KeyVaultClient(token);
const keyResponse = await kvClient.createKey(kvBaseUrl, kvKeyName, 'EC', {curve: 'P-256K'});
kvKeyVersion = keyResponse.key.kid.split('/').pop();
kvPubJwk = keyResponse.key;
console.log('Successfully created a key in key vault.');
// make some edits to the JWK format to meet expected format from registration service
kvPubJwk.kid = `#${kvKeyVersion}`;
kvPubJwk.use = 'verify';
kvPubJwk.defaultEncryptionAlgorithm = 'none';
kvPubJwk.defaultSigningAlgorithm = 'ES256K';
kvPubJwk.x = base64url(kvPubJwk.x);
kvPubJwk.y = base64url(kvPubJwk.y);
// format a DID document for the new DID to be registered
const didDocument = {
"@context": "https://w3id.org/did/v1",
"publicKey": [
{
"id": kvPubJwk.kid,
"type": "Secp256k1VerificationKey2018",
"publicKeyJwk": kvPubJwk
}
]
}
// form the signature input to pass to KeyVault to be signed
const encodedBody = base64url(Buffer.from(JSON.stringify(didDocument)));
const signatureInput = "" + "." + encodedBody;
const encodedSignature = await GetSignatureForString(signatureInput, kvKeyName, kvKeyVersion);
console.log('Successfully signed a DID registration request using a key in key vault.');
// format a registration request
const requestBody = {
"header": {
"alg": "ES256K",
"kid": kvPubJwk.kid,
"operation": "create",
"proofOfWork": "{}"
},
"payload": encodedBody,
"signature": encodedSignature
}
// register the DID
const registrationResult = await SendPost("https://beta.ion.microsoft.com/api/1.0/register", requestBody);
// create the enterprise agent config file
didConfig = {
"did": registrationResult.id,
"kvKeyName": kvKeyName,
"kvKeyVersion": kvKeyVersion
};
console.log('Successfully registered a DID.');
// upload the new DID configuration document to Azure Blob Storage
UploadDidConfig(JSON.stringify(didConfig));
console.log('Successfully uploaded a new DID config file to Azure blob storage.');
return;
}
// Accepts a string as input, hashes the string, and sends the hash to
// Key Vault to be signed using the DID's' private key.
async function GetSignatureForString(inputString, keyName, keyVersion) {
// get a token from MSI to call KeyVault
const token = await MsRestAzure.loginWithAppServiceMSI({resource: 'https://vault.azure.net'});
// get pointer to KeyVault from environment variables
const kvVaultName = process.env.AZURE_KEY_VAULT;
// setup KeyVault client
const kvClient = new KeyVault.KeyVaultClient(token);
const kvBaseUrl = `https://${kvVaultName}.vault.azure.net/`;
const kvKeyName = keyName;
const kvKeyVersion = keyVersion;
// construct the hash of the input string
const hash = CryptoJS.SHA256(inputString);
const buffer = Buffer.from(hash.toString(CryptoJS.enc.Hex), 'hex');
const array = new Uint8Array(buffer);
// sign the registration request with the private key in KeyVault
const signResponse = await kvClient.sign(kvBaseUrl, kvKeyName, kvKeyVersion, 'ES256K', array);
var signature = signResponse.result;
var derSig = ToDer([signature.slice(0, signature.byteLength / 2), signature.slice(signature.byteLength / 2, signature.byteLength)]);
const encodedSignature = base64url(derSig);
return encodedSignature;
}
// Accepts a properly formatted verfiable credential JSON object as input,
// formats it into a JWT, signs the JWT, and returns the compact serialization
// of a JWT.
async function GenerateVerifiableCredential(contents) {
// construct the claim to be issued
const jwtHeader = {
"alg": "ES256K",
"typ": "JWT",
"kid": `${didConfig.did}#${didConfig.kvKeyVersion}`
}
const jwtBody = {
"sub": "did:alice",
"iss": didConfig.did,
"iat": Date.now(),
"vc": contents
}
// form the signature input to pass to KeyVault to be signed
const encodedBody = base64url(Buffer.from(JSON.stringify(jwtBody)));
const encodedHeader = base64url(Buffer.from(JSON.stringify(jwtHeader)));
const signatureInput = encodedHeader + "." + encodedBody;
const encodedSignature = await GetSignatureForString(signatureInput, didConfig.kvKeyName, didConfig.kvKeyVersion);
// finally, form the claim as a JWT
const claimDetails = `${encodedHeader}.${encodedBody}.${encodedSignature}`;
return claimDetails;
}
// Helper function for sending HTTP POST in async/await style
async function SendPost(url, json) {
return new Promise(function (resolve, reject) {
request.post(url, {json: json}, function (error, res, body) {
if (!error && res.statusCode == 200) {
resolve(body);
} else {
reject(error);
}
});
});
}
// Helper function for reading files from blob storage
async function StreamToString(readableStream) {
return new Promise((resolve, reject) => {
const chunks = [];
readableStream.on("data", data => {
chunks.push(data.toString());
});
readableStream.on("end", () => {
resolve(chunks.join(""));
});
readableStream.on("error", reject);
});
}
// Helper function for converting from KeyVault signature
// format into signature format expected by registration service
function ToDer(elements) {
var index = 0;
// calculate total size.
let lengthOfRemaining = 0;
for (let element = 0 ; element < elements.length; element++) {
// Add element format bytes
lengthOfRemaining += 2;
const buffer = new Uint8Array(elements[element]);
const size = (buffer[0] & 0x80) === 0x80 ? buffer.length + 1 : buffer.length;
lengthOfRemaining += size;
}
// Prepare output
index = 0;
const result = new Uint8Array(lengthOfRemaining + 2);
result.set([0x30, lengthOfRemaining], index);
index += 2;
for (let element = 0 ; element < elements.length; element++) {
// Add element format bytes
const buffer = new Uint8Array(elements[element]);
const size = (buffer[0] & 0x80) === 0x80 ? buffer.length + 1 : buffer.length;
result.set([0x02, size], index);
index += 2;
if (size > buffer.length) {
result.set([0x0], index++);
}
result.set(buffer, index);
index += buffer.length;
}
return result;
}
module.exports = {
EnsureDidRegistered: EnsureDidRegistered,
GenerateVerifiableCredential: GenerateVerifiableCredential
}