oydid 0.4.4 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/oydid/basic.rb +149 -33
- data/lib/oydid/didcomm.rb +9 -9
- data/lib/oydid/log.rb +14 -7
- data/lib/oydid/vc.rb +308 -0
- data/lib/oydid.rb +305 -112
- data/spec/input/basic/sample_b16_dec.doc +1 -0
- data/spec/input/basic/sample_b17_edec.doc +1 -0
- data/spec/input/basic/sample_b32_dec.doc +1 -0
- data/spec/input/basic/sample_b58_enc.doc +1 -0
- data/spec/input/basic/sample_b64_dec.doc +1 -0
- data/spec/input/basic/sample_b64_enc.doc +1 -0
- data/spec/input/basic/sample_blake2b-16_b16_hash.doc +1 -0
- data/spec/input/basic/sample_blake2b-32_b32_hash.doc +1 -0
- data/spec/input/basic/sample_blake2b-64_b58_hash.doc +1 -0
- data/spec/input/basic/sample_invalid2_readkey.doc +1 -1
- data/spec/input/basic/sample_invalid3_readkey.doc +1 -1
- data/spec/input/basic/sample_readkey.doc +1 -1
- data/spec/input/basic/sample_sha2-256_b58_hash.doc +1 -0
- data/spec/input/basic/sample_sha2-512_b58_hash.doc +1 -0
- data/spec/input/basic/sample_sha3-224_b64_hash.doc +1 -0
- data/spec/output/basic/sample_b16_dec.doc +1 -0
- data/spec/output/basic/sample_b16_enc.doc +1 -0
- data/spec/output/basic/sample_b17_edec.doc +2 -0
- data/spec/output/basic/sample_b17_enc.doc +1 -0
- data/spec/output/basic/sample_b32_dec.doc +1 -0
- data/spec/output/basic/sample_b32_enc.doc +1 -0
- data/spec/output/basic/sample_b58_dec.doc +1 -0
- data/spec/output/basic/sample_b64_dec.doc +1 -0
- data/spec/output/basic/sample_b64_enc.doc +1 -0
- data/spec/output/basic/sample_blake2b-16_b16_hash.doc +1 -0
- data/spec/output/basic/sample_blake2b-32_b32_hash.doc +1 -0
- data/spec/output/basic/sample_blake2b-64_b58_hash.doc +1 -0
- data/spec/output/basic/sample_sha2-512_b58_hash.doc +1 -0
- data/spec/output/basic/sample_sha3-224_b64_hash.doc +1 -0
- data/spec/oydid_spec.rb +95 -13
- metadata +72 -19
- /data/spec/input/basic/{sample_enc.doc → sample_b16_enc.doc} +0 -0
- /data/spec/input/basic/{sample_hash.doc → sample_b17_enc.doc} +0 -0
- /data/spec/{output/basic/sample_dec.doc → input/basic/sample_b32_enc.doc} +0 -0
- /data/spec/input/basic/{sample_dec.doc → sample_b58_dec.doc} +0 -0
- /data/spec/output/basic/{sample_enc.doc → sample_b58_enc.doc} +0 -0
- /data/spec/output/basic/{sample_hash.doc → sample_sha2-256_b58_hash.doc} +0 -0
data/lib/oydid/vc.rb
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Oydid
|
5
|
+
def self.read_vc(identifier, options)
|
6
|
+
vc_location = ""
|
7
|
+
if !options[:location].nil?
|
8
|
+
vc_location = options[:location]
|
9
|
+
end
|
10
|
+
if vc_location.to_s == ""
|
11
|
+
vc_location = DEFAULT_LOCATION
|
12
|
+
end
|
13
|
+
vc_url = vc_location.sub(/(\/)+$/,'') + "/credentials/" + identifier
|
14
|
+
|
15
|
+
holder = options[:holder].to_s rescue nil
|
16
|
+
if holder.to_s == ""
|
17
|
+
msg = "missing holder information"
|
18
|
+
return [nil, msg]
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
|
22
|
+
private_key = options[:holder_privateKey].to_s rescue nil
|
23
|
+
if private_key.to_s == ""
|
24
|
+
msg = "missing private document key information"
|
25
|
+
return [nil, msg]
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
# authenticate against repository
|
30
|
+
init_url = vc_location + "/oydid/init"
|
31
|
+
sid = SecureRandom.hex(20).to_s
|
32
|
+
response = HTTParty.post(init_url,
|
33
|
+
headers: { 'Content-Type' => 'application/json' },
|
34
|
+
body: { "session_id": sid,
|
35
|
+
"public_key": Oydid.public_key(private_key, options).first }.to_json ).parsed_response rescue {}
|
36
|
+
if response["challenge"].nil?
|
37
|
+
msg = "missing challenge for repository authentication"
|
38
|
+
return [nil, msg]
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
challenge = response["challenge"].to_s
|
42
|
+
|
43
|
+
# sign challenge and request token
|
44
|
+
token_url = vc_location + "/oydid/token"
|
45
|
+
response = HTTParty.post(token_url,
|
46
|
+
headers: { 'Content-Type' => 'application/json' },
|
47
|
+
body: { "session_id": sid,
|
48
|
+
"signed_challenge": Oydid.sign(challenge, private_key, options).first }.to_json).parsed_response rescue {}
|
49
|
+
access_token = response["access_token"].to_s rescue nil
|
50
|
+
if access_token.nil?
|
51
|
+
msg = "invalid repository authentication (access_token)"
|
52
|
+
return [nil, msg]
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
retVal = HTTParty.get(vc_url,
|
57
|
+
headers: {'Authorization' => 'Bearer ' + access_token})
|
58
|
+
if retVal.code != 200
|
59
|
+
if retVal.code == 401
|
60
|
+
msg = "unauthorized (valid Bearer token required)"
|
61
|
+
else
|
62
|
+
msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vc_url.to_s
|
63
|
+
end
|
64
|
+
return [nil, msg]
|
65
|
+
end
|
66
|
+
return [retVal.parsed_response, ""]
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.create_vc(content, options)
|
70
|
+
vercred = {}
|
71
|
+
# set the context, which establishes the special terms used
|
72
|
+
if content["@context"].nil?
|
73
|
+
vercred["@context"] = ["https://www.w3.org/ns/credentials/v2"]
|
74
|
+
else
|
75
|
+
vercred["@context"] = content["@context"]
|
76
|
+
end
|
77
|
+
if vercred["@context"].to_s == "" || vercred["@context"].to_s == "{}" || vercred["@context"].to_s == "[]"
|
78
|
+
return [nil, "invalid '@context'"]
|
79
|
+
end
|
80
|
+
if content["type"].nil?
|
81
|
+
vercred["type"] = ["VerifiableCredential"]
|
82
|
+
else
|
83
|
+
vercred["type"] = content["type"]
|
84
|
+
end
|
85
|
+
if vercred["type"].to_s == "" || vercred["type"].to_s == "{}" || vercred["type"].to_s == "[]"
|
86
|
+
return [nil, "invalid 'type'"]
|
87
|
+
end
|
88
|
+
if content["issuer"].nil?
|
89
|
+
vercred["issuer"] = options[:issuer]
|
90
|
+
else
|
91
|
+
vercred["issuer"] = content["issuer"]
|
92
|
+
end
|
93
|
+
if vercred["issuer"].to_s == "" || vercred["issuer"].to_s == "{}" || vercred["issuer"].to_s == "[]"
|
94
|
+
return [nil, "invalid 'issuer'"]
|
95
|
+
end
|
96
|
+
if options[:ts].nil?
|
97
|
+
vercred["issuanceDate"] = Time.now.utc.iso8601
|
98
|
+
else
|
99
|
+
vercred["issuanceDate"] = Time.at(options[:ts]).utc.iso8601
|
100
|
+
end
|
101
|
+
if content["credentialSubject"].nil?
|
102
|
+
vercred["credentialSubject"] = {"id": options[:holder]}.merge(content)
|
103
|
+
else
|
104
|
+
vercred["credentialSubject"] = content["credentialSubject"]
|
105
|
+
end
|
106
|
+
if vercred["credentialSubject"].to_s == "" || vercred["credentialSubject"].to_s == "{}" || vercred["credentialSubject"].to_s == "[]"
|
107
|
+
return [nil, "invalid 'credentialSubject'"]
|
108
|
+
end
|
109
|
+
if content["proof"].nil?
|
110
|
+
proof = {}
|
111
|
+
proof["type"] = "Ed25519Signature2020"
|
112
|
+
proof["verificationMethod"] = options[:issuer].to_s
|
113
|
+
proof["proofPurpose"] = "assertionMethod"
|
114
|
+
proof["proofValue"] = sign(vercred["credentialSubject"].to_json_c14n, options[:issuer_privateKey], []).first
|
115
|
+
vercred["proof"] = proof
|
116
|
+
else
|
117
|
+
vercred["proof"] = content["proof"]
|
118
|
+
end
|
119
|
+
if vercred["proof"].to_s == "" || vercred["proof"].to_s == "{}" || vercred["proof"].to_s == "[]"
|
120
|
+
return [nil, "invalid 'proof'"]
|
121
|
+
end
|
122
|
+
|
123
|
+
# specify the identifier of the credential
|
124
|
+
vercred["identifier"] = hash(vercred.to_json)
|
125
|
+
return [vercred, ""]
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.create_vc_proof(content, options)
|
129
|
+
proof = {}
|
130
|
+
proof["type"] = "Ed25519Signature2020"
|
131
|
+
proof["verificationMethod"] = options[:issuer].to_s
|
132
|
+
proof["proofPurpose"] = "assertionMethod"
|
133
|
+
proof["proofValue"] = sign(content.to_json_c14n, options[:issuer_privateKey], []).first
|
134
|
+
|
135
|
+
return [proof, ""]
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.publish_vc(vc, options)
|
139
|
+
vc = vc.transform_keys(&:to_s)
|
140
|
+
identifier = vc["identifier"] rescue nil
|
141
|
+
if identifier.nil?
|
142
|
+
return [nil, "invalid format (missing identifier"]
|
143
|
+
exit
|
144
|
+
end
|
145
|
+
if vc["credentialSubject"].is_a?(Array)
|
146
|
+
cs = vc["credentialSubject"].last.transform_keys(&:to_s) rescue nil
|
147
|
+
else
|
148
|
+
cs = vc["credentialSubject"].transform_keys(&:to_s) rescue nil
|
149
|
+
end
|
150
|
+
holder = cs["id"] rescue nil
|
151
|
+
if holder.nil?
|
152
|
+
return [nil, "invalid format (missing holder)"]
|
153
|
+
exit
|
154
|
+
end
|
155
|
+
|
156
|
+
vc_location = ""
|
157
|
+
if !options[:location].nil?
|
158
|
+
vc_location = options[:location]
|
159
|
+
end
|
160
|
+
if vc_location.to_s == ""
|
161
|
+
vc_location = DEFAULT_LOCATION
|
162
|
+
end
|
163
|
+
vc["identifier"] = vc_location.sub(/(\/)+$/,'') + "/credentials/" + identifier
|
164
|
+
|
165
|
+
# build object to post
|
166
|
+
vc_data = {
|
167
|
+
"identifier": identifier,
|
168
|
+
"vc": vc,
|
169
|
+
"holder": holder
|
170
|
+
}
|
171
|
+
vc_url = vc_location.sub(/(\/)+$/,'') + "/credentials"
|
172
|
+
retVal = HTTParty.post(vc_url,
|
173
|
+
headers: { 'Content-Type' => 'application/json' },
|
174
|
+
body: vc_data.to_json )
|
175
|
+
if retVal.code != 200
|
176
|
+
err_msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vc_url.to_s
|
177
|
+
return [nil, err_msg]
|
178
|
+
end
|
179
|
+
return [vc["identifier"], ""]
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.read_vp(identifier, options)
|
183
|
+
vp_location = ""
|
184
|
+
if !options[:location].nil?
|
185
|
+
vp_location = options[:location]
|
186
|
+
end
|
187
|
+
if vp_location.to_s == ""
|
188
|
+
vp_location = DEFAULT_LOCATION
|
189
|
+
end
|
190
|
+
vp_url = vp_location.sub(/(\/)+$/,'') + "/presentations/" + identifier
|
191
|
+
retVal = HTTParty.get(vp_url)
|
192
|
+
if retVal.code != 200
|
193
|
+
msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vp_url.to_s
|
194
|
+
return [nil, msg]
|
195
|
+
end
|
196
|
+
return [retVal.parsed_response, ""]
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.create_vp(content, options)
|
200
|
+
verpres = {}
|
201
|
+
# set the context, which establishes the special terms used
|
202
|
+
verpres["@context"] = ["https://www.w3.org/ns/credentials/v2"]
|
203
|
+
verpres["type"] = ["VerifiablePresentation"]
|
204
|
+
verpres["verifiableCredential"] = [content].flatten
|
205
|
+
|
206
|
+
proof = {}
|
207
|
+
proof["type"] = "Ed25519Signature2020"
|
208
|
+
if options[:ts].nil?
|
209
|
+
proof["created"] = Time.now.utc.iso8601
|
210
|
+
else
|
211
|
+
proof["created"] = Time.at(options[:ts]).utc.iso8601
|
212
|
+
end
|
213
|
+
proof["verificationMethod"] = options[:holder].to_s
|
214
|
+
proof["proofPurpose"] = "authentication"
|
215
|
+
|
216
|
+
# private_key = generate_private_key(options[:issuer_privateKey], "ed25519-priv", []).first
|
217
|
+
proof["proofValue"] = sign([content].flatten.to_json_c14n, options[:holder_privateKey], []).first
|
218
|
+
verpres["proof"] = proof
|
219
|
+
|
220
|
+
# specify the identifier of the credential
|
221
|
+
verpres["identifier"] = hash(verpres.to_json)
|
222
|
+
return [verpres, ""]
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.publish_vp(vp, options)
|
226
|
+
vc = vp.transform_keys(&:to_s)
|
227
|
+
identifier = vp["identifier"] rescue nil
|
228
|
+
if identifier.nil?
|
229
|
+
return [nil, "invalid format (missing identifier"]
|
230
|
+
exit
|
231
|
+
end
|
232
|
+
|
233
|
+
proof = vp["proof"].transform_keys(&:to_s) rescue nil
|
234
|
+
holder = proof["verificationMethod"] rescue nil
|
235
|
+
if holder.nil?
|
236
|
+
return [nil, "invalid format (missing holder)"]
|
237
|
+
exit
|
238
|
+
end
|
239
|
+
|
240
|
+
vp_location = ""
|
241
|
+
if !options[:location].nil?
|
242
|
+
vp_location = options[:location]
|
243
|
+
end
|
244
|
+
if vp_location.to_s == ""
|
245
|
+
vp_location = DEFAULT_LOCATION
|
246
|
+
end
|
247
|
+
vp["identifier"] = vp_location.sub(/(\/)+$/,'') + "/presentations/" + identifier
|
248
|
+
|
249
|
+
# build object to post
|
250
|
+
vp_data = {
|
251
|
+
"identifier": identifier,
|
252
|
+
"vp": vp,
|
253
|
+
"holder": holder
|
254
|
+
}
|
255
|
+
vp_url = vp_location.sub(/(\/)+$/,'') + "/presentations"
|
256
|
+
retVal = HTTParty.post(vp_url,
|
257
|
+
headers: { 'Content-Type' => 'application/json' },
|
258
|
+
body: vp_data.to_json )
|
259
|
+
if retVal.code != 200
|
260
|
+
err_msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vp_url.to_s
|
261
|
+
return [nil, err_msg]
|
262
|
+
end
|
263
|
+
return [vp["identifier"], ""]
|
264
|
+
end
|
265
|
+
|
266
|
+
def self.verify_vp(content, options)
|
267
|
+
retVal = {}
|
268
|
+
if content["identifier"].nil?
|
269
|
+
return [nil, "invalid VP (unknown identifier"]
|
270
|
+
exit
|
271
|
+
end
|
272
|
+
retVal[:identifier] = content["identifier"].to_s
|
273
|
+
|
274
|
+
proofValue = content["proof"]["proofValue"].to_s rescue nil
|
275
|
+
if proofValue.nil?
|
276
|
+
return [nil, "invalid VP (unknown proofValue"]
|
277
|
+
exit
|
278
|
+
end
|
279
|
+
|
280
|
+
vercred = content["verifiableCredential"].to_json_c14n rescue nil
|
281
|
+
if vercred.nil?
|
282
|
+
return [nil, "invalid VP (unknown verifiableCredential"]
|
283
|
+
exit
|
284
|
+
end
|
285
|
+
|
286
|
+
holder = content["proof"]["verificationMethod"].to_s rescue nil
|
287
|
+
if holder.nil?
|
288
|
+
return [nil, "invalid VP (unknown holder"]
|
289
|
+
exit
|
290
|
+
end
|
291
|
+
pubKey, msg = getPubKeyFromDID(holder)
|
292
|
+
if pubKey.nil?
|
293
|
+
return [nil, "cannot verify public key"]
|
294
|
+
exit
|
295
|
+
end
|
296
|
+
result, msg = verify(vercred, proofValue, pubKey)
|
297
|
+
if result.to_s == ""
|
298
|
+
return [nil, msg]
|
299
|
+
exit
|
300
|
+
end
|
301
|
+
if result
|
302
|
+
return [retVal, ""]
|
303
|
+
else
|
304
|
+
return [nil, "signature verification failed"]
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|