oydid 0.5.6 → 0.6.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.
- checksums.yaml +4 -4
- data/LICENSE +10 -18
- data/VERSION +1 -1
- data/lib/oydid/basic.rb +545 -58
- data/lib/oydid/didcomm.rb +58 -0
- data/lib/oydid/log.rb +16 -6
- data/lib/oydid/vc.rb +367 -61
- data/lib/oydid.rb +322 -143
- metadata +131 -37
data/lib/oydid/didcomm.rb
CHANGED
|
@@ -63,6 +63,29 @@ class Oydid
|
|
|
63
63
|
when 'ed25519-priv'
|
|
64
64
|
private_key = RbNaCl::Signatures::Ed25519::SigningKey.new(digest)
|
|
65
65
|
token = JWT.encode payload, private_key, 'ED25519'
|
|
66
|
+
when 'p256-priv'
|
|
67
|
+
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
68
|
+
pub_key = group.generator.mul(OpenSSL::BN.new(digest, 2))
|
|
69
|
+
pub_oct = pub_key.to_bn.to_s(2)
|
|
70
|
+
|
|
71
|
+
parameters = OpenSSL::ASN1::ObjectId("prime256v1")
|
|
72
|
+
parameters.tag = 0
|
|
73
|
+
parameters.tagging = :EXPLICIT
|
|
74
|
+
parameters.tag_class = :CONTEXT_SPECIFIC
|
|
75
|
+
|
|
76
|
+
public_key_bitstring = OpenSSL::ASN1::BitString(pub_oct)
|
|
77
|
+
public_key_bitstring.tag = 1
|
|
78
|
+
public_key_bitstring.tagging = :EXPLICIT
|
|
79
|
+
public_key_bitstring.tag_class = :CONTEXT_SPECIFIC
|
|
80
|
+
|
|
81
|
+
ec_private_key_asn1 = OpenSSL::ASN1::Sequence([
|
|
82
|
+
OpenSSL::ASN1::Integer(1),
|
|
83
|
+
OpenSSL::ASN1::OctetString(digest),
|
|
84
|
+
parameters,
|
|
85
|
+
public_key_bitstring
|
|
86
|
+
])
|
|
87
|
+
ec_key = OpenSSL::PKey.read(ec_private_key_asn1.to_der)
|
|
88
|
+
token = JWT.encode(payload, ec_key, 'ES256')
|
|
66
89
|
else
|
|
67
90
|
token = nil
|
|
68
91
|
error = "unsupported key codec"
|
|
@@ -117,4 +140,39 @@ class Oydid
|
|
|
117
140
|
}.to_json)
|
|
118
141
|
return retVal.parsed_response["access_token"]
|
|
119
142
|
end
|
|
143
|
+
|
|
144
|
+
# other helpers -----------------------------
|
|
145
|
+
def self.build_jwks(content, input_did, options)
|
|
146
|
+
|
|
147
|
+
tmp_did_hash = input_did.delete_prefix("did:oyd:") rescue ""
|
|
148
|
+
tmp_did10 = tmp_did_hash[0,10] + "_private_key.enc" rescue ""
|
|
149
|
+
privateKey, msg = getPrivateKey(options[:doc_enc], options[:doc_pwd], options[:doc_key], tmp_did10, options)
|
|
150
|
+
if privateKey.nil?
|
|
151
|
+
return [nil, "private document key not available: " + msg.to_s]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
code, length, digest = multi_decode(privateKey).first.unpack('SCa*')
|
|
155
|
+
case Multicodecs[code].name
|
|
156
|
+
when 'ed25519-priv'
|
|
157
|
+
signing_key = Ed25519::SigningKey.new(digest)
|
|
158
|
+
else
|
|
159
|
+
return [nil, "unsupported key codec: " + Multicodecs[code].name.to_s]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
jwk = content
|
|
163
|
+
jwk['iss'] = input_did
|
|
164
|
+
jwk['sub'] = input_did
|
|
165
|
+
jwk['iat'] = Time.now.to_i
|
|
166
|
+
jwk['exp'] = Time.now.to_i + 120
|
|
167
|
+
jwk['jti'] = SecureRandom.uuid
|
|
168
|
+
|
|
169
|
+
algorithm = 'EdDSA'
|
|
170
|
+
headers = {
|
|
171
|
+
alg: algorithm,
|
|
172
|
+
typ: 'JWT',
|
|
173
|
+
kid: input_did + '#key-doc' }
|
|
174
|
+
|
|
175
|
+
jwks = JWT.encode(jwk, signing_key, algorithm, headers)
|
|
176
|
+
return [jwks, nil]
|
|
177
|
+
end
|
|
120
178
|
end
|
data/lib/oydid/log.rb
CHANGED
|
@@ -289,10 +289,13 @@ class Oydid
|
|
|
289
289
|
end
|
|
290
290
|
doc = doc.first["doc"]
|
|
291
291
|
if el["op"] == 2 # CREATE
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
292
|
+
# signature for CREATE is optional (due to CMSM)
|
|
293
|
+
if !el["sig"].nil?
|
|
294
|
+
if !match_log_did?(el, doc)
|
|
295
|
+
currentDID["error"] = 1
|
|
296
|
+
currentDID["message"] = "Signatures in log don't match"
|
|
297
|
+
return currentDID
|
|
298
|
+
end
|
|
296
299
|
end
|
|
297
300
|
end
|
|
298
301
|
currentDID["did"] = doc_did
|
|
@@ -331,13 +334,20 @@ class Oydid
|
|
|
331
334
|
did_hash = did_hash.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first
|
|
332
335
|
did10 = did_hash[0,10]
|
|
333
336
|
doc = retrieve_document_raw(doc_did, did10 + ".doc", doc_location, {})
|
|
337
|
+
doc = doc.first["doc"]
|
|
338
|
+
|
|
339
|
+
if !Oydid.match_log_did?(el, doc)
|
|
340
|
+
currentDID["error"] = 1
|
|
341
|
+
currentDID["message"] = "Signatures in log don't match"
|
|
342
|
+
return currentDID
|
|
343
|
+
end
|
|
344
|
+
|
|
334
345
|
# since it retrieves a DID that previously existed, this test is not necessary
|
|
335
346
|
# if doc.first.nil?
|
|
336
347
|
# currentDID["error"] = 2
|
|
337
348
|
# currentDID["message"] = doc.last.to_s
|
|
338
349
|
# return currentDID
|
|
339
350
|
# end
|
|
340
|
-
doc = doc.first["doc"]
|
|
341
351
|
term = doc["log"]
|
|
342
352
|
log_location = term.split(LOCATION_PREFIX).last.split(CGI.escape LOCATION_PREFIX).last rescue ""
|
|
343
353
|
if log_location.to_s == "" || log_location == term
|
|
@@ -512,7 +522,7 @@ class Oydid
|
|
|
512
522
|
did_orig = "did:oyd:" + did_orig
|
|
513
523
|
end
|
|
514
524
|
case rotate_DID_method
|
|
515
|
-
when "did:ebsi"
|
|
525
|
+
when "did:ebsi", "did:cheqd"
|
|
516
526
|
public_resolver = DEFAULT_PUBLIC_RESOLVER
|
|
517
527
|
rotate_DID_Document = HTTParty.get(public_resolver + rotate_DID)
|
|
518
528
|
rotate_ddoc = JSON.parse(rotate_DID_Document.parsed_response)
|
data/lib/oydid/vc.rb
CHANGED
|
@@ -65,11 +65,102 @@ class Oydid
|
|
|
65
65
|
return [retVal.parsed_response, ""]
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
def self.vc_proof_prep(vc, proof)
|
|
69
|
+
cntxt = vc["@context"].dup
|
|
70
|
+
if !cntxt.is_a?(Array)
|
|
71
|
+
cntxt = [cntxt]
|
|
72
|
+
end
|
|
73
|
+
cntxt << ED25519_SECURITY_SUITE unless cntxt.include?(ED25519_SECURITY_SUITE)
|
|
74
|
+
vc["@context"] = cntxt.dup
|
|
75
|
+
vc.delete("proof")
|
|
76
|
+
vc = JSON::LD::API.compact(JSON.parse(vc.to_json), JSON.parse(cntxt.to_json))
|
|
77
|
+
graph = RDF::Graph.new << JSON::LD::Reader.new(vc.to_json)
|
|
78
|
+
norm_graph = graph.dump(:normalize).to_s
|
|
79
|
+
if norm_graph.strip == ""
|
|
80
|
+
return [nil, nil, "empty VC"]
|
|
81
|
+
end
|
|
82
|
+
hash1 = Multibases.pack("base16", RbNaCl::Hash.sha256(norm_graph)).to_s[1..]
|
|
83
|
+
|
|
84
|
+
remove_context = false
|
|
85
|
+
if proof["@context"].nil?
|
|
86
|
+
proof["@context"] = cntxt.dup
|
|
87
|
+
remove_context = true
|
|
88
|
+
else
|
|
89
|
+
cntxt = proof["@context"]
|
|
90
|
+
end
|
|
91
|
+
if proof["created"].nil?
|
|
92
|
+
proof["created"] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
93
|
+
end
|
|
94
|
+
proof.delete("proofValue")
|
|
95
|
+
proof = JSON::LD::API.compact(JSON.parse(proof.to_json), JSON.parse(cntxt.to_json))
|
|
96
|
+
graph = RDF::Graph.new << JSON::LD::Reader.new(proof.to_json)
|
|
97
|
+
norm_graph = graph.dump(:normalize).to_s
|
|
98
|
+
if norm_graph.strip == ""
|
|
99
|
+
return [nil, nil, "empty proof"]
|
|
100
|
+
end
|
|
101
|
+
hash2 = Multibases.pack("base16", RbNaCl::Hash.sha256(norm_graph)).to_s[1..]
|
|
102
|
+
if remove_context
|
|
103
|
+
proof.delete("@context")
|
|
104
|
+
end
|
|
105
|
+
vc["proof"] = proof
|
|
106
|
+
|
|
107
|
+
return [vc, hash2+hash1, nil]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Verifiable Credential hash
|
|
111
|
+
# vc = {"@context", "type", "issuer", "issuanceDate", "credentialSubject"}
|
|
112
|
+
# but no "proof"!
|
|
113
|
+
# proof = {"type", "verificationMethod", "proofPurpose", "created"}
|
|
114
|
+
# but no "proofValue"
|
|
115
|
+
# private_key_encoded (string) "z..."
|
|
116
|
+
# https://www.w3.org/TR/vc-di-eddsa/#representation-ed25519signature2020
|
|
117
|
+
def self.vc_proof(vc, proof, private_key_encoded, options)
|
|
118
|
+
vc, vc_hash, errmsg = vc_proof_prep(vc, proof)
|
|
119
|
+
if vc.nil?
|
|
120
|
+
return [nil, errmsg]
|
|
121
|
+
end
|
|
122
|
+
code, length, digest = multi_decode(private_key_encoded).first.unpack('SCa*')
|
|
123
|
+
case Multicodecs[code].name
|
|
124
|
+
when 'ed25519-priv'
|
|
125
|
+
signing_key = Ed25519::SigningKey.new(digest)
|
|
126
|
+
vc["proof"]["proofValue"] = multi_encode(signing_key.sign([vc_hash].pack('H*')).bytes, options).first
|
|
127
|
+
when 'p256-priv'
|
|
128
|
+
vc["proof"]["proofValue"] = sign("message", private_key_encoded, options)
|
|
129
|
+
else
|
|
130
|
+
return [nil, "unsupported key codec"]
|
|
131
|
+
end
|
|
132
|
+
return [vc, nil]
|
|
133
|
+
|
|
134
|
+
end
|
|
135
|
+
|
|
68
136
|
def self.create_vc(content, options)
|
|
69
|
-
|
|
137
|
+
if options[:issuer_privateKey].to_s == ""
|
|
138
|
+
return [nil, "missing issuer private key"]
|
|
139
|
+
end
|
|
140
|
+
code, length, digest = multi_decode(options[:issuer_privateKey]).first.unpack('SCa*')
|
|
141
|
+
case options[:vc_type].to_s
|
|
142
|
+
when 'Ed25519Signature2020'
|
|
143
|
+
if Multicodecs[code].name != 'ed25519-priv'
|
|
144
|
+
return [nil, "combination of credential type '" + options[:vc_type].to_s + "' and key type '" + Multicodecs[code].name.to_s + "' not supported"]
|
|
145
|
+
end
|
|
146
|
+
when 'JsonWebSignature2020'
|
|
147
|
+
if Multicodecs[code].name != 'p256-priv'
|
|
148
|
+
return [nil, "combination of credential type '" + options[:vc_type].to_s + "' and key type '" + Multicodecs[code].name.to_s + "' not supported"]
|
|
149
|
+
end
|
|
150
|
+
else
|
|
151
|
+
return [nil, "unsupported credential type '" + options[:vc_type].to_s + "'"]
|
|
152
|
+
end
|
|
153
|
+
vercred = content
|
|
70
154
|
# set the context, which establishes the special terms used
|
|
71
155
|
if content["@context"].nil?
|
|
72
|
-
|
|
156
|
+
case options[:vc_type].to_s
|
|
157
|
+
when "Ed25519Signature2020"
|
|
158
|
+
vercred["@context"] = ["https://www.w3.org/ns/credentials/v2"]
|
|
159
|
+
when "JsonWebSignature2020"
|
|
160
|
+
vercred["@context"] = ["https://www.w3.org/2018/credentials/v1"]
|
|
161
|
+
else
|
|
162
|
+
return [nil, "invalid credential type '" + options[:vc_type].to_s + "'"]
|
|
163
|
+
end
|
|
73
164
|
else
|
|
74
165
|
vercred["@context"] = content["@context"]
|
|
75
166
|
end
|
|
@@ -93,7 +184,7 @@ class Oydid
|
|
|
93
184
|
return [nil, "invalid 'issuer'"]
|
|
94
185
|
end
|
|
95
186
|
if options[:ts].nil?
|
|
96
|
-
vercred["issuanceDate"] = Time.now.utc.
|
|
187
|
+
vercred["issuanceDate"] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
97
188
|
else
|
|
98
189
|
vercred["issuanceDate"] = Time.at(options[:ts]).utc.iso8601
|
|
99
190
|
end
|
|
@@ -101,47 +192,117 @@ class Oydid
|
|
|
101
192
|
vercred["credentialSubject"] = {"id": options[:holder]}.merge(content)
|
|
102
193
|
else
|
|
103
194
|
vercred["credentialSubject"] = content["credentialSubject"]
|
|
195
|
+
if vercred["credentialSubject"]["id"].nil?
|
|
196
|
+
if options[:holder].nil?
|
|
197
|
+
return [nil, "missing 'id' (of holder) in 'credentialSubject'"]
|
|
198
|
+
end
|
|
199
|
+
vercred["credentialSubject"]["id"] = options[:holder]
|
|
200
|
+
end
|
|
104
201
|
end
|
|
105
202
|
if vercred["credentialSubject"].to_s == "" || vercred["credentialSubject"].to_s == "{}" || vercred["credentialSubject"].to_s == "[]"
|
|
106
203
|
return [nil, "invalid 'credentialSubject'"]
|
|
107
204
|
end
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
205
|
+
|
|
206
|
+
case options[:vc_type].to_s
|
|
207
|
+
when 'Ed25519Signature2020'
|
|
208
|
+
if content["proof"].nil?
|
|
209
|
+
proof = {}
|
|
210
|
+
proof["type"] = "Ed25519Signature2020"
|
|
211
|
+
proof["verificationMethod"] = options[:issuer].to_s + "#key-doc"
|
|
212
|
+
proof["proofPurpose"] = "assertionMethod"
|
|
213
|
+
id_vc = vercred.dup
|
|
214
|
+
id_vc["proof"] = proof
|
|
215
|
+
identifier_str = multi_hash(canonical(id_vc), options).first
|
|
216
|
+
if options[:vc_location].nil?
|
|
217
|
+
vercred["identifier"] = identifier_str
|
|
218
|
+
else
|
|
219
|
+
vc_location = options[:vc_location].to_s
|
|
220
|
+
if !vc_location.start_with?("http")
|
|
221
|
+
vc_location = "https://" + token_url
|
|
222
|
+
end
|
|
223
|
+
if !vc_location.end_with?('/')
|
|
224
|
+
vc_location += '/'
|
|
225
|
+
end
|
|
226
|
+
if !vc_location.end_with?('credentials/')
|
|
227
|
+
vc_location += 'credentials/'
|
|
228
|
+
end
|
|
229
|
+
vercred["id"] = vc_location + identifier_str
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
vercred, errmsg = vc_proof(vercred, proof, options[:issuer_privateKey], options)
|
|
233
|
+
if vercred.nil?
|
|
234
|
+
return [nil, errmsg]
|
|
235
|
+
end
|
|
236
|
+
# proof["proofValue"] = sign(vercred["credentialSubject"].transform_keys(&:to_s).to_json_c14n, options[:issuer_privateKey], []).first
|
|
237
|
+
else
|
|
238
|
+
id_vc = vercred.dup
|
|
239
|
+
content["proof"].delete("proofValue")
|
|
240
|
+
id_vc["proof"] = content["proof"]
|
|
241
|
+
identifier_str = multi_hash(canonical(id_vc), options).first
|
|
242
|
+
if options[:vc_location].nil?
|
|
243
|
+
vercred["identifier"] = identifier_str
|
|
244
|
+
else
|
|
245
|
+
vercred["id"] = options[:vc_location].to_s + identifier_str
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
vercred, errmsg = vc_proof(vercred, content["proof"], options[:issuer_privateKey], options)
|
|
249
|
+
if vercred.nil?
|
|
250
|
+
return [nil, errmsg]
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
if vercred["proof"].to_s == "" || vercred["proof"].to_s == "{}" || vercred["proof"].to_s == "[]"
|
|
254
|
+
return [nil, "invalid 'proof'"]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
when 'JsonWebSignature2020'
|
|
258
|
+
jwt_vc = {}
|
|
259
|
+
jwt_vc["vc"] = vercred.dup
|
|
260
|
+
jwt_vc["exp"] = (Time.now + (3 * 30 * 24 * 60 * 60)).to_i
|
|
261
|
+
jwt_vc["iss"] = vercred["issuer"]
|
|
262
|
+
jwt_vc["nbf"] =
|
|
263
|
+
if options[:ts].nil?
|
|
264
|
+
jwt_vc["nbf"] = Time.now.utc.to_i
|
|
265
|
+
else
|
|
266
|
+
jwt_vc["nbf"] = options[:ts].to_i
|
|
267
|
+
end
|
|
268
|
+
identifier_str = multi_hash(canonical(vercred), options).first
|
|
269
|
+
jwt_vc["jti"] = identifier_str
|
|
270
|
+
jwt_vc["sub"] = options[:holder]
|
|
271
|
+
|
|
272
|
+
vercred = jwt_vc.dup
|
|
115
273
|
else
|
|
116
|
-
|
|
117
|
-
end
|
|
118
|
-
if vercred["proof"].to_s == "" || vercred["proof"].to_s == "{}" || vercred["proof"].to_s == "[]"
|
|
119
|
-
return [nil, "invalid 'proof'"]
|
|
274
|
+
return [nil, "unsupported credential type '" + options[:vc_type].to_s + "'"]
|
|
120
275
|
end
|
|
121
276
|
|
|
122
|
-
# specify the identifier of the credential
|
|
123
|
-
vercred["identifier"] = hash(vercred.to_json)
|
|
124
277
|
return [vercred, ""]
|
|
125
278
|
end
|
|
126
279
|
|
|
127
280
|
def self.create_vc_proof(content, options)
|
|
128
281
|
if content["id"].nil?
|
|
129
|
-
content["id"] = options[:
|
|
282
|
+
content["id"] = options[:issuer]
|
|
130
283
|
end
|
|
131
284
|
proof = {}
|
|
132
285
|
proof["type"] = "Ed25519Signature2020"
|
|
133
286
|
proof["verificationMethod"] = options[:issuer].to_s
|
|
134
287
|
proof["proofPurpose"] = "assertionMethod"
|
|
135
|
-
proof["proofValue"] = sign(content.to_json_c14n, options[:issuer_privateKey], []).first
|
|
136
288
|
|
|
137
|
-
|
|
289
|
+
content, errmsg = vc_proof(content, proof, options[:issuer_privateKey], options)
|
|
290
|
+
if content.nil?
|
|
291
|
+
return [nil, errmsg]
|
|
292
|
+
end
|
|
293
|
+
# proof["proofValue"] = sign(content.to_json_c14n, options[:issuer_privateKey], []).first
|
|
294
|
+
|
|
295
|
+
return [content["proof"], ""]
|
|
138
296
|
end
|
|
139
297
|
|
|
140
298
|
def self.publish_vc(vc, options)
|
|
141
299
|
vc = vc.transform_keys(&:to_s)
|
|
142
300
|
identifier = vc["identifier"] rescue nil
|
|
143
301
|
if identifier.nil?
|
|
144
|
-
|
|
302
|
+
identifier = vc["id"] rescue nil
|
|
303
|
+
end
|
|
304
|
+
if identifier.nil?
|
|
305
|
+
return [nil, "invalid format (missing identifier)"]
|
|
145
306
|
exit
|
|
146
307
|
end
|
|
147
308
|
if vc["credentialSubject"].is_a?(Array)
|
|
@@ -162,7 +323,9 @@ class Oydid
|
|
|
162
323
|
if vc_location.to_s == ""
|
|
163
324
|
vc_location = DEFAULT_LOCATION
|
|
164
325
|
end
|
|
165
|
-
|
|
326
|
+
if !identifier.start_with?('http')
|
|
327
|
+
identifier = vc_location.sub(/(\/)+$/,'') + "/credentials/" + identifier
|
|
328
|
+
end
|
|
166
329
|
|
|
167
330
|
# build object to post
|
|
168
331
|
vc_data = {
|
|
@@ -178,7 +341,7 @@ class Oydid
|
|
|
178
341
|
err_msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vc_url.to_s
|
|
179
342
|
return [nil, err_msg]
|
|
180
343
|
end
|
|
181
|
-
return [
|
|
344
|
+
return [retVal["identifier"], ""]
|
|
182
345
|
end
|
|
183
346
|
|
|
184
347
|
def self.read_vp(identifier, options)
|
|
@@ -201,39 +364,68 @@ class Oydid
|
|
|
201
364
|
def self.create_vp(content, options)
|
|
202
365
|
verpres = {}
|
|
203
366
|
# set the context, which establishes the special terms used
|
|
204
|
-
|
|
367
|
+
if !content["@context"].nil?
|
|
368
|
+
verpres["@context"] = content["@context"].dup
|
|
369
|
+
else
|
|
370
|
+
verpres["@context"] = ["https://www.w3.org/ns/credentials/v2", ED25519_SECURITY_SUITE]
|
|
371
|
+
end
|
|
205
372
|
verpres["type"] = ["VerifiablePresentation"]
|
|
206
373
|
verpres["verifiableCredential"] = [content].flatten
|
|
207
374
|
|
|
208
375
|
proof = {}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
proof[
|
|
376
|
+
case options[:vc_type].to_s
|
|
377
|
+
when 'Ed25519Signature2020'
|
|
378
|
+
proof['type'] = 'Ed25519Signature2020'
|
|
379
|
+
if !options[:ts].nil?
|
|
380
|
+
proof["created"] = Time.at(options[:ts]).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
381
|
+
end
|
|
382
|
+
proof["verificationMethod"] = options[:holder].to_s
|
|
383
|
+
proof["proofPurpose"] = "authentication"
|
|
384
|
+
verpres, errmsg = vc_proof(verpres, proof, options[:holder_privateKey], options)
|
|
385
|
+
when 'JsonWebSignature2020'
|
|
386
|
+
verpres["holder"] = options[:holder].to_s
|
|
387
|
+
proof['type'] = 'JsonWebSignature2020'
|
|
388
|
+
if options[:ts].nil?
|
|
389
|
+
proof['created'] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
390
|
+
else
|
|
391
|
+
proof["created"] = Time.at(options[:ts]).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
392
|
+
end
|
|
393
|
+
proof["proofPurpose"] = "authentication"
|
|
394
|
+
proof["verificationMethod"] = options[:holder].to_s + '#key-doc'
|
|
395
|
+
|
|
396
|
+
verpres["proof"] = proof
|
|
397
|
+
|
|
398
|
+
options[:issuer] = options[:holder]
|
|
399
|
+
options[:issuer_privateKey] = options[:holder_privateKey]
|
|
400
|
+
jwt, msg = Oydid.jwt_from_vc(verpres, options)
|
|
401
|
+
parts = jwt.split('.')
|
|
402
|
+
detached_jws = "#{parts[0]}..#{parts[2]}"
|
|
403
|
+
|
|
404
|
+
proof['jws'] = detached_jws
|
|
405
|
+
verpres["proof"] = proof
|
|
212
406
|
else
|
|
213
|
-
|
|
407
|
+
return [nil, "unsupported credential type '" + options[:vc_type].to_s + "'"]
|
|
214
408
|
end
|
|
215
|
-
proof["verificationMethod"] = options[:holder].to_s
|
|
216
|
-
proof["proofPurpose"] = "authentication"
|
|
217
409
|
|
|
218
410
|
# private_key = generate_private_key(options[:issuer_privateKey], "ed25519-priv", []).first
|
|
219
|
-
proof["proofValue"] = sign([content].flatten.to_json_c14n, options[:holder_privateKey], []).first
|
|
220
|
-
verpres["proof"] = proof
|
|
411
|
+
# proof["proofValue"] = sign([content].flatten.to_json_c14n, options[:holder_privateKey], []).first
|
|
412
|
+
# verpres["proof"] = proof
|
|
221
413
|
|
|
222
414
|
# specify the identifier of the credential
|
|
223
|
-
verpres["identifier"] = hash(verpres.to_json)
|
|
415
|
+
verpres["identifier"] = hash(canonical(verpres.to_json))
|
|
224
416
|
return [verpres, ""]
|
|
225
417
|
end
|
|
226
418
|
|
|
227
419
|
def self.publish_vp(vp, options)
|
|
228
|
-
|
|
420
|
+
vp = vp.transform_keys(&:to_s)
|
|
229
421
|
identifier = vp["identifier"] rescue nil
|
|
230
422
|
if identifier.nil?
|
|
231
|
-
return [nil, "invalid format (missing identifier"]
|
|
423
|
+
return [nil, "invalid format (missing identifier)"]
|
|
232
424
|
exit
|
|
233
425
|
end
|
|
234
426
|
|
|
235
427
|
proof = vp["proof"].transform_keys(&:to_s) rescue nil
|
|
236
|
-
holder =
|
|
428
|
+
holder = vp["holder"] rescue nil
|
|
237
429
|
if holder.nil?
|
|
238
430
|
return [nil, "invalid format (missing holder)"]
|
|
239
431
|
exit
|
|
@@ -265,46 +457,160 @@ class Oydid
|
|
|
265
457
|
return [vp["identifier"], ""]
|
|
266
458
|
end
|
|
267
459
|
|
|
268
|
-
def self.
|
|
460
|
+
def self.verify_vc(content, options)
|
|
269
461
|
retVal = {}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
462
|
+
vercred = content.to_json_c14n rescue nil
|
|
463
|
+
if vercred.nil?
|
|
464
|
+
return [nil, "invalid verifiableCredential input"]
|
|
273
465
|
end
|
|
274
|
-
retVal[:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
466
|
+
retVal[:id] = content["id"] rescue nil
|
|
467
|
+
if retVal[:id].nil?
|
|
468
|
+
retVal[:id] = content["identifier"] rescue nil
|
|
469
|
+
if retVal[:id].nil?
|
|
470
|
+
return [nil, "invalid VC (missing id)"]
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
issuer = content["issuer"].to_s rescue nil
|
|
474
|
+
if issuer.nil?
|
|
475
|
+
return [nil, "invalid VC (unknown issuer)"]
|
|
279
476
|
exit
|
|
280
477
|
end
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
return [nil, "invalid VP (unknown verifiableCredential"]
|
|
478
|
+
publicKey, msg = getPubKeyFromDID(issuer)
|
|
479
|
+
if publicKey.nil?
|
|
480
|
+
return [nil, "cannot verify public key"]
|
|
285
481
|
exit
|
|
286
482
|
end
|
|
483
|
+
vc, vc_hash, errmsg = vc_proof_prep(JSON.parse(content.to_json), JSON.parse(content["proof"].to_json))
|
|
484
|
+
begin
|
|
485
|
+
pubkey = Oydid.multi_decode(publicKey).first
|
|
486
|
+
code = pubkey.bytes.first
|
|
487
|
+
digest = pubkey[-32..]
|
|
488
|
+
case Multicodecs[code].name
|
|
489
|
+
when 'ed25519-pub'
|
|
490
|
+
verify_key = Ed25519::VerifyKey.new(digest)
|
|
491
|
+
signature_verification = false
|
|
492
|
+
begin
|
|
493
|
+
verify_key.verify(multi_decode(content["proof"]["proofValue"]).first, [vc_hash].pack('H*'))
|
|
494
|
+
signature_verification = true
|
|
495
|
+
rescue Ed25519::VerifyError
|
|
496
|
+
signature_verification = false
|
|
497
|
+
end
|
|
498
|
+
if signature_verification
|
|
499
|
+
return [retVal, nil]
|
|
500
|
+
else
|
|
501
|
+
return [nil, "proof signature does not match VC"]
|
|
502
|
+
end
|
|
503
|
+
else
|
|
504
|
+
return [nil, "unsupported key codec"]
|
|
505
|
+
end
|
|
506
|
+
rescue
|
|
507
|
+
return [nil, "unknown key codec"]
|
|
508
|
+
end
|
|
509
|
+
end
|
|
287
510
|
|
|
511
|
+
def self.verify_vp(content, options)
|
|
512
|
+
retVal = {}
|
|
513
|
+
verpres = content.to_json_c14n rescue nil
|
|
514
|
+
if verpres.nil?
|
|
515
|
+
return [nil, "invalid verifiablePresetation input"]
|
|
516
|
+
end
|
|
517
|
+
retVal[:id] = content["id"] rescue nil
|
|
518
|
+
if retVal[:id].nil?
|
|
519
|
+
retVal[:id] = content["identifier"] rescue nil
|
|
520
|
+
if retVal[:id].nil?
|
|
521
|
+
return [nil, "invalid VP (missing id)"]
|
|
522
|
+
end
|
|
523
|
+
end
|
|
288
524
|
holder = content["proof"]["verificationMethod"].to_s rescue nil
|
|
289
525
|
if holder.nil?
|
|
290
526
|
return [nil, "invalid VP (unknown holder"]
|
|
291
|
-
exit
|
|
292
527
|
end
|
|
293
|
-
|
|
294
|
-
if
|
|
528
|
+
publicKey, msg = getPubKeyFromDID(holder)
|
|
529
|
+
if publicKey.nil?
|
|
295
530
|
return [nil, "cannot verify public key"]
|
|
296
|
-
exit
|
|
297
|
-
end
|
|
298
|
-
result, msg = verify(vercred, proofValue, pubKey)
|
|
299
|
-
if result.to_s == ""
|
|
300
|
-
return [nil, msg]
|
|
301
|
-
exit
|
|
302
531
|
end
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
532
|
+
# begin
|
|
533
|
+
key_type = get_keytype(publicKey)
|
|
534
|
+
case key_type
|
|
535
|
+
# pubkey = Oydid.multi_decode(publicKey).first
|
|
536
|
+
# code = pubkey.bytes.first
|
|
537
|
+
# digest = pubkey[-32..]
|
|
538
|
+
# case Multicodecs[code].name
|
|
539
|
+
when 'ed25519-pub'
|
|
540
|
+
pubkey = Oydid.multi_decode(publicKey).first
|
|
541
|
+
code = pubkey.bytes.first
|
|
542
|
+
digest = pubkey[-32..]
|
|
543
|
+
verify_key = Ed25519::VerifyKey.new(digest)
|
|
544
|
+
vp, vp_hash, errmsg = vc_proof_prep(JSON.parse(content.to_json), JSON.parse(content["proof"].to_json))
|
|
545
|
+
|
|
546
|
+
signature_verification = false
|
|
547
|
+
begin
|
|
548
|
+
verify_key.verify(multi_decode(content["proof"]["proofValue"]).first, [vp_hash].pack('H*'))
|
|
549
|
+
signature_verification = true
|
|
550
|
+
rescue Ed25519::VerifyError
|
|
551
|
+
signature_verification = false
|
|
552
|
+
end
|
|
553
|
+
if signature_verification
|
|
554
|
+
return [retVal, nil]
|
|
555
|
+
else
|
|
556
|
+
return [nil, "proof signature does not match VP"]
|
|
557
|
+
end
|
|
558
|
+
when 'p256-pub'
|
|
559
|
+
jws = content["proof"]["jws"]
|
|
560
|
+
head_b64, _, sig_b64 = jws.split('.')
|
|
561
|
+
verpres = JSON.parse(verpres)
|
|
562
|
+
verpres["proof"].delete("jws")
|
|
563
|
+
verpres.delete("identifier")
|
|
564
|
+
encoded_payload = Base64.urlsafe_encode64(verpres.to_json_c14n, padding: false)
|
|
565
|
+
data_to_sign = "#{head_b64}.#{encoded_payload}"
|
|
566
|
+
# puts 'data_to_sign: ' + data_to_sign.to_s
|
|
567
|
+
# puts 'encoded_signature: ' + sig_b64.to_s
|
|
568
|
+
# puts 'publicKey: ' + publicKey.to_s
|
|
569
|
+
|
|
570
|
+
valid = verify(data_to_sign, sig_b64, publicKey).first
|
|
571
|
+
if valid
|
|
572
|
+
return [retVal, nil]
|
|
573
|
+
else
|
|
574
|
+
return [nil, "proof signature does not match VP"]
|
|
575
|
+
end
|
|
576
|
+
else
|
|
577
|
+
return [nil, "unsupported key codec"]
|
|
578
|
+
end
|
|
579
|
+
# rescue
|
|
580
|
+
# return [nil, "unknown key codec"]
|
|
581
|
+
# end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def self.jwt_from_vc(vc, options)
|
|
585
|
+
if options[:issuer].to_s == ''
|
|
586
|
+
return [nil, 'missing issuer DID']
|
|
587
|
+
end
|
|
588
|
+
header = {
|
|
589
|
+
alg: 'ES256',
|
|
590
|
+
typ: 'JWT',
|
|
591
|
+
kid: options[:issuer] + '#key-doc'
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if options[:issuer_privateKey].to_s == ''
|
|
595
|
+
return [nil, 'missing issuer private key']
|
|
307
596
|
end
|
|
597
|
+
private_key = decode_private_key(options[:issuer_privateKey]).first
|
|
598
|
+
|
|
599
|
+
encoded_header = Base64.urlsafe_encode64(header.to_json, padding: false)
|
|
600
|
+
encoded_payload = Base64.urlsafe_encode64(vc.to_json_c14n, padding: false)
|
|
601
|
+
data_to_sign = "#{encoded_header}.#{encoded_payload}"
|
|
602
|
+
# puts 'data_to_sign: ' + data_to_sign.to_s
|
|
603
|
+
# puts 'privateKey: ' + options[:issuer_privateKey].to_s
|
|
604
|
+
|
|
605
|
+
jwt_digest = OpenSSL::Digest::SHA256.new
|
|
606
|
+
asn1_signature = OpenSSL::ASN1.decode(private_key.dsa_sign_asn1(jwt_digest.digest(data_to_sign)))
|
|
607
|
+
raw_signature = asn1_signature.value.map { |i| i.value.to_s(2).rjust(32, "\x00") }.join()
|
|
608
|
+
encoded_signature = Base64.urlsafe_encode64(raw_signature, padding: false)
|
|
609
|
+
# puts 'encoded_signature: ' + encoded_signature.to_s
|
|
610
|
+
|
|
611
|
+
jwt = "#{encoded_header}.#{encoded_payload}.#{encoded_signature}"
|
|
612
|
+
|
|
613
|
+
return [jwt, nil]
|
|
308
614
|
end
|
|
309
615
|
|
|
310
616
|
end
|