Class: Oydid
- Inherits:
-
Object
- Object
- Oydid
- Defined in:
- lib/oydid.rb,
lib/oydid/vc.rb,
lib/oydid/log.rb,
lib/oydid/basic.rb,
lib/oydid/didcomm.rb
Constant Summary collapse
- LOCATION_PREFIX =
"@"- DEFAULT_LOCATION =
"https://oydid.ownyourdata.eu"- DEFAULT_DIGEST =
"sha2-256"- SUPPORTED_DIGESTS =
["sha2-256", "sha2-512", "sha3-224", "sha3-256", "sha3-384", "sha3-512", "blake2b-16", "blake2b-32", "blake2b-64"]
- DEFAULT_ENCODING =
"base58btc"- SUPPORTED_ENCODINGS =
["base16", "base32", "base58btc", "base64"]
- LOG_HASH_OPTIONS =
{:digest => "sha2-256", :encode => "base58btc"}
- ED25519_SECURITY_SUITE =
"https://w3id.org/security/suites/ed25519-2020/v1"- JWS_SECURITY_SUITE =
"https://w3id.org/security/suites/jws-2020/v1"- DEFAULT_PUBLIC_RESOLVER =
"https://dev.uniresolver.io/1.0/identifiers/"
Class Method Summary collapse
-
.add_hash(log) ⇒ Object
log functions —————————–.
- .base64_url_decode(str) ⇒ Object
-
.build_jwks(content, input_did, options) ⇒ Object
other helpers —————————–.
- .canonical(message) ⇒ Object
- .check_cmsm(pubkey, options) ⇒ Object
- .clone(did, options) ⇒ Object
- .create(content, options) ⇒ Object
- .create_vc(content, options) ⇒ Object
- .create_vc_proof(content, options) ⇒ Object
- .create_vp(content, options) ⇒ Object
- .dag2array(dag, log_array, index, result, options) ⇒ Object
- .dag2array_terminate(dag, log_array, index, result, options) ⇒ Object
- .dag_did(logs, options) ⇒ Object
- .dag_update(currentDID, options) ⇒ Object
-
.dcpm(payload, options) ⇒ Object
DIDComm Plain Message ———————.
-
.dcsm(payload, private_key_encoded, options) ⇒ Object
DIDComm Signed Message ——————–.
- .dcsm_verify(token, options) ⇒ Object
- .decode_private_key(key_encoded, options = {}) ⇒ Object
- .decode_public_key(key_encoded) ⇒ Object
- .decrypt(message, private_key, options = {}) ⇒ Object
- .decryptJWE(message, private_key, options = {}) ⇒ Object
- .delegate(did, options) ⇒ Object
- .encrypt(message, public_key, options = {}) ⇒ Object
- .encryptJWE(message, public_key, options = {}) ⇒ Object
- .fromW3C(didDocument, options) ⇒ Object
- .generate_base(content, did, mode, options) ⇒ Object
- .generate_private_key(input, method = "ed25519-priv", options = {}) ⇒ Object
- .get_digest(message) ⇒ Object
- .get_encoding(message) ⇒ Object
-
.get_keytype(input) ⇒ Object
key management —————————-.
- .get_location(id) ⇒ Object
-
.getDelegatedPubKeysFromDID(did, key_type = "doc") ⇒ Object
available key_types * doc - document key * rev - revocation key.
- .getDelegatedPubKeysFromFullDidDocument(did_document, key_type = "doc") ⇒ Object
- .getPrivateKey(enc, pwd, dsk, dfl, options) ⇒ Object
-
.getPubKeyFromDID(did) ⇒ Object
if the identifier is already the public key there is no validation if it is a valid key (this is a privacy-preserving feature).
- .hash(message) ⇒ Object
- .jwt_from_vc(vc, options) ⇒ Object
-
.match_log_did?(log, doc) ⇒ Boolean
check if signature matches current document check if signature in log is correct.
- .msg_decrypt(token, public_key_encoded, options) ⇒ Object
-
.msg_encrypt(payload, private_key_encoded, did, options) ⇒ Object
encryption ———————————–.
-
.msg_sign(payload, hmac_secret) ⇒ Object
signing for JWS —————————.
- .msg_verify_jws(token, hmac_secret) ⇒ Object
- .multi_decode(message) ⇒ Object
-
.multi_encode(message, options) ⇒ Object
basic functions ————————— %w[multibases multihashes rbnacl json multicodecs].each { |f| require f }.
- .multi_hash(message, options) ⇒ Object
- .percent_encode(did) ⇒ Object
- .persist_cmsm(pubkey, payload, options) ⇒ Object
- .private_key_to_jwk(private_key) ⇒ Object
- .public_key(private_key, options = {}, method = nil) ⇒ Object
- .public_key_from_jwk(jwk, options = {}) ⇒ Object
- .public_key_to_jwk(public_key) ⇒ Object
- .publish(did, didDocument, logs, options) ⇒ Object
- .publish_vc(vc, options) ⇒ Object
- .publish_vp(vp, options) ⇒ Object
-
.read(did, options) ⇒ Object
expected DID format: did:oyd:123.
- .read_private_key(filename, options) ⇒ Object
- .read_private_storage(filename) ⇒ Object
- .read_varint(str) ⇒ Object
- .read_vc(identifier, options) ⇒ Object
- .read_vp(identifier, options) ⇒ Object
- .retrieve_document(doc_identifier, doc_file, doc_location, options) ⇒ Object
- .retrieve_document_raw(doc_hash, doc_file, doc_location, options) ⇒ Object
- .retrieve_log(did_hash, log_file, log_location, options) ⇒ Object
- .retrieve_log_item(log_hash, log_location, options) ⇒ Object
- .revoke(did, options) ⇒ Object
- .revoke_base(did, options) ⇒ Object
- .revoke_publish(did, revoc_log, options) ⇒ Object
- .sign(message, private_key, options = {}) ⇒ Object
- .simulate_did(content, did, mode, options) ⇒ Object
- .to_varint(n) ⇒ Object
-
.token_from_challenge(host, pwd, options = {}) ⇒ Object
DID Auth for data container with challenge —.
- .update(content, did, options) ⇒ Object
-
.vc_proof(vc, proof, private_key_encoded, options) ⇒ Object
Verifiable Credential hash vc = “type”, “issuer”, “issuanceDate”, “credentialSubject” but no “proof”! proof = “verificationMethod”, “proofPurpose”, “created” but no “proofValue” private_key_encoded (string) “z…” www.w3.org/TR/vc-di-eddsa/#representation-ed25519signature2020.
- .vc_proof_prep(vc, proof) ⇒ Object
- .verify(message, signature, public_key) ⇒ Object
- .verify_vc(content, options) ⇒ Object
- .verify_vp(content, options) ⇒ Object
- .w3c(did_info, options) ⇒ Object
- .w3c_legacy(did_info, options) ⇒ Object
- .write(content, did, mode, options) ⇒ Object
- .write_log(did, log, options = {}) ⇒ Object
-
.write_private_storage(payload, filename) ⇒ Object
storage functions —————————–.
Instance Method Summary collapse
Class Method Details
.add_hash(log) ⇒ Object
log functions —————————–
6 7 8 9 10 11 12 13 14 |
# File 'lib/oydid/log.rb', line 6 def self.add_hash(log) log.map do |item| item["entry-hash"] = multi_hash(canonical(item.slice("ts","op","doc","sig","previous")), LOG_HASH_OPTIONS).first if item.transform_keys(&:to_s)["op"] == 1 # REVOKE item["sub-entry-hash"] = multi_hash(canonical(item.slice("ts","op","doc","sig")), LOG_HASH_OPTIONS).first end item end end |
.base64_url_decode(str) ⇒ Object
959 960 961 |
# File 'lib/oydid/basic.rb', line 959 def self.base64_url_decode(str) Base64.urlsafe_decode64(str + '=' * (4 - str.length % 4)) end |
.build_jwks(content, input_did, options) ⇒ Object
other helpers —————————–
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 |
# File 'lib/oydid/didcomm.rb', line 145 def self.build_jwks(content, input_did, ) tmp_did_hash = input_did.delete_prefix("did:oyd:") rescue "" tmp_did10 = tmp_did_hash[0,10] + "_private_key.enc" rescue "" privateKey, msg = getPrivateKey([:doc_enc], [:doc_pwd], [:doc_key], tmp_did10, ) if privateKey.nil? return [nil, "private document key not available: " + msg.to_s] end code, length, digest = multi_decode(privateKey).first.unpack('SCa*') case Multicodecs[code].name when 'ed25519-priv' signing_key = Ed25519::SigningKey.new(digest) else return [nil, "unsupported key codec: " + Multicodecs[code].name.to_s] end jwk = content jwk['iss'] = input_did jwk['sub'] = input_did jwk['iat'] = Time.now.to_i jwk['exp'] = Time.now.to_i + 120 jwk['jti'] = SecureRandom.uuid algorithm = 'EdDSA' headers = { alg: algorithm, typ: 'JWT', kid: input_did + '#key-doc' } jwks = JWT.encode(jwk, signing_key, algorithm, headers) return [jwks, nil] end |
.canonical(message) ⇒ Object
101 102 103 104 105 106 107 108 |
# File 'lib/oydid/basic.rb', line 101 def self.canonical() if .is_a? String = JSON.parse() rescue else = JSON.parse(.to_json) rescue end .to_json_c14n end |
.check_cmsm(pubkey, options) ⇒ Object
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 |
# File 'lib/oydid.rb', line 614 def self.check_cmsm(pubkey, ) doc_location = [:doc_location] if doc_location.to_s == "" doc_location = DEFAULT_LOCATION end doc_location = doc_location.sub("%3A%2F%2F","://").sub("%3A", ":") case doc_location.to_s when /^http/ retVal = HTTParty.get(doc_location + "/cmsm/" + pubkey) if retVal.code != 200 msg = retVal.parsed_response["error"].to_s rescue "" if msg.to_s == "" msg = "invalid response from " + doc_location.to_s + "/cmsm/" + pubkey.to_s end return [nil, msg] end return [retVal.parsed_response.transform_keys(&:to_s), ""] else return [nil, "location not supported for querying data in cmsm-flow"] end return [payload, ""] end |
.clone(did, options) ⇒ Object
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 |
# File 'lib/oydid.rb', line 992 def self.clone(did, ) # check if locations differ target_location = [:doc_location] if target_location.to_s == "" target_location = DEFAULT_LOCATION end if did.include?(LOCATION_PREFIX) tmp = did.split(LOCATION_PREFIX) did = tmp[0] source_location = tmp[1] end if did.include?(CGI.escape LOCATION_PREFIX) tmp = did.split(CGI.escape LOCATION_PREFIX) did = tmp[0] source_location = tmp[1] end if source_location.to_s == "" source_location = DEFAULT_LOCATION end if target_location == source_location return [nil, "cannot clone to same location (" + target_location.to_s + ")"] end # get original did info [:doc_location] = source_location [:log_location] = source_location source_did, msg = read(did, ) if source_did.nil? return [nil, "cannot resolve DID (on cloning DID)"] end if source_did["error"] != 0 return [nil, source_did["message"].to_s] end if source_did["doc_log_id"].nil? return [nil, "cannot parse DID log"] end source_log = source_did["log"].first(source_did["doc_log_id"] + 1).last.to_json # write did to new location [:doc_location] = target_location [:log_location] = target_location [:previous_clone] = multi_hash(canonical(source_log), LOG_HASH_OPTIONS).first + LOCATION_PREFIX + source_location [:source_location] = source_location [:source_did] = source_did["did"] retVal, msg = write(source_did["doc"]["doc"], nil, "clone", ) return [retVal, msg] end |
.create(content, options) ⇒ Object
168 169 170 |
# File 'lib/oydid.rb', line 168 def self.create(content, ) return write(content, nil, "create", ) end |
.create_vc(content, options) ⇒ Object
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 |
# File 'lib/oydid/vc.rb', line 136 def self.create_vc(content, ) if [:issuer_privateKey].to_s == "" return [nil, "missing issuer private key"] end code, length, digest = multi_decode([:issuer_privateKey]).first.unpack('SCa*') case [:vc_type].to_s when 'Ed25519Signature2020' if Multicodecs[code].name != 'ed25519-priv' return [nil, "combination of credential type '" + [:vc_type].to_s + "' and key type '" + Multicodecs[code].name.to_s + "' not supported"] end when 'JsonWebSignature2020' if Multicodecs[code].name != 'p256-priv' return [nil, "combination of credential type '" + [:vc_type].to_s + "' and key type '" + Multicodecs[code].name.to_s + "' not supported"] end else return [nil, "unsupported credential type '" + [:vc_type].to_s + "'"] end vercred = content # set the context, which establishes the special terms used if content["@context"].nil? case [:vc_type].to_s when "Ed25519Signature2020" vercred["@context"] = ["https://www.w3.org/ns/credentials/v2"] when "JsonWebSignature2020" vercred["@context"] = ["https://www.w3.org/2018/credentials/v1"] else return [nil, "invalid credential type '" + [:vc_type].to_s + "'"] end else vercred["@context"] = content["@context"] end if vercred["@context"].to_s == "" || vercred["@context"].to_s == "{}" || vercred["@context"].to_s == "[]" return [nil, "invalid '@context'"] end if content["type"].nil? vercred["type"] = ["VerifiableCredential"] else vercred["type"] = content["type"] end if vercred["type"].to_s == "" || vercred["type"].to_s == "{}" || vercred["type"].to_s == "[]" return [nil, "invalid 'type'"] end if content["issuer"].nil? vercred["issuer"] = [:issuer] else vercred["issuer"] = content["issuer"] end if vercred["issuer"].to_s == "" || vercred["issuer"].to_s == "{}" || vercred["issuer"].to_s == "[]" return [nil, "invalid 'issuer'"] end if [:ts].nil? vercred["issuanceDate"] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") else vercred["issuanceDate"] = Time.at([:ts]).utc.iso8601 end if content["credentialSubject"].nil? vercred["credentialSubject"] = {"id": [:holder]}.merge(content) else vercred["credentialSubject"] = content["credentialSubject"] if vercred["credentialSubject"]["id"].nil? if [:holder].nil? return [nil, "missing 'id' (of holder) in 'credentialSubject'"] end vercred["credentialSubject"]["id"] = [:holder] end end if vercred["credentialSubject"].to_s == "" || vercred["credentialSubject"].to_s == "{}" || vercred["credentialSubject"].to_s == "[]" return [nil, "invalid 'credentialSubject'"] end case [:vc_type].to_s when 'Ed25519Signature2020' if content["proof"].nil? proof = {} proof["type"] = "Ed25519Signature2020" proof["verificationMethod"] = [:issuer].to_s + "#key-doc" proof["proofPurpose"] = "assertionMethod" id_vc = vercred.dup id_vc["proof"] = proof identifier_str = multi_hash(canonical(id_vc), ).first if [:vc_location].nil? vercred["identifier"] = identifier_str else vc_location = [:vc_location].to_s if !vc_location.start_with?("http") vc_location = "https://" + token_url end if !vc_location.end_with?('/') vc_location += '/' end if !vc_location.end_with?('credentials/') vc_location += 'credentials/' end vercred["id"] = vc_location + identifier_str end vercred, errmsg = vc_proof(vercred, proof, [:issuer_privateKey], ) if vercred.nil? return [nil, errmsg] end # proof["proofValue"] = sign(vercred["credentialSubject"].transform_keys(&:to_s).to_json_c14n, options[:issuer_privateKey], []).first else id_vc = vercred.dup content["proof"].delete("proofValue") id_vc["proof"] = content["proof"] identifier_str = multi_hash(canonical(id_vc), ).first if [:vc_location].nil? vercred["identifier"] = identifier_str else vercred["id"] = [:vc_location].to_s + identifier_str end vercred, errmsg = vc_proof(vercred, content["proof"], [:issuer_privateKey], ) if vercred.nil? return [nil, errmsg] end end if vercred["proof"].to_s == "" || vercred["proof"].to_s == "{}" || vercred["proof"].to_s == "[]" return [nil, "invalid 'proof'"] end when 'JsonWebSignature2020' jwt_vc = {} jwt_vc["vc"] = vercred.dup jwt_vc["exp"] = (Time.now + (3 * 30 * 24 * 60 * 60)).to_i jwt_vc["iss"] = vercred["issuer"] jwt_vc["nbf"] = if [:ts].nil? jwt_vc["nbf"] = Time.now.utc.to_i else jwt_vc["nbf"] = [:ts].to_i end identifier_str = multi_hash(canonical(vercred), ).first jwt_vc["jti"] = identifier_str jwt_vc["sub"] = [:holder] vercred = jwt_vc.dup else return [nil, "unsupported credential type '" + [:vc_type].to_s + "'"] end return [vercred, ""] end |
.create_vc_proof(content, options) ⇒ Object
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/oydid/vc.rb', line 280 def self.create_vc_proof(content, ) if content["id"].nil? content["id"] = [:issuer] end proof = {} proof["type"] = "Ed25519Signature2020" proof["verificationMethod"] = [:issuer].to_s proof["proofPurpose"] = "assertionMethod" content, errmsg = vc_proof(content, proof, [:issuer_privateKey], ) if content.nil? return [nil, errmsg] end # proof["proofValue"] = sign(content.to_json_c14n, options[:issuer_privateKey], []).first return [content["proof"], ""] end |
.create_vp(content, options) ⇒ Object
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/oydid/vc.rb', line 364 def self.create_vp(content, ) verpres = {} # set the context, which establishes the special terms used if !content["@context"].nil? verpres["@context"] = content["@context"].dup else verpres["@context"] = ["https://www.w3.org/ns/credentials/v2", ED25519_SECURITY_SUITE] end verpres["type"] = ["VerifiablePresentation"] verpres["verifiableCredential"] = [content].flatten proof = {} case [:vc_type].to_s when 'Ed25519Signature2020' proof['type'] = 'Ed25519Signature2020' if ![:ts].nil? proof["created"] = Time.at([:ts]).utc.strftime("%Y-%m-%dT%H:%M:%SZ") end proof["verificationMethod"] = [:holder].to_s proof["proofPurpose"] = "authentication" verpres, errmsg = vc_proof(verpres, proof, [:holder_privateKey], ) when 'JsonWebSignature2020' verpres["holder"] = [:holder].to_s proof['type'] = 'JsonWebSignature2020' if [:ts].nil? proof['created'] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") else proof["created"] = Time.at([:ts]).utc.strftime("%Y-%m-%dT%H:%M:%SZ") end proof["proofPurpose"] = "authentication" proof["verificationMethod"] = [:holder].to_s + '#key-doc' verpres["proof"] = proof [:issuer] = [:holder] [:issuer_privateKey] = [:holder_privateKey] jwt, msg = Oydid.jwt_from_vc(verpres, ) parts = jwt.split('.') detached_jws = "#{parts[0]}..#{parts[2]}" proof['jws'] = detached_jws verpres["proof"] = proof else return [nil, "unsupported credential type '" + [:vc_type].to_s + "'"] end # private_key = generate_private_key(options[:issuer_privateKey], "ed25519-priv", []).first # proof["proofValue"] = sign([content].flatten.to_json_c14n, options[:holder_privateKey], []).first # verpres["proof"] = proof # specify the identifier of the credential verpres["identifier"] = hash(canonical(verpres.to_json)) return [verpres, ""] end |
.dag2array(dag, log_array, index, result, options) ⇒ Object
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/oydid/log.rb', line 208 def self.dag2array(dag, log_array, index, result, ) if .transform_keys(&:to_s)["trace"] if [:silent].nil? || ![:silent] puts " vertex " + index.to_s + " at " + log_array[index]["ts"].to_s + " op: " + log_array[index]["op"].to_s + " doc: " + log_array[index]["doc"].to_s end end result << log_array[index] dag.vertices[index].successors.each do |s| # check if successor has predecessor that is not self (i.e. REVOKE with TERMINATE) s.predecessors.each do |p| if p[:id] != index if .transform_keys(&:to_s)["trace"] if [:silent].nil? || ![:silent] puts " vertex " + p[:id].to_s + " at " + log_array[p[:id]]["ts"].to_s + " op: " + log_array[p[:id]]["op"].to_s + " doc: " + log_array[p[:id]]["doc"].to_s end end result << log_array[p[:id]] end end unless s.predecessors.length < 2 dag2array(dag, log_array, s[:id], result, ) end unless dag.vertices[index].successors.count == 0 result.uniq end |
.dag2array_terminate(dag, log_array, index, result, options) ⇒ Object
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/oydid/log.rb', line 232 def self.dag2array_terminate(dag, log_array, index, result, ) if .transform_keys(&:to_s)["trace"] if [:silent].nil? || ![:silent] puts " vertex " + index.to_s + " at " + log_array[index]["ts"].to_s + " op: " + log_array[index]["op"].to_s + " doc: " + log_array[index]["doc"].to_s end end dag.vertices[index].predecessors.each do |p| if p[:id] != index if .transform_keys(&:to_s)["trace"] if [:silent].nil? || ![:silent] puts " vertex " + p[:id].to_s + " at " + log_array[p[:id]]["ts"].to_s + " op: " + log_array[p[:id]]["op"].to_s + " doc: " + log_array[p[:id]]["doc"].to_s end end result << log_array[p[:id]] end end unless dag.vertices[index].nil? result << log_array[index] result.uniq end |
.dag_did(logs, options) ⇒ Object
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 |
# File 'lib/oydid/log.rb', line 93 def self.dag_did(logs, ) dag = DAG.new dag_log = [] log_hash = [] # calculate hash values for each entry and build vertices i = 0 create_entries = 0 create_index = nil terminate_indices = [] logs.each do |el| case el["op"].to_i when 0 # TERMINATE terminate_indices << i when 2 # CREATE create_entries += 1 create_index = i end log_hash << Oydid.multi_hash(Oydid.canonical(el.slice("ts","op","doc","sig","previous")), LOG_HASH_OPTIONS).first dag_log << dag.add_vertex(id: i) i += 1 end unless logs.nil? if create_entries != 1 return [nil, nil, nil, "wrong number of CREATE entries (" + create_entries.to_s + ") in log" ] end if terminate_indices.length == 0 return [nil, nil, nil, "missing TERMINATE entries" ] end # create provisional edges between vertices i = 0 logs.each do |el| el["previous"].each do |p| position = log_hash.find_index(p) if !position.nil? dag.add_edge from: dag_log[position], to: dag_log[i] end end unless el["previous"] == [] i += 1 end unless logs.nil? # identify tangling TERMINATE entry i = 0 terminate_entries = 0 terminate_overall = 0 terminate_index = nil logs.each do |el| if el["op"].to_i == 0 # TERMINATE if dag.vertices[i].successors.length == 0 terminate_entries += 1 terminate_index = i end terminate_overall += 1 elsif el["op"].to_i == 1 # REVOKE # get terminate_index for revoked DIDs if dag.vertices[i].successors.length == 0 dag.vertices[i].predecessors.each do |l| if logs[l[:id]]["op"].to_i == 0 # TERMINATE terminate_index = l[:id] end end end end i += 1 end unless logs.nil? if terminate_entries != 1 && ![:log_complete] && ![:followAlsoKnownAs] if [:silent].nil? || ![:silent] return [nil, nil, nil, "cannot resolve DID" ] end end # create actual edges between vertices (but only use last terminate index for delegates) dag = DAG.new dag_log = [] log_hash = [] # calculate hash values for each entry and build vertices i = 0 create_entries = 0 create_index = nil terminate_indices = [] logs.each do |el| case el["op"].to_i when 0 # TERMINATE terminate_indices << i when 2 # CREATE create_entries += 1 create_index = i end log_hash << Oydid.multi_hash(Oydid.canonical(el.slice("ts","op","doc","sig","previous")), LOG_HASH_OPTIONS).first dag_log << dag.add_vertex(id: i) i += 1 end unless logs.nil? i = 0 logs.each do |el| el["previous"].each do |p| position = log_hash.find_index(p) if !position.nil? if logs[position]["op"].to_i == 5 # DELEGATE if i == terminate_index # only delegates in the last terminate index are relevant dag.add_edge from: dag_log[position], to: dag_log[i] end else dag.add_edge from: dag_log[position], to: dag_log[i] end end end unless el["previous"] == [] i += 1 end unless logs.nil? return [dag, create_index, terminate_index, ""] end |
.dag_update(currentDID, options) ⇒ Object
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 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 |
# File 'lib/oydid/log.rb', line 252 def self.dag_update(currentDID, ) i = 0 doc_location = [:doc_location].to_s initial_did = currentDID["did"].to_s.dup initial_did = initial_did.delete_prefix("did:oyd:") if initial_did.include?(LOCATION_PREFIX) tmp = initial_did.split(LOCATION_PREFIX) initial_did = tmp[0] doc_location = tmp[1] end if initial_did.include?(CGI.escape LOCATION_PREFIX) tmp = initial_did.split(CGI.escape LOCATION_PREFIX) initial_did = tmp[0] doc_location = tmp[1] end doc_location = doc_location.gsub("%3A",":") doc_location = doc_location.gsub("%2F%2F","//") current_public_doc_key = "" verification_output = false currentDID["log"].each do |el| case el["op"] when 2,3 # CREATE, UPDATE currentDID["doc_log_id"] = i doc_did = el["doc"] did_hash = doc_did.delete_prefix("did:oyd:") did_hash = did_hash.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first did10 = did_hash[0,10] doc = retrieve_document_raw(doc_did, did10 + ".doc", doc_location, {}) if doc.first.nil? currentDID["error"] = 2 msg = doc.last.to_s if msg == "" msg = "cannot retrieve " + doc_did.to_s end currentDID["message"] = msg return currentDID end doc = doc.first["doc"] if el["op"] == 2 # CREATE # signature for CREATE is optional (due to CMSM) if !el["sig"].nil? if !match_log_did?(el, doc) currentDID["error"] = 1 currentDID["message"] = "Signatures in log don't match" return currentDID end end end currentDID["did"] = doc_did currentDID["doc"] = doc # since hash is guaranteed during retrieve_document this check is not necessary # if hash(canonical(doc)) != did_hash # currentDID["error"] = 1 # currentDID["message"] = "DID identifier and DID document don't match" # if did_hash == initial_did # verification_output = true # end # if verification_output # currentDID["verification"] += "identifier: " + did_hash.to_s + "\n" # currentDID["verification"] += "⛔ does not match DID Document:" + "\n" # currentDID["verification"] += JSON.pretty_generate(doc) + "\n" # currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#calculate_hash)" + "\n\n" # end # return currentDID # end if did_hash == initial_did verification_output = true end if verification_output currentDID["verification"] += "identifier: " + did_hash.to_s + "\n" currentDID["verification"] += "✅ is hash of DID Document:" + "\n" currentDID["verification"] += JSON.pretty_generate(doc) + "\n" currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#calculate_hash)" + "\n\n" end current_public_doc_key = currentDID["doc"]["key"].split(":").first rescue "" when 0 # TERMINATE currentDID["termination_log_id"] = i doc_did = currentDID["did"] did_hash = doc_did.delete_prefix("did:oyd:") did_hash = did_hash.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first did10 = did_hash[0,10] doc = retrieve_document_raw(doc_did, did10 + ".doc", doc_location, {}) doc = doc.first["doc"] if !Oydid.match_log_did?(el, doc) currentDID["error"] = 1 currentDID["message"] = "Signatures in log don't match" return currentDID end # since it retrieves a DID that previously existed, this test is not necessary # if doc.first.nil? # currentDID["error"] = 2 # currentDID["message"] = doc.last.to_s # return currentDID # end term = doc["log"] log_location = term.split(LOCATION_PREFIX).last.split(CGI.escape LOCATION_PREFIX).last rescue "" if log_location.to_s == "" || log_location == term log_location = DEFAULT_LOCATION end term = term.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first = .dup el_hash = el["doc"].split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first [:digest] = get_digest(el_hash).first [:encode] = get_encoding(el_hash).first if multi_hash(canonical(el.slice("ts","op","doc","sig","previous")), ).first != term currentDID["error"] = 1 currentDID["message"] = "Log reference and record don't match" if verification_output currentDID["verification"] += "'log' reference in DID Document: " + term.to_s + "\n" currentDID["verification"] += "⛔ does not match TERMINATE log record:" + "\n" currentDID["verification"] += JSON.pretty_generate(el) + "\n" currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#calculate_hash)" + "\n\n" end return currentDID end if verification_output currentDID["verification"] += "'log' reference in DID Document: " + term.to_s + "\n" currentDID["verification"] += "✅ is hash of TERMINATE log record:" + "\n" currentDID["verification"] += JSON.pretty_generate(el) + "\n" currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#calculate_hash)" + "\n\n" end # check if there is a revocation entry revocation_record = {} revoc_term = el["doc"] revoc_term = revoc_term.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first revoc_term_found = false log_array, msg = retrieve_log(did_hash, did10 + ".log", log_location, ) log_array.each do |log_el| log_el_structure = log_el.dup if log_el["op"].to_i == 1 # TERMINATE log_el_structure.delete("previous") end if multi_hash(canonical(log_el_structure.slice("ts","op","doc","sig","previous")), ).first == revoc_term revoc_term_found = true revocation_record = log_el.dup if verification_output currentDID["verification"] += "'doc' reference in TERMINATE log record: " + revoc_term.to_s + "\n" currentDID["verification"] += "✅ is hash of REVOCATION log record (without 'previous' attribute):" + "\n" currentDID["verification"] += JSON.pretty_generate(log_el) + "\n" currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#calculate_hash)" + "\n\n" end break end end unless log_array.nil? # this should actually be covered by retrieve_log in the block above # (actually I wasn't able to craft a test case covering this part...) # if !options.transform_keys(&:to_s)["log_location"].nil? # log_array, msg = retrieve_log(revoc_term, did10 + ".log", options.transform_keys(&:to_s)["log_location"], options) # log_array.each do |log_el| # if log_el["op"] == 1 # REVOKE # log_el_structure = log_el.delete("previous") # else # log_el_structure = log_el # end # if hash(canonical(log_el_structure)) == revoc_term # revoc_term_found = true # revocation_record = log_el.dup # if verification_output # currentDID["verification"] += "'doc' reference in TERMINATE log record: " + revoc_term.to_s + "\n" # currentDID["verification"] += "✅ is hash of REVOCATION log record (without 'previous' attribute):" + "\n" # currentDID["verification"] += JSON.pretty_generate(log_el) + "\n" # currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#calculate_hash)" + "\n\n" # end # break # end # end # end if revoc_term_found update_term_found = false log_array.each do |log_el| if log_el["op"].to_i == 3 if log_el["previous"].include?(multi_hash(canonical(revocation_record), LOG_HASH_OPTIONS).first) update_term_found = true = log_el["doc"].to_s signature = log_el["sig"] # public_key = current_public_doc_key.to_s extend_currentDID = currentDID.dup extend_currentDID["log"] = extend_currentDID["full_log"] # !!!TODO: check for delegates only at certain point in time pubKeys, msg = Oydid.getDelegatedPubKeysFromFullDidDocument(extend_currentDID, "doc") signature_verification = false used_pubkey = "" pubKeys.each do |key| if Oydid.verify(, signature, key).first signature_verification = true used_pubkey = key break end end if signature_verification if verification_output currentDID["verification"] += "found UPDATE log record:" + "\n" currentDID["verification"] += JSON.pretty_generate(log_el) + "\n" currentDID["verification"] += "✅ public key: " + used_pubkey.to_s + "\n" currentDID["verification"] += "verifies 'doc' reference of new DID Document: " + log_el["doc"].to_s + "\n" currentDID["verification"] += log_el["sig"].to_s + "\n" currentDID["verification"] += "of next DID Document (Details: https://ownyourdata.github.io/oydid/#verify_signature)" + "\n" next_doc_did = log_el["doc"].to_s next_doc_location = doc_location next_did_hash = next_doc_did.delete_prefix("did:oyd:") next_did_hash = next_did_hash.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first next_did10 = next_did_hash[0,10] next_doc = retrieve_document_raw(next_doc_did, next_did10 + ".doc", next_doc_location, {}) if next_doc.first.nil? currentDID["error"] = 2 currentDID["message"] = next_doc.last return currentDID end next_doc = next_doc.first["doc"] if pubKeys.include?(next_doc["key"].split(":").first) currentDID["verification"] += "⚠️ no key rotation in updated DID Document" + "\n" end currentDID["verification"] += "\n" end else currentDID["error"] = 1 currentDID["message"] = "Signature does not match" if verification_output new_doc_did = log_el["doc"].to_s new_doc_location = doc_location new_did_hash = new_doc_did.delete_prefix("did:oyd:") new_did_hash = new_did_hash.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first new_did10 = new_did_hash[0,10] new_doc = retrieve_document(new_doc_did, new_did10 + ".doc", new_doc_location, {}).first currentDID["verification"] += "found UPDATE log record:" + "\n" currentDID["verification"] += JSON.pretty_generate(log_el) + "\n" currentDID["verification"] += "⛔ none of available public keys (" + pubKeys.join(", ") + ")\n" currentDID["verification"] += "does not verify 'doc' reference of new DID Document: " + log_el["doc"].to_s + "\n" currentDID["verification"] += log_el["sig"].to_s + "\n" currentDID["verification"] += "next DID Document (Details: https://ownyourdata.github.io/oydid/#verify_signature)" + "\n" currentDID["verification"] += JSON.pretty_generate(new_doc) + "\n\n" end return currentDID end break end end end else if verification_output currentDID["verification"] += "Revocation reference in log record: " + revoc_term.to_s + "\n" currentDID["verification"] += "✅ cannot find revocation record searching at" + "\n" currentDID["verification"] += "- " + log_location + "\n" if !.transform_keys(&:to_s)["log_location"].nil? currentDID["verification"] += "- " + .transform_keys(&:to_s)["log_location"].to_s + "\n" end currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#retrieve_log)" + "\n\n" end break end when 1 # revocation log entry # handle DID Rotation if (i == (currentDID["log"].length-1)) if [:followAlsoKnownAs] current_doc = currentDID["doc"] if current_doc["doc"].transform_keys(&:to_s).has_key?("alsoKnownAs") rotate_DID = current_doc["doc"].transform_keys(&:to_s)["alsoKnownAs"] if rotate_DID.start_with?("did:") rotate_DID_method = rotate_DID.split(":").take(2).join(":") did_orig = currentDID["did"] if !did_orig.start_with?("did:oyd") did_orig = "did:oyd:" + did_orig end case rotate_DID_method when "did:ebsi", "did:cheqd" public_resolver = DEFAULT_PUBLIC_RESOLVER rotate_DID_Document = HTTParty.get(public_resolver + rotate_DID) rotate_ddoc = JSON.parse(rotate_DID_Document.parsed_response) rotate_ddoc = rotate_ddoc.except("didDocumentMetadata", "didResolutionMetadata") # checks # 1) is original DID revoked -> fulfilled, otherwise we would not be in this branch # 2) das new DID reference back original DID currentDID["did"] = rotate_DID currentDID["doc"]["doc"] = rotate_ddoc if verification_output currentDID["verification"] += "DID rotation to: " + rotate_DID.to_s + "\n" currentDID["verification"] += "✅ original DID (" + did_orig + ") revoked and referenced in alsoKnownAs\n" currentDID["verification"] += "(Details: https://ownyourdata.github.io/oydid/#did_rotation)" + "\n\n" end when "did:oyd" puts "try to resolve did:oyd with our own resolver" puts "add verification text" else # do nothing: DID Rotation is not supported for this DID method yet end end end end end when 5 # DELEGATE # do nothing else currentDID["error"] = 2 currentDID["message"] = "FATAL ERROR: op code '" + el["op"].to_s + "' not implemented" return currentDID end i += 1 end unless currentDID["log"].nil? return currentDID end |
.dcpm(payload, options) ⇒ Object
DIDComm Plain Message ———————
7 8 9 10 11 12 13 14 15 16 17 18 19 |
# File 'lib/oydid/didcomm.rb', line 7 def self.dcpm(payload, ) dcDoc = {} dcDoc["id"] = SecureRandom.random_number(10e14).to_i dcDoc["type"] = [:didcomm_type] if ![:didcomm_from_did].nil? dcDoc["from"] = [:didcomm_from_did] end dcDoc["to"] = [[:didcomm_to_did]] dcDoc["created_time"] = Time.now.utc.to_i dcDoc["body"] = payload return [dcDoc, ""] end |
.dcsm(payload, private_key_encoded, options) ⇒ Object
DIDComm Signed Message ——————–
22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/oydid/didcomm.rb', line 22 def self.dcsm(payload, private_key_encoded, ) error = "" code, length, digest = multi_decode(private_key_encoded).first.unpack('SCa*') case Multicodecs[code].name when 'ed25519-priv' private_key = RbNaCl::Signatures::Ed25519::SigningKey.new(digest) token = JWT.encode payload, private_key, 'ED25519', { typ: 'JWM', kid: [:sign_did].to_s, alg: 'ED25519' } else token = nil error = "unsupported key codec" end return [token, error] end |
.dcsm_verify(token, options) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/oydid/didcomm.rb', line 36 def self.dcsm_verify(token, ) error = "" decoded_payload = JWT.decode token, nil, false pubkey_did = decoded_payload.last["kid"] result, msg = Oydid.read(pubkey_did, ) public_key_encoded = Oydid.w3c(result, )["authentication"].first["publicKeyMultibase"] begin code, length, digest = multi_decode(public_key_encoded).first.unpack('CCa*') case Multicodecs[code].name when 'ed25519-pub' public_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(digest) payload = JWT.decode token.to_s, public_key, true, { algorithm: 'ED25519' } else payload = nil error = "unsupported key codec" end return [payload, error] rescue return [nil, "verification failed"] end end |
.decode_private_key(key_encoded, options = {}) ⇒ Object
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 |
# File 'lib/oydid/basic.rb', line 802 def self.decode_private_key(key_encoded, = {}) code, length, digest = multi_decode(key_encoded).first.unpack('SCa*') case Multicodecs[code].name when 'ed25519-priv' private_key = Ed25519::SigningKey.new(digest).to_bytes when 'p256-priv' group = OpenSSL::PKey::EC::Group.new('prime256v1') pub_key = group.generator.mul(OpenSSL::BN.new(digest, 2)) pub_oct = pub_key.to_bn.to_s(2) parameters = OpenSSL::ASN1::ObjectId("prime256v1") parameters.tag = 0 parameters.tagging = :EXPLICIT parameters.tag_class = :CONTEXT_SPECIFIC public_key_bitstring = OpenSSL::ASN1::BitString(pub_oct) public_key_bitstring.tag = 1 public_key_bitstring.tagging = :EXPLICIT public_key_bitstring.tag_class = :CONTEXT_SPECIFIC ec_private_key_asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(1), OpenSSL::ASN1::OctetString(digest), parameters, public_key_bitstring ]) private_key = OpenSSL::PKey.read(ec_private_key_asn1.to_der) else return [nil, "unsupported key codec"] end return [private_key, nil] end |
.decode_public_key(key_encoded) ⇒ Object
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 |
# File 'lib/oydid/basic.rb', line 837 def self.decode_public_key(key_encoded) begin pubkey = multi_decode(key_encoded).first if pubkey.bytes.length == 34 code = pubkey.bytes.first digest = pubkey[-32..] else if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT')) code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub) # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT') else code = pubkey.unpack('n').first end digest = pubkey[-1*(pubkey.bytes.length-2)..] end case Multicodecs[code].name when 'ed25519-pub' verify_key = Ed25519::VerifyKey.new(digest) return [verify_key, ""] when 'x25519-pub' pub_key = RbNaCl::PublicKey.new(digest) return [pub_key, ""] when 'p256-pub' asn1_public_key = OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'), OpenSSL::ASN1::ObjectId.new('prime256v1') ]), OpenSSL::ASN1::BitString.new(digest) ]) pub_key = OpenSSL::PKey::EC.new(asn1_public_key.to_der) return [pub_key, ""] else return [nil, "unsupported key codec"] end rescue return [nil, "unknown key codec"] end end |
.decrypt(message, private_key, options = {}) ⇒ Object
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 |
# File 'lib/oydid/basic.rb', line 577 def self.decrypt(, private_key, = {}) begin key_type = get_keytype(private_key) case key_type when 'ed25519-priv' cipher = [JSON.parse()["value"]].pack('H*') nonce = [JSON.parse()["nonce"]].pack('H*') code, length, digest = multi_decode(private_key).first.unpack('SCa*') if length != 32 # support only encoded keys digest = Multibases.unpack(private_key).decode.to_s('ASCII-8BIT') code = Multicodecs["ed25519-priv"].code end privKey = RbNaCl::PrivateKey.new(digest) authHash = RbNaCl::Hash.sha256('auth'.dup.force_encoding('ASCII-8BIT')) authKey = RbNaCl::PrivateKey.new(authHash).public_key box = RbNaCl::Box.new(authKey, privKey) retVal = box.decrypt(nonce, cipher) return [retVal, ""] when 'p256-priv' private_key = decode_private_key(private_key).first head_b64, _, iv_b64, cipher_b64, tag_b64 = .split('.') decoded_header = JSON.parse(Base64.urlsafe_decode64(head_b64)) epk_pub = Oydid.decode_public_key(Oydid.public_key_from_jwk(decoded_header['epk']).first).first shared_secret2 = private_key.dh_compute_key(epk_pub.public_key) # ECDH (Gegenseite) cek2 = OpenSSL::Digest::SHA256.digest(shared_secret2) decipher = OpenSSL::Cipher.new('aes-256-gcm') decipher.decrypt decipher.key = cek2 decipher.iv = Base64.urlsafe_decode64(iv_b64) decipher.auth_tag = Base64.urlsafe_decode64(tag_b64) decipher.auth_data = '' plaintext = decipher.update(Base64.urlsafe_decode64(cipher_b64)) + decipher.final return [plaintext, nil] else return [nil, "unsupported key codec"] end rescue return [nil, "decryption failed"] end end |
.decryptJWE(message, private_key, options = {}) ⇒ Object
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 |
# File 'lib/oydid/basic.rb', line 716 def self.decryptJWE(, private_key, = {}) # JWE parsing jwe_full = JSON.parse() snd_pub_enc = jwe_full["recipients"].first["header"]["epk"]["x"] snd_key_enc = jwe_full["recipients"].first["encrypted_key"] snd_nnc_enc = jwe_full["recipients"].first["header"]["iv"] snd_tag_enc = jwe_full["recipients"].first["header"]["tag"] cnt_cip_enc = jwe_full["ciphertext"] cnt_tag_enc = jwe_full["tag"] cnt_nnc_enc = jwe_full["iv"] cnt_aad_enc = jwe_full["protected"] recipient_alg = jwe_full["recipients"].first["header"]["alg"] snd_pub = Base64.urlsafe_decode64(snd_pub_enc) snd_nnc = Base64.urlsafe_decode64(snd_nnc_enc) snd_key = Base64.urlsafe_decode64(snd_key_enc) snd_tag = Base64.urlsafe_decode64(snd_tag_enc) cnt_nnc = Base64.urlsafe_decode64(cnt_nnc_enc) cnt_cip = Base64.urlsafe_decode64(cnt_cip_enc) cnt_tag = Base64.urlsafe_decode64(cnt_tag_enc) cnt_aad = Base64.urlsafe_decode64(cnt_aad_enc) # Key Decryption code, length, digest = multi_decode(private_key).first.unpack('SCa*') buffer = RbNaCl::Util.zeros(RbNaCl::Boxes::Curve25519XSalsa20Poly1305::PublicKey::BYTES) RbNaCl::Signatures::Ed25519::SigningKey.crypto_sign_ed25519_sk_to_curve25519(buffer, digest) shared_secret = RbNaCl::GroupElement.new(snd_pub).mult(buffer) jwe_const = [0, 0, 0, 1] + shared_secret.to_bytes.unpack('C*') + [0,0,0,15] + recipient_alg.bytes + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] kek = RbNaCl::Hash.sha256(jwe_const.pack('C*')) snd_aead = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(kek) cnt_key = snd_aead.decrypt(snd_nnc, snd_key+snd_tag, nil) # Content Decryption cnt_aead = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(cnt_key) cnt_dec = cnt_aead.decrypt(cnt_nnc, cnt_cip+cnt_tag, cnt_aad) return [cnt_dec, ""] end |
.delegate(did, options) ⇒ Object
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 |
# File 'lib/oydid.rb', line 1041 def self.delegate(did, ) # check location location = [:doc_location] if location.to_s == "" location = DEFAULT_LOCATION end if did.include?(LOCATION_PREFIX) tmp = did.split(LOCATION_PREFIX) did = tmp[0] location = tmp[1] end if did.include?(CGI.escape LOCATION_PREFIX) tmp = did.split(CGI.escape LOCATION_PREFIX) did = tmp[0] location = tmp[1] end [:doc_location] = location [:log_location] = location if [:ts].nil? ts = Time.now.utc.to_i else ts = [:ts] end # build log record log = {} log["ts"] = ts log["op"] = 5 # DELEGATE pwd = false doc_privateKey, msg = getPrivateKey([:doc_enc], [:doc_pwd], [:doc_key], "", ) rev_privateKey, msg = getPrivateKey([:rev_enc], [:rev_pwd], [:rev_key], "", ) if !doc_privateKey.nil? pwd="doc" privateKey = doc_privateKey end if !rev_privateKey.nil? pwd="rev" privateKey = rev_privateKey end if !pwd || privateKey.to_s == "" return [nil, "missing or invalid delegate key"] end log["doc"] = pwd + ":" + public_key(privateKey, ).first.to_s log["sig"] = sign(privateKey, privateKey, ).first log["previous"] = [did] # DID in previous cannot be resolved in the DAG but guarantees unique log hash # revocation delegate keys need to specify a public key for encrypting the revocation record if pwd == "rev" publicEncryptionKey, msg = public_key(privateKey, {}, 'x25519-pub') log["encryption-key"] = publicEncryptionKey end log_hash, msg = write_log(did, log, ) if log_hash.nil? return [nil, msg] else return [{"log": log_hash}, ""] end end |
.encrypt(message, public_key, options = {}) ⇒ Object
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
# File 'lib/oydid/basic.rb', line 507 def self.encrypt(, public_key, = {}) begin if [:key_type].to_s == '' pk = multi_decode(public_key).first code = pk.bytes.first digest = pk[-32..] key_type = Multicodecs[code].name rescue '' else key_type = [:key_type] end case key_type when 'x25519-pub' pubKey = RbNaCl::PublicKey.new(digest) authHash = RbNaCl::Hash.sha256('auth'.dup.force_encoding('ASCII-8BIT')) authKey = RbNaCl::PrivateKey.new(authHash) box = RbNaCl::Box.new(pubKey, authKey) nonce = RbNaCl::Random.random_bytes(box.nonce_bytes) msg = .force_encoding('ASCII-8BIT') cipher = box.encrypt(nonce, msg) return [ { value: cipher.unpack('H*')[0], nonce: nonce.unpack('H*')[0] }, "" ] when 'p256-pub' recipient_pub_key = decode_public_key(public_key).first # a) Ephemeren Sender-Key erzeugen + ECDH ephemeral_key = OpenSSL::PKey::EC.generate('prime256v1') shared_secret = ephemeral_key.dh_compute_key(recipient_pub_key.public_key) # b) Ableitung Content-Encryption-Key (CEK) – simple HKDF-Light cek = OpenSSL::Digest::SHA256.digest(shared_secret) # c) Symmetrische Verschlüsselung (AES-256-GCM) cipher = OpenSSL::Cipher.new('aes-256-gcm') cipher.encrypt cipher.key = cek iv = OpenSSL::Random.random_bytes(12) # 96-bit IV wie empfohlen cipher.iv = iv cipher.auth_data = '' # kein AAD ciphertext = cipher.update() + cipher.final tag = cipher.auth_tag # d) JWE-Header (nur die nötigen Felder) header = { alg: 'ECDH-ES', enc: 'A256GCM', kid: [:kid], epk: JWT::JWK.new(ephemeral_key).export.slice(:kty, :crv, :x, :y) } # e) JWE-Compact-Serialisierung (EncryptedKey leer bei ECDH-ES) jwe_compact = [ Base64.urlsafe_encode64(header.to_json).delete("="), '', Base64.urlsafe_encode64(iv).delete("="), Base64.urlsafe_encode64(ciphertext).delete("="), Base64.urlsafe_encode64(tag).delete("="), ].join('.') return [jwe_compact, nil] else return [nil, "unsupported key codec"] end rescue return [nil, "encryption failed"] end end |
.encryptJWE(message, public_key, options = {}) ⇒ Object
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 |
# File 'lib/oydid/basic.rb', line 639 def self.encryptJWE(, public_key, = {}) jwe_header = {"enc":"XC20P"} recipient_alg = 'ECDH-ES+XC20PKW' # Content Encryption --- # random nonce for XChaCha20-Poly1305: uses a 192-bit nonce (24 bytes) cnt_nnc = RbNaCl::Random.random_bytes(RbNaCl::AEAD::XChaCha20Poly1305IETF.nonce_bytes) # random key for XChaCha20-Poly1305: uses a 256-bit key (32 bytes) cnt_key = RbNaCl::Random.random_bytes(RbNaCl::AEAD::XChaCha20Poly1305IETF.key_bytes) # addtional data cnt_aad = jwe_header.to_json # setup XChaCha20-Poly1305 for Authenticated Encryption with Associated Data (AEAD) cnt_aead = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(cnt_key) # encrypt msg_enc = cnt_aead.encrypt(cnt_nnc, , cnt_aad) cnt_enc = msg_enc[0...-cnt_aead.tag_bytes] cnt_tag = msg_enc[-cnt_aead.tag_bytes .. -1] # Key Encryption --- snd_prv = RbNaCl::PrivateKey.generate code, length, digest = multi_decode(public_key).first.unpack('CCa*') buffer = RbNaCl::Util.zeros(RbNaCl::Boxes::Curve25519XSalsa20Poly1305::PublicKey::BYTES) RbNaCl::Signatures::Ed25519::VerifyKey.crypto_sign_ed25519_pk_to_curve25519(buffer, digest) shared_secret = RbNaCl::GroupElement.new(buffer).mult(snd_prv.to_bytes) jwe_const = [0, 0, 0, 1] + shared_secret.to_bytes.unpack('C*') + [0,0,0,15] + recipient_alg.bytes + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] kek = RbNaCl::Hash.sha256(jwe_const.pack('C*')) snd_nnc = RbNaCl::Random.random_bytes(RbNaCl::AEAD::XChaCha20Poly1305IETF.nonce_bytes) snd_aead = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(kek) snd_enc = snd_aead.encrypt(snd_nnc, cnt_key, nil) snd_key = snd_enc[0...-snd_aead.tag_bytes] snd_aut = snd_enc[-snd_aead.tag_bytes .. -1] # create JWE --- jwe_protected = Base64.urlsafe_encode64(jwe_header.to_json).delete("=") jwe_encrypted_key = Base64.urlsafe_encode64(snd_key).delete("=") jwe_init_vector = Base64.urlsafe_encode64(cnt_nnc).delete("=") jwe_cipher_text = Base64.urlsafe_encode64(cnt_enc).delete("=") jwe_auth_tag = Base64.urlsafe_encode64(cnt_tag).delete("=") rcp_nnc_enc = Base64.urlsafe_encode64(snd_nnc).delete("=") rcp_tag_enc = Base64.urlsafe_encode64(snd_aut).delete("=") jwe_full = { protected: jwe_protected, iv: jwe_init_vector, ciphertext: jwe_cipher_text, tag: jwe_auth_tag, recipients: [ { encrypted_key: jwe_encrypted_key, header: { alg: recipient_alg, iv: rcp_nnc_enc, tag: rcp_tag_enc, epk: { kty: "OKP", crv: "X25519", x: Base64.urlsafe_encode64(snd_prv.public_key.to_bytes).delete("=") } } } ] } jwe = jwe_protected jwe += "." + jwe_encrypted_key jwe += "." + jwe_init_vector jwe += "." + jwe_cipher_text jwe += "." + jwe_auth_tag return [jwe_full, ""] end |
.fromW3C(didDocument, options) ⇒ Object
1417 1418 1419 1420 1421 1422 1423 |
# File 'lib/oydid.rb', line 1417 def self.fromW3C(didDocument, ) didDocument = didDocument.transform_keys(&:to_s) if didDocument["@context"] == ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"] didDocument.delete("@context") end didDocument end |
.generate_base(content, did, mode, options) ⇒ Object
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 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 |
# File 'lib/oydid.rb', line 182 def self.generate_base(content, did, mode, ) # input validation did_doc = JSON.parse(content.to_json) rescue nil if did_doc.nil? if !content.nil? return [nil, nil, nil, "invalid payload"] end end did_old = nil log_old = nil prev_hash = [] revoc_log = nil doc_location = [:location] if [:ts].nil? ts = Time.now.utc.to_i else ts = [:ts] end [:cmsm2] = false if [:cmsm] if did_doc["key"].nil? return [nil, nil, nil, "CMSM requires public key"] end cmsm_keys = did_doc["key"].split(':') did_doc.delete("key") if did_doc == {} did_doc = nil end if cmsm_keys.count == 1 publicKey = cmsm_keys.first revocationKey, msg = generate_private_key("", [:key_type]+'-priv', ) pubRevoKey = public_key(revocationKey, ).first else return [nil, nil, nil, "CMSM with multiple keys is not yet supported"] end # check if information for provided key already exists payload, msg = check_cmsm(publicKey, ) if !payload.nil? && !did_doc.nil? && !did_doc["opt"].nil? if payload.is_a?(String) payload = JSON.parse(payload) rescue nil end if payload.nil? return [nil, nil, nil, "invalid persisted data in CMSM flow"] end did_doc = JSON.parse(did_doc.to_json) if did_doc["opt"].nil? if [:sig].nil? return [nil, nil, nil, "1missing signature in CMSM flow (sig)"] end l2_sig = [:sig] else if did_doc["opt"]["sig"].nil? return [nil, nil, nil, "2missing signature in CMSM flow (sig)"] end l2_sig = did_doc["opt"]["sig"] end [:cmsm2] = true privateKey = nil revocationKey = payload["revocationKey"] did_doc = payload["did_doc"] did_key = payload["did_key"] l2_doc = payload["l2_doc"] r1 = payload["r1"] end else # key management tmp_did_hash = did.delete_prefix("did:oyd:") rescue "" tmp_did10 = tmp_did_hash[0,10] + "_private_key.enc" rescue "" privateKey, msg = getPrivateKey([:doc_enc], [:doc_pwd], [:doc_key], tmp_did10, ) if privateKey.nil? privateKey, msg = generate_private_key("", [:key_type]+'-priv', ) if privateKey.nil? return [nil, nil, nil, "private document key not found"] end end tmp_did10 = tmp_did_hash[0,10] + "_revocation_key.enc" rescue "" revocationKey, msg = getPrivateKey([:rev_enc], [:rev_pwd], [:rev_key], tmp_did10, ) if revocationKey.nil? revocationKey, msg = generate_private_key("", [:key_type]+'-priv', ) if revocationKey.nil? return [nil, nil, nil, "private revocation key not found"] end end end # mode-specific handling if mode == "create" || mode == "clone" operation_mode = 2 # CREATE else # mode == "update" => read information first operation_mode = 3 # UPDATE did_info, msg = read(did, ) if did_info.nil? return [nil, nil, nil, "cannot resolve DID (on updating DID)"] end if did_info["error"] != 0 return [nil, nil, nil, did_info["message"].to_s] end did = did_info["did"] did_hash = did.delete_prefix("did:oyd:") did10 = did_hash[0,10] if doc_location.to_s == "" if did_hash.include?(LOCATION_PREFIX) hash_split = did_hash.split(LOCATION_PREFIX) did_hash = hash_split[0] doc_location = hash_split[1] end end did_old = did.dup did10_old = did10.dup log_old = did_info["log"] # check if provided old keys are native DID keys or delegates ================== tmp_old_doc_did10 = did10_old + "_private_key.enc" rescue "" old_privateKey, msg = getPrivateKey([:old_doc_enc], [:old_doc_pwd], [:old_doc_key], tmp_old_doc_did10, ) tmp_old_rev_did10 = did10_old + "_revocation_key.enc" rescue "" old_revocationKey, msg = getPrivateKey([:old_rev_enc], [:old_rev_pwd], [:old_rev_key], tmp_old_rev_did10, ) old_publicDocKey = public_key(old_privateKey, {}).first old_publicRevKey = public_key(old_revocationKey, {}).first old_did_key = old_publicDocKey + ":" + old_publicRevKey # compare old keys with existing DID Document & generate revocation record if old_did_key.to_s == did_info["doc"]["key"].to_s # provided keys are native DID keys ------------------ # re-build revocation document old_did_doc = did_info["doc"]["doc"] old_ts = did_info["log"].last["ts"] old_subDid = {"doc": old_did_doc, "key": old_did_key}.to_json old_subDidHash = multi_hash(canonical(old_subDid), LOG_HASH_OPTIONS).first old_signedSubDidHash = sign(old_subDidHash, old_revocationKey, LOG_HASH_OPTIONS).first revocationLog = { "ts": old_ts, "op": 1, # REVOKE "doc": old_subDidHash, "sig": old_signedSubDidHash }.transform_keys(&:to_s).to_json else # proviced keys are either delegates or invalid ------ # * check validity of key-doc delegate pubKeys, msg = getDelegatedPubKeysFromDID(did, "doc") if !pubKeys.include?(old_publicDocKey) return [nil, nil, nil, "invalid or missing old private document key"] end # * check validity of key-rev delegate pubKeys, msg = getDelegatedPubKeysFromDID(did, "rev") if !pubKeys.include?(old_publicRevKey) return [nil, nil, nil, "invalid or missing old private revocation key"] end # retrieve revocationLog from previous in key-rev delegate revoc_log = nil log_old.each do |item| if !item["encrypted-revocation-log"].nil? revoc_log = item["encrypted-revocation-log"] end end if revoc_log.nil? return [nil, nil, nil, "cannot retrieve revocation log"] end revocationLog, msg = decrypt(revoc_log.to_json, old_revocationKey.to_s) if revocationLog.nil? return [nil, nil, nil, "cannot decrypt revocation log entry: " + msg] end end # compare old keys with existing DID Document revoc_log = JSON.parse(revocationLog) revoc_log["previous"] = [ multi_hash(canonical(log_old[did_info["doc_log_id"].to_i]), LOG_HASH_OPTIONS).first, multi_hash(canonical(log_old[did_info["termination_log_id"].to_i]), LOG_HASH_OPTIONS).first ] prev_hash = [multi_hash(canonical(revoc_log), LOG_HASH_OPTIONS).first] end if ![:cmsm2] if ![:cmsm] publicKey = public_key(privateKey, ).first pubRevoKey = public_key(revocationKey, ).first end did_key = publicKey + ":" + pubRevoKey if [:keyAgreement] if did_doc.nil? did_doc = {} end did_doc[:keyAgreement] = ["#key-doc"] did_doc = did_doc.transform_keys(&:to_s) end if [:x25519_keyAgreement] if did_doc.nil? did_doc = {} end did_doc[:keyAgreement] = [{ "id": "#key-doc-x25519", "type": "X25519KeyAgreementKey2019", "publicKeyMultibase": public_key(privateKey, , 'x25519-pub').first }] did_doc = did_doc.transform_keys(&:to_s) end if [:authentication] if did_doc.nil? did_doc = {} end did_doc[:authentication] = ["#key-doc"] did_doc = did_doc.transform_keys(&:to_s) end # build new revocation document subDid = {"doc": did_doc, "key": did_key}.to_json retVal = multi_hash(canonical(subDid), LOG_HASH_OPTIONS) if retVal.first.nil? return [nil, nil, nil, retVal.last] end subDidHash = retVal.first signedSubDidHash = sign(subDidHash, revocationKey, LOG_HASH_OPTIONS).first r1 = { "ts": ts, "op": 1, # REVOKE "doc": subDidHash, "sig": signedSubDidHash }.transform_keys(&:to_s) # build termination log entry l2_doc = multi_hash(canonical(r1), LOG_HASH_OPTIONS).first if !doc_location.nil? l2_doc += LOCATION_PREFIX + doc_location.to_s end if [:cmsm] # persist data payload = { revocationKey: revocationKey, did_doc: did_doc, did_key: did_key, l2_doc: l2_doc, r1: r1 } success, msg = persist_cmsm(publicKey, payload, ) cmsm_doc = { cmsm: true, pk: publicKey, sign: l2_doc } return [cmsm_doc, nil, r1, "cmsm"] end l2_sig = sign(l2_doc, privateKey, ).first end if [:confirm_logs].nil? previous_array = [] else previous_array = [:confirm_logs] end l2 = { "ts": ts, "op": 0, # TERMINATE "doc": l2_doc, "sig": l2_sig, "previous": previous_array }.transform_keys(&:to_s) # build actual DID document log_str = multi_hash(canonical(l2), LOG_HASH_OPTIONS).first if !doc_location.nil? log_str += LOCATION_PREFIX + doc_location.to_s end didDocument = { "doc": did_doc, "key": did_key, "log": log_str }.transform_keys(&:to_s) # create DID l1_doc = multi_hash(canonical(didDocument), ).first if !doc_location.nil? l1_doc += LOCATION_PREFIX + doc_location.to_s end did = "did:oyd:" + l1_doc did10 = l1_doc[0,10] if mode == "clone" # create log entry for source DID new_log = { "ts": ts, "op": 4, # CLONE "doc": l1_doc, "sig": sign(l1_doc, privateKey, ).first, "previous": [[:previous_clone].to_s] } retVal = HTTParty.post([:source_location] + "/log/" + [:source_did], headers: { 'Content-Type' => 'application/json' }, body: {"log": new_log}.to_json ) prev_hash = [multi_hash(canonical(new_log), LOG_HASH_OPTIONS).first] end # build creation log entry log_revoke_encrypted_array = nil l1_sig = nil if operation_mode == 3 # UPDATE if ![:cmsm] l1_sig = sign(l1_doc, old_privateKey, ).first end l1 = { "ts": ts, "op": operation_mode, # UPDATE "doc": l1_doc, "sig": l1_sig, "previous": prev_hash }.transform_keys(&:to_s) [:confirm_logs].each do |el| # read each log entry to check if it is a revocation delegation log_item, msg = retrieve_log_item(el, doc_location, ) if log_item["doc"][0..3] == "rev:" cipher, msg = encrypt(r1.to_json, log_item["encryption-key"], {}) cipher[:log] = el.to_s if log_revoke_encrypted_array.nil? log_revoke_encrypted_array = [cipher] else log_revoke_encrypted_array << cipher end end end unless [:confirm_logs].nil? else if ![:cmsm] l1_sig = sign(l1_doc, privateKey, ).first end l1 = { "ts": ts, "op": operation_mode, # CREATE "doc": l1_doc, "sig": l1_sig, "previous": prev_hash }.transform_keys(&:to_s) end # did, didDocument, revoc_log, l1, l2, r1, privateKey, revocationKey, did_old, log_old, msg = Oydid.generate_base(content, "", "create", options) # did_doc = [did, didDocument, did_old] # did_log = [revoc_log, l1, l2, r1, log_old] # did_key = [privateKey, revocationKey] did_doc = { :did => did, :didDocument => didDocument, :did_old => did_old } did_log = { :revoc_log => revoc_log, :l1 => l1, :l2 => l2, :r1 => r1, :log_old => log_old } if !log_revoke_encrypted_array.nil? did_log[:r1_encrypted] = log_revoke_encrypted_array end did_key = { :privateKey => privateKey, :revocationKey => revocationKey } return [did_doc, did_key, did_log, ""] # return [did, didDocument, revoc_log, l1, l2, r1, privateKey, revocationKey, did_old, log_old, ""] end |
.generate_private_key(input, method = "ed25519-priv", options = {}) ⇒ Object
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 |
# File 'lib/oydid/basic.rb', line 169 def self.generate_private_key(input, method = "ed25519-priv", = {}) begin omc = Multicodecs[method].code rescue return [nil, "unknown key codec"] end case Multicodecs[method].name when 'ed25519-priv' if input == "" raw_key = Ed25519::SigningKey.generate else raw_key = Ed25519::SigningKey.new(RbNaCl::Hash.sha256(input)) end raw_key = raw_key.to_bytes when 'p256-priv' key = OpenSSL::PKey::EC.new('prime256v1') if input == "" key = OpenSSL::PKey::EC.generate('prime256v1') else # input for p256-priv requires valid base64 encoded private key begin key = OpenSSL::PKey.read Base64.decode64(input) rescue return [nil, "invalid input"] end end raw_key = key.private_key.to_s(2) else return [nil, "unsupported key codec"] end # only encoding without specifying key-type # encoded = multi_encode(raw_key, options) # encoding with specyfying key-type length = raw_key.bytesize encoded = multi_encode([omc, length, raw_key].pack("SCa#{length}"), ) if encoded.first.nil? return [nil, encoded.last] else return [encoded.first, ""] end end |
.get_digest(message) ⇒ Object
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 |
# File 'lib/oydid/basic.rb', line 65 def self.get_digest() , error = multi_decode() if .nil? return [nil, error] end # retVal = Multihashes.decode decoded_message # if retVal[:hash_function].to_s != "" # return [retVal[:hash_function].to_s, ""] # end case [0..1].to_s when "\x02\x10" return ["blake2b-16", ""] when "\x04 " return ["blake2b-32", ""] when "\b@" return ["blake2b-64", ""] else code, length, digest = .unpack('CCa*') retVal = Multicodecs[code].name rescue nil if !retVal.nil? return [retVal, ""] else return [nil, "unknown digest"] end end end |
.get_encoding(message) ⇒ Object
92 93 94 95 96 97 98 99 |
# File 'lib/oydid/basic.rb', line 92 def self.get_encoding() # from https://github.com/multiformats/multibase/blob/master/multibase.csv begin [Multibases.unpack().encoding, ""] rescue => error [nil, error.] end end |
.get_keytype(input) ⇒ Object
key management —————————-
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 |
# File 'lib/oydid/basic.rb', line 141 def self.get_keytype(input) code, length, digest = multi_decode(input).first.unpack('SCa*') case Multicodecs[code]&.name when 'ed25519-priv', 'p256-priv' return Multicodecs[code].name else pubkey = multi_decode(input).first if pubkey.bytes.length == 34 code = pubkey.bytes.first digest = pubkey[-32..] else if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT')) code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub) # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT') else code = pubkey.unpack('n').first end digest = pubkey[-1*(pubkey.bytes.length-2)..] end case Multicodecs[code]&.name when 'ed25519-pub', 'p256-pub' return Multicodecs[code].name else return nil end end end |
.get_location(id) ⇒ Object
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 |
# File 'lib/oydid/basic.rb', line 1020 def self.get_location(id) if id.include?(LOCATION_PREFIX) id_split = id.split(LOCATION_PREFIX) return id_split[1] else if id.include?(CGI.escape(LOCATION_PREFIX)) id_split = id.split(CGI.escape(LOCATION_PREFIX)) return id_split[1] else return DEFAULT_LOCATION end end end |
.getDelegatedPubKeysFromDID(did, key_type = "doc") ⇒ Object
available key_types
-
doc - document key
-
rev - revocation key
314 315 316 317 318 319 320 321 322 323 |
# File 'lib/oydid/basic.rb', line 314 def self.getDelegatedPubKeysFromDID(did, key_type = "doc") # retrieve DID did_document, msg = read(did, {}) keys, msg = getDelegatedPubKeysFromFullDidDocument(did_document, key_type) if keys.nil? return [nil, msg] else return [keys, ""] end end |
.getDelegatedPubKeysFromFullDidDocument(did_document, key_type = "doc") ⇒ Object
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/oydid/basic.rb', line 325 def self.getDelegatedPubKeysFromFullDidDocument(did_document, key_type = "doc") # get current public key case key_type when "doc" keys = [did_document["doc"]["key"].split(":").first] rescue nil when "rev" keys = [did_document["doc"]["key"].split(":").last] rescue nil else return [nil, "invalid key type: " + key_type] end if keys.nil? return [nil, "cannot retrieve current key"] end # travers through log and get active delegation public keys log = did_document["log"] log.each do |item| if item["op"] == 5 # DELEGATE # !!!OPEN: check if log entry is confirmed / referenced in a termination entry item_keys = item["doc"] if key_type == "doc" && item_keys[0..3] == "doc:" keys << item_keys[4-item_keys.length..] elsif key_type == "rev" && item_keys[0..3] == "rev:" keys << item_keys[4-item_keys.length..] end end end unless log.nil? # return array return [keys.uniq, ""] end |
.getPrivateKey(enc, pwd, dsk, dfl, options) ⇒ Object
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/oydid/basic.rb', line 263 def self.getPrivateKey(enc, pwd, dsk, dfl, ) if enc.to_s == "" # usually read from options[:doc_enc] if pwd.to_s == "" # usually read from options[:doc_pwd] if dsk.to_s == "" # usually read from options[:doc_key] if dfl.to_s == "" # default file name for key return [nil, "no reference"] else privateKey, msg = read_private_key(dfl.to_s, ) end else privateKey, msg = read_private_key(dsk.to_s, ) end else privateKey, msg = generate_private_key(pwd, 'ed25519-priv', ) end else privateKey, msg = decode_private_key(enc.to_s, ) if msg.nil? privateKey = enc.to_s end end return [privateKey, msg] end |
.getPubKeyFromDID(did) ⇒ Object
if the identifier is already the public key there is no validation if it is a valid key (this is a privacy-preserving feature)
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/oydid/basic.rb', line 289 def self.getPubKeyFromDID(did) identifier = did.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first rescue did identifier = identifier.delete_prefix("did:oyd:") # check if identifier is already PubKey if decode_public_key(identifier).first.nil? did_document, msg = read(did, {}) if did_document.nil? return [nil, msg] exit end pubKey = did_document["doc"]["key"].split(":").first rescue nil if pubKey.nil? return [nil, "cannot resolve " + did.to_s] else return [pubKey, ""] end else return [identifier, ""] end end |
.hash(message) ⇒ Object
32 33 34 |
# File 'lib/oydid/basic.rb', line 32 def self.hash() return multi_hash(, {:digest => DEFAULT_DIGEST}).first end |
.jwt_from_vc(vc, options) ⇒ Object
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 |
# File 'lib/oydid/vc.rb', line 584 def self.jwt_from_vc(vc, ) if [:issuer].to_s == '' return [nil, 'missing issuer DID'] end header = { alg: 'ES256', typ: 'JWT', kid: [:issuer] + '#key-doc' } if [:issuer_privateKey].to_s == '' return [nil, 'missing issuer private key'] end private_key = decode_private_key([:issuer_privateKey]).first encoded_header = Base64.urlsafe_encode64(header.to_json, padding: false) encoded_payload = Base64.urlsafe_encode64(vc.to_json_c14n, padding: false) data_to_sign = "#{encoded_header}.#{encoded_payload}" # puts 'data_to_sign: ' + data_to_sign.to_s # puts 'privateKey: ' + options[:issuer_privateKey].to_s jwt_digest = OpenSSL::Digest::SHA256.new asn1_signature = OpenSSL::ASN1.decode(private_key.dsa_sign_asn1(jwt_digest.digest(data_to_sign))) raw_signature = asn1_signature.value.map { |i| i.value.to_s(2).rjust(32, "\x00") }.join() encoded_signature = Base64.urlsafe_encode64(raw_signature, padding: false) # puts 'encoded_signature: ' + encoded_signature.to_s jwt = "#{encoded_header}.#{encoded_payload}.#{encoded_signature}" return [jwt, nil] end |
.match_log_did?(log, doc) ⇒ Boolean
check if signature matches current document check if signature in log is correct
18 19 20 21 22 23 24 |
# File 'lib/oydid/log.rb', line 18 def self.match_log_did?(log, doc) = log["doc"].to_s signature = log["sig"].to_s public_keys = doc["key"].to_s public_key = public_keys.split(":")[0] rescue "" return verify(, signature, public_key).first end |
.msg_decrypt(token, public_key_encoded, options) ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/oydid/didcomm.rb', line 96 def self.msg_decrypt(token, public_key_encoded, ) error = "" code, length, digest = Oydid.multi_decode(public_key_encoded).first.unpack('CCa*') case Multicodecs[code].name when 'ed25519-pub' public_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(digest) payload = JWT.decode token.to_s, public_key, true, { algorithm: 'ED25519' } else payload = nil error = "unsupported key codec" end return [payload, error] end |
.msg_encrypt(payload, private_key_encoded, did, options) ⇒ Object
encryption ———————————–
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 |
# File 'lib/oydid/didcomm.rb', line 59 def self.msg_encrypt(payload, private_key_encoded, did, ) error = "" code, length, digest = multi_decode(private_key_encoded).first.unpack('SCa*') case Multicodecs[code].name when 'ed25519-priv' private_key = RbNaCl::Signatures::Ed25519::SigningKey.new(digest) token = JWT.encode payload, private_key, 'ED25519' when 'p256-priv' group = OpenSSL::PKey::EC::Group.new('prime256v1') pub_key = group.generator.mul(OpenSSL::BN.new(digest, 2)) pub_oct = pub_key.to_bn.to_s(2) parameters = OpenSSL::ASN1::ObjectId("prime256v1") parameters.tag = 0 parameters.tagging = :EXPLICIT parameters.tag_class = :CONTEXT_SPECIFIC public_key_bitstring = OpenSSL::ASN1::BitString(pub_oct) public_key_bitstring.tag = 1 public_key_bitstring.tagging = :EXPLICIT public_key_bitstring.tag_class = :CONTEXT_SPECIFIC ec_private_key_asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(1), OpenSSL::ASN1::OctetString(digest), parameters, public_key_bitstring ]) ec_key = OpenSSL::PKey.read(ec_private_key_asn1.to_der) token = JWT.encode(payload, ec_key, 'ES256') else token = nil error = "unsupported key codec" end return [token, error] end |
.msg_sign(payload, hmac_secret) ⇒ Object
signing for JWS —————————
111 112 113 114 |
# File 'lib/oydid/didcomm.rb', line 111 def self.msg_sign(payload, hmac_secret) token = JWT.encode payload, hmac_secret, 'HS256' return [token, ""] end |
.msg_verify_jws(token, hmac_secret) ⇒ Object
116 117 118 119 120 121 122 123 |
# File 'lib/oydid/didcomm.rb', line 116 def self.msg_verify_jws(token, hmac_secret) begin decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' } return [decoded_token, ""] rescue return [nil, "verification failed"] end end |
.multi_decode(message) ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/oydid/basic.rb', line 24 def self.multi_decode() begin [Multibases.unpack().decode.to_s('ASCII-8BIT'), ""] rescue => error [nil, error.] end end |
.multi_encode(message, options) ⇒ Object
basic functions ————————— %w[multibases multihashes rbnacl json multicodecs].each { |f| require f }
14 15 16 17 18 19 20 21 22 |
# File 'lib/oydid/basic.rb', line 14 def self.multi_encode(, ) method = [:encode] || DEFAULT_ENCODING rescue DEFAULT_ENCODING case method when *SUPPORTED_ENCODINGS return [Multibases.pack(method, ).to_s, ""] else return [nil, "unsupported encoding: '" + method + "'"] end end |
.multi_hash(message, options) ⇒ Object
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 |
# File 'lib/oydid/basic.rb', line 36 def self.multi_hash(, ) method = [:digest] || DEFAULT_DIGEST case method.to_s when "sha2-256" digest = RbNaCl::Hash.sha256() when "sha2-512" digest = RbNaCl::Hash.sha512() when "sha3-224", "sha3-256", "sha3-384", "sha3-512" digest = OpenSSL::Digest.digest(method, ) when "blake2b-16" digest = RbNaCl::Hash.blake2b(, {digest_size: 16}) when "blake2b-32" digest = RbNaCl::Hash.blake2b(, {digest_size: 32}) when "blake2b-64" digest = RbNaCl::Hash.blake2b() else return [nil, "unsupported digest: '" + method.to_s + "'"] end code = Multicodecs[method].code length = digest.bytesize encoded = multi_encode([code, length, digest].pack("CCa#{length}"), ) # encoded = multi_encode(Multihashes.encode(digest, method.to_s), options) if encoded.first.nil? return [nil, encoded.last] else return [encoded.first, ""] end end |
.percent_encode(did) ⇒ Object
110 111 112 113 |
# File 'lib/oydid/basic.rb', line 110 def self.percent_encode(did) # remove "https://" from string as it is default did = did.sub("https://","").sub("@", "%40").sub("http://","http%3A%2F%2F").gsub(":","%3A").sub("did%3Aoyd%3A", "did:oyd:") end |
.persist_cmsm(pubkey, payload, options) ⇒ Object
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 |
# File 'lib/oydid.rb', line 586 def self.persist_cmsm(pubkey, payload, ) doc_location = [:doc_location] if doc_location.to_s == "" doc_location = DEFAULT_LOCATION end doc_location = doc_location.sub("%3A%2F%2F","://").sub("%3A", ":") my_body = { pubkey: pubkey, payload: payload.to_json } case doc_location.to_s when /^http/ persist_url = doc_location.to_s + "/cmsm" retVal = HTTParty.post(persist_url, headers: { 'Content-Type' => 'application/json' }, body: my_body.to_json ) if retVal.code != 200 err_msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/cmsm" return [false, err_msg] end else return [nil, "location not supported for persisting data in cmsm-flow"] end return [true, ""] end |
.private_key_to_jwk(private_key) ⇒ Object
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 |
# File 'lib/oydid/basic.rb', line 878 def self.private_key_to_jwk(private_key) code, length, digest = multi_decode(private_key).first.unpack('SCa*') case Multicodecs[code].name when 'ed25519-priv' return [nil, "not supported yet"] when 'p256-priv' group = OpenSSL::PKey::EC::Group.new('prime256v1') public_key = group.generator.mul(OpenSSL::BN.new(digest, 2)) point = public_key.to_bn.to_s(2) x_bin = point[1, 32] y_bin = point[33, 32] x = Base64.urlsafe_encode64(x_bin, padding: false) y = Base64.urlsafe_encode64(y_bin, padding: false) d = Base64.urlsafe_encode64(digest, padding: false) jwk = { kty: "EC", crv: "P-256", x: x, y: y, d: d } return [jwk, ""] else return [nil, "unsupported key codec"] end end |
.public_key(private_key, options = {}, method = nil) ⇒ Object
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 |
# File 'lib/oydid/basic.rb', line 214 def self.public_key(private_key, = {}, method = nil) code, length, digest = multi_decode(private_key).first.unpack('SCa*') case Multicodecs[code].name when 'ed25519-priv' method = 'ed25519-pub' if method.nil? case method when 'ed25519-pub' public_key = Ed25519::SigningKey.new(digest).verify_key when 'x25519-pub' public_key = RbNaCl::PrivateKey.new(digest).public_key else return [nil, "unsupported key codec"] end # encoding according to https://www.w3.org/TR/vc-di-eddsa/#ed25519verificationkey2020 encoded = multi_encode( Multibases::DecodedByteArray.new( ([Multicodecs[method].code, 1] << public_key.to_bytes.bytes).flatten) .to_s(Encoding::BINARY), ) # previous (up until oydid 0.5.6) wrong encoding (length should not be set!): # length = public_key.to_bytes.bytesize # encoded = multi_encode([Multicodecs[method].code, length, public_key].pack("CCa#{length}"), options) if encoded.first.nil? return [nil, encoded.last] else return [encoded.first, ""] end when 'p256-priv' method = 'p256-pub' if method.nil? group = OpenSSL::PKey::EC::Group.new('prime256v1') public_key = group.generator.mul(OpenSSL::BN.new(digest, 2)) encoded = multi_encode( Multibases::DecodedByteArray.new( (to_varint(0x1200) << public_key.to_bn.to_s(2).bytes) .flatten).to_s(Encoding::BINARY), ) if encoded.first.nil? return [nil, encoded.last] else return [encoded.first, ""] end else return [nil, "unsupported key codec"] end end |
.public_key_from_jwk(jwk, options = {}) ⇒ Object
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 |
# File 'lib/oydid/basic.rb', line 963 def self.public_key_from_jwk(jwk, = {}) begin if jwk.is_a?(String) jwk = JSON.parse(jwk) end rescue return [nil, "invalid input"] end jwk = jwk.transform_keys(&:to_s) if jwk["kty"] == "EC" && jwk["crv"] == "P-256" x = base64_url_decode(jwk["x"]) y = base64_url_decode(jwk["y"]) digest = OpenSSL::ASN1::BitString.new(OpenSSL::BN.new("\x04" + x + y, 2).to_s(2)) encoded = multi_encode( Multibases::DecodedByteArray.new( (to_varint(0x1200) << digest.value.bytes) .flatten).to_s(Encoding::BINARY), ) # asn1_public_key = OpenSSL::ASN1::Sequence.new([ # OpenSSL::ASN1::Sequence.new([ # OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'), # OpenSSL::ASN1::ObjectId.new('prime256v1') # ]), # OpenSSL::ASN1::BitString.new(OpenSSL::BN.new("\x04" + x + y, 2).to_s(2)) # ]) # pub_key = OpenSSL::PKey::EC.new(asn1_public_key.to_der) # encoded = multi_encode( # Multibases::DecodedByteArray.new( # (to_varint(0x1200) << pub_key.to_bn.to_s(2).bytes) # .flatten).to_s(Encoding::BINARY), # options) if encoded.first.nil? return [nil, encoded.last] else return [encoded.first, ""] end else return [nil, "unsupported key codec"] end end |
.public_key_to_jwk(public_key) ⇒ Object
907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 |
# File 'lib/oydid/basic.rb', line 907 def self.public_key_to_jwk(public_key) begin pubkey = multi_decode(public_key).first if pubkey.bytes.length == 34 code = pubkey.bytes.first digest = pubkey[-32..] else if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT')) code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub) # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT') else code = pubkey.unpack('n').first end digest = pubkey[-1*(pubkey.bytes.length-2)..] end case Multicodecs[code].name when 'ed25519-pub' return [nil, "not supported yet"] when 'p256-pub' if digest.bytes.first == 4 # Unkomprimiertes Format: X (32 Bytes) || Y (32 Bytes) x_coord = digest[1..32] y_coord = digest[33..64] x_base64 = Base64.urlsafe_encode64(x_coord, padding: false) y_base64 = Base64.urlsafe_encode64(y_coord, padding: false) else asn1_public_key = OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'), OpenSSL::ASN1::ObjectId.new('prime256v1') ]), OpenSSL::ASN1::BitString.new(digest) ]) key = OpenSSL::PKey::EC.new(asn1_public_key.to_der) x, y = key.public_key.to_octet_string(:uncompressed)[1..].unpack1('H*').scan(/.{64}/) x_base64 = Base64.urlsafe_encode64([x].pack('H*'), padding: false) y_base64 = Base64.urlsafe_encode64([y].pack('H*'), padding: false) end jwk = { "kty" => "EC", "crv" => "P-256", "x" => x_base64, "y" => y_base64 } return [jwk, ""] else return [nil, "unsupported key codec"] end rescue return [nil, "unknown key codec"] end end |
.publish(did, didDocument, logs, options) ⇒ Object
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 |
# File 'lib/oydid.rb', line 543 def self.publish(did, didDocument, logs, ) did_hash = did.delete_prefix("did:oyd:") did10 = did_hash[0,10] doc_location = [:doc_location] if doc_location.to_s == "" if did_hash.include?(LOCATION_PREFIX) hash_split = did_hash.split(LOCATION_PREFIX) did_hash = hash_split[0] doc_location = hash_split[1] else doc_location = DEFAULT_LOCATION end end # wirte data based on location case doc_location.to_s when /^http/ # build object to post did_data = { "did": did, "did-document": didDocument, "logs": logs } oydid_url = doc_location.to_s + "/doc" retVal = HTTParty.post(oydid_url, headers: { 'Content-Type' => 'application/json' }, body: did_data.to_json ) if retVal.code != 200 err_msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/doc" return [false, err_msg] end else # write files to disk write_private_storage(logs.to_json, did10 + ".log") write_private_storage(didDocument.to_json, did10 + ".doc") write_private_storage(did, did10 + ".did") end return [true, ""] end |
.publish_vc(vc, options) ⇒ Object
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 342 343 344 345 |
# File 'lib/oydid/vc.rb', line 298 def self.publish_vc(vc, ) vc = vc.transform_keys(&:to_s) identifier = vc["identifier"] rescue nil if identifier.nil? identifier = vc["id"] rescue nil end if identifier.nil? return [nil, "invalid format (missing identifier)"] exit end if vc["credentialSubject"].is_a?(Array) cs = vc["credentialSubject"].last.transform_keys(&:to_s) rescue nil else cs = vc["credentialSubject"].transform_keys(&:to_s) rescue nil end holder = cs["id"] rescue nil if holder.nil? return [nil, "invalid format (missing holder)"] exit end vc_location = "" if ![:location].nil? vc_location = [:location] end if vc_location.to_s == "" vc_location = DEFAULT_LOCATION end if !identifier.start_with?('http') identifier = vc_location.sub(/(\/)+$/,'') + "/credentials/" + identifier end # build object to post vc_data = { "identifier": identifier, "vc": vc, "holder": holder } vc_url = vc_location.sub(/(\/)+$/,'') + "/credentials" retVal = HTTParty.post(vc_url, headers: { 'Content-Type' => 'application/json' }, body: vc_data.to_json ) if retVal.code != 200 err_msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vc_url.to_s return [nil, err_msg] end return [retVal["identifier"], ""] end |
.publish_vp(vp, options) ⇒ Object
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
# File 'lib/oydid/vc.rb', line 419 def self.publish_vp(vp, ) vp = vp.transform_keys(&:to_s) identifier = vp["identifier"] rescue nil if identifier.nil? return [nil, "invalid format (missing identifier)"] exit end proof = vp["proof"].transform_keys(&:to_s) rescue nil holder = vp["holder"] rescue nil if holder.nil? return [nil, "invalid format (missing holder)"] exit end vp_location = "" if ![:location].nil? vp_location = [:location] end if vp_location.to_s == "" vp_location = DEFAULT_LOCATION end vp["identifier"] = vp_location.sub(/(\/)+$/,'') + "/presentations/" + identifier # build object to post vp_data = { "identifier": identifier, "vp": vp, "holder": holder } vp_url = vp_location.sub(/(\/)+$/,'') + "/presentations" retVal = HTTParty.post(vp_url, headers: { 'Content-Type' => 'application/json' }, body: vp_data.to_json ) if retVal.code != 200 err_msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vp_url.to_s return [nil, err_msg] end return [vp["identifier"], ""] end |
.read(did, options) ⇒ Object
expected DID format: did:oyd:123
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 |
# File 'lib/oydid.rb', line 42 def self.read(did, ) if did.to_s == "" return [nil, "missing DID"] end # setup currentDID = { "did": did, "doc": "", "log": [], "doc_log_id": nil, "termination_log_id": nil, "error": 0, "message": "", "verification": "" }.transform_keys(&:to_s) # get did location did_location = "" if ![:doc_location].nil? did_location = [:doc_location] end if did_location.to_s == "" if ![:location].nil? did_location = [:location] end end if did_location.to_s == "" if did.include?(LOCATION_PREFIX) tmp = did.split(LOCATION_PREFIX) did = tmp[0] did_location = tmp[1] end if did.include?(CGI.escape LOCATION_PREFIX) tmp = did.split(CGI.escape LOCATION_PREFIX) did = tmp[0] did_location = tmp[1] end end if did_location == "" did_location = DEFAULT_LOCATION end did_hash = did.delete_prefix("did:oyd:") did10 = did_hash[0,10] # retrieve DID document did_document = retrieve_document(did_hash, did10 + ".doc", did_location, ) if did_document.first.nil? return [nil, did_document.last] end did_document = did_document.first currentDID["doc"] = did_document if [:trace] puts " .. DID document retrieved" end # get log location log_hash = did_document["log"] log_location = "" if ![:log_location].nil? log_location = [:log_location] end if log_location.to_s == "" if ![:location].nil? log_location = [:location] end end if log_location.to_s == "" if log_hash.include?(LOCATION_PREFIX) hash_split = log_hash.split(LOCATION_PREFIX) log_hash = hash_split[0] log_location = hash_split[1] end end if log_location == "" log_location = DEFAULT_LOCATION end # retrieve and traverse log to get current DID state log_array, msg = retrieve_log(log_hash, did10 + ".log", log_location, ) if log_array.nil? return [nil, msg] else if [:trace] puts " .. Log retrieved" end dag, create_index, terminate_index, msg = dag_did(log_array, ) if dag.nil? return [nil, msg] end if [:trace] puts " .. DAG with " + dag.vertices.length.to_s + " vertices and " + dag.edges.length.to_s + " edges, CREATE index: " + create_index.to_s end result = dag2array(dag, log_array, create_index, [], ) ordered_log_array = dag2array_terminate(dag, log_array, terminate_index, result, ) currentDID["log"] = ordered_log_array # !!! ugly hack to get access to all delegation keys required in dag_update currentDID["full_log"] = log_array if [:trace] if [:silent].nil? || ![:silent] dag.edges.each do |e| puts " edge " + e.origin[:id].to_s + " <- " + e.destination[:id].to_s end end end # identify if DID Rotation was performed rotated_DID = (currentDID.transform_keys(&:to_s)["doc"]["doc"].has_key?("@context") && currentDID.transform_keys(&:to_s)["doc"]["doc"].has_key?("id") && currentDID.transform_keys(&:to_s)["doc"]["doc"]["id"].split(":").first == "did") rescue false if rotated_DID doc = currentDID["doc"].dup currentDID = dag_update(currentDID, ) currentDID["doc"] = doc else currentDID = dag_update(currentDID, ) end if [:log_complete] currentDID["log"] = log_array end return [currentDID, ""] end end |
.read_private_key(filename, options) ⇒ Object
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 |
# File 'lib/oydid/basic.rb', line 760 def self.read_private_key(filename, ) begin f = File.open(filename) key_encoded = f.read.strip f.close rescue return [nil, "cannot read file"] end key_type = get_keytype(key_encoded) || [:key_type] rescue [:key_type] if key_type.include?('-') key_type = key_type.split('-').first || [:key_type] rescue [:key_type] end if key_type == 'p256' begin key = decode_private_key(key_encoded).first private_key = key.private_key.to_s(2) code = Multicodecs["p256-priv"].code rescue return [nil, "invalid base64 encoded p256-priv key"] end else begin code, length, digest = multi_decode(key_encoded).first.unpack('SCa*') case Multicodecs[code].name when 'ed25519-priv' private_key = Ed25519::SigningKey.new(digest).to_bytes # when 'p256-priv' # key = OpenSSL::PKey::EC.new('prime256v1') # key.private_key = OpenSSL::BN.new(digest, 2) # private_key = key.private_key.to_s(2) else return [nil, "unsupported key codec"] end rescue return [nil, "invalid key"] end end length = private_key.bytesize return multi_encode([code, length, private_key].pack("SCa#{length}"), ) end |
.read_private_storage(filename) ⇒ Object
1012 1013 1014 1015 1016 1017 1018 |
# File 'lib/oydid/basic.rb', line 1012 def self.read_private_storage(filename) begin File.open(filename, 'r') { |f| f.read } rescue nil end end |
.read_varint(str) ⇒ Object
130 131 132 133 134 135 136 137 138 |
# File 'lib/oydid/basic.rb', line 130 def self.read_varint(str) n = shift = 0 str.each_byte do |byte| n |= (byte & 0x7f) << shift break unless (byte & 0x80) == 0x80 shift += 7 end return n end |
.read_vc(identifier, options) ⇒ Object
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 |
# File 'lib/oydid/vc.rb', line 5 def self.read_vc(identifier, ) vc_location = "" if ![:location].nil? vc_location = [:location] end if vc_location.to_s == "" vc_location = DEFAULT_LOCATION end vc_url = vc_location.sub(/(\/)+$/,'') + "/credentials/" + identifier holder = [:holder].to_s rescue nil if holder.to_s == "" msg = "missing holder information" return [nil, msg] exit end private_key = [:holder_privateKey].to_s rescue nil if private_key.to_s == "" msg = "missing private document key information" return [nil, msg] exit end # authenticate against repository init_url = vc_location + "/oydid/init" sid = SecureRandom.hex(20).to_s response = HTTParty.post(init_url, headers: { 'Content-Type' => 'application/json' }, body: { "session_id": sid, "public_key": Oydid.public_key(private_key, ).first }.to_json ).parsed_response rescue {} if response["challenge"].nil? msg = "missing challenge for repository authentication" return [nil, msg] exit end challenge = response["challenge"].to_s # sign challenge and request token token_url = vc_location + "/oydid/token" response = HTTParty.post(token_url, headers: { 'Content-Type' => 'application/json' }, body: { "session_id": sid, "signed_challenge": Oydid.sign(challenge, private_key, ).first }.to_json).parsed_response rescue {} access_token = response["access_token"].to_s rescue nil if access_token.nil? msg = "invalid repository authentication (access_token)" return [nil, msg] exit end retVal = HTTParty.get(vc_url, headers: {'Authorization' => 'Bearer ' + access_token}) if retVal.code != 200 if retVal.code == 401 msg = "unauthorized (valid Bearer token required)" else msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vc_url.to_s end return [nil, msg] end return [retVal.parsed_response, ""] end |
.read_vp(identifier, options) ⇒ Object
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/oydid/vc.rb', line 347 def self.read_vp(identifier, ) vp_location = "" if ![:location].nil? vp_location = [:location] end if vp_location.to_s == "" vp_location = DEFAULT_LOCATION end vp_url = vp_location.sub(/(\/)+$/,'') + "/presentations/" + identifier retVal = HTTParty.get(vp_url) if retVal.code != 200 msg = retVal.parsed_response("error").to_s rescue "invalid response from " + vp_url.to_s return [nil, msg] end return [retVal.parsed_response, ""] end |
.retrieve_document(doc_identifier, doc_file, doc_location, options) ⇒ Object
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 |
# File 'lib/oydid/basic.rb', line 1034 def self.retrieve_document(doc_identifier, doc_file, doc_location, ) if doc_location == "" doc_location = DEFAULT_LOCATION end if !(doc_location == "" || doc_location == "local") if !doc_location.start_with?("http") doc_location = "https://" + doc_location end end case doc_location when /^http/ doc_location = doc_location.sub("%3A%2F%2F","://").sub("%3A", ":") option_str = "" if [:followAlsoKnownAs] option_str = "?followAlsoKnownAs=true" end retVal = HTTParty.get(doc_location + "/doc/" + doc_identifier + option_str) if retVal.code != 200 msg = retVal.parsed_response["error"].to_s rescue "" if msg.to_s == "" msg = "invalid response from " + doc_location.to_s + "/doc/" + doc_identifier.to_s end return [nil, msg] end if .transform_keys(&:to_s)["trace"] if [:silent].nil? || ![:silent] puts "GET " + doc_identifier + " from " + doc_location end end return [retVal.parsed_response, ""] when "", "local" doc = JSON.parse(read_private_storage(doc_file)) rescue {} if doc == {} return [nil, "cannot read file"] else return [doc, ""] end end end |
.retrieve_document_raw(doc_hash, doc_file, doc_location, options) ⇒ Object
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 |
# File 'lib/oydid/basic.rb', line 1074 def self.retrieve_document_raw(doc_hash, doc_file, doc_location, ) doc_hash = doc_hash.split(LOCATION_PREFIX).first.split(CGI.escape LOCATION_PREFIX).first rescue doc_hash doc_hash = doc_hash.delete_prefix("did:oyd:") if doc_location == "" doc_location = DEFAULT_LOCATION end if !(doc_location == "" || doc_location == "local") if !doc_location.start_with?("http") doc_location = "https://" + doc_location end end case doc_location when /^http/ doc_location = doc_location.sub("%3A%2F%2F","://").sub("%3A", ":") retVal = HTTParty.get(doc_location + "/doc_raw/" + doc_hash) if retVal.code != 200 msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/doc/" + doc_hash.to_s return [nil, msg] end if .transform_keys(&:to_s)["trace"] if [:silent].nil? || ![:silent] puts "GET " + doc_hash + " from " + doc_location end end return [retVal.parsed_response, ""] when "", "local" doc = JSON.parse(read_private_storage(doc_file)) rescue {} log = JSON.parse(read_private_storage(doc_file.sub(".doc", ".log"))) rescue {} if doc == {} return [nil, "cannot read file"] else obj = {"doc" => doc, "log" => log} return [obj, ""] end end end |
.retrieve_log(did_hash, log_file, log_location, options) ⇒ Object
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 |
# File 'lib/oydid/log.rb', line 26 def self.retrieve_log(did_hash, log_file, log_location, ) if log_location == "" log_location = DEFAULT_LOCATION end if !(log_location == "" || log_location == "local") if !log_location.start_with?("http") log_location = "https://" + log_location end end case log_location when /^http/ log_location = log_location.gsub("%3A",":") log_location = log_location.gsub("%2F%2F","//") retVal = HTTParty.get(log_location + "/log/" + did_hash) if retVal.code != 200 msg = retVal.parsed_response("error").to_s rescue "invalid response from " + log_location.to_s + "/log/" + did_hash.to_s return [nil, msg] end if .transform_keys(&:to_s)["trace"] if [:silent].nil? || ![:silent] puts "GET log for " + did_hash + " from " + log_location end end retVal = JSON.parse(retVal.to_s) rescue nil return [retVal, ""] when "", "local" doc = JSON.parse(read_private_storage(log_file)) rescue {} if doc == {} return [nil, "cannot read file '" + log_file + "'"] end return [doc, ""] end end |
.retrieve_log_item(log_hash, log_location, options) ⇒ Object
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 |
# File 'lib/oydid/log.rb', line 63 def self.retrieve_log_item(log_hash, log_location, ) if log_location.to_s == "" log_location = DEFAULT_LOCATION end if !log_location.start_with?("http") log_location = "https://" + log_location end case log_location when /^http/ log_location = log_location.gsub("%3A",":") log_location = log_location.gsub("%2F%2F","//") retVal = HTTParty.get(log_location + "/log/" + log_hash + "/item") if retVal.code != 200 msg = retVal.parsed_response("error").to_s rescue "invalid response from " + log_location.to_s + "/log/" + log_hash.to_s + "/item" return [nil, msg] end if .transform_keys(&:to_s)["trace"] if [:silent].nil? || ![:silent] puts "GET log entry for " + log_hash + " from " + log_location end end retVal = JSON.parse(retVal.to_s) rescue nil return [retVal, ""] else return [nil, "cannot read from " + log_location] end end |
.revoke(did, options) ⇒ Object
984 985 986 987 988 989 990 |
# File 'lib/oydid.rb', line 984 def self.revoke(did, ) revoc_log, msg = revoke_base(did, ) if revoc_log.nil? return [nil, msg] end success, msg = revoke_publish(did, revoc_log, ) end |
.revoke_base(did, options) ⇒ Object
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 |
# File 'lib/oydid.rb', line 794 def self.revoke_base(did, ) did_orig = did.dup doc_location = [:doc_location] if [:ts].nil? ts = Time.now.utc.to_i else ts = [:ts] end did_info, msg = read(did, ) if did_info.nil? return [nil, "cannot resolve DID (on revoking DID)"] end if did_info["error"] != 0 return [nil, did_info["message"].to_s] end did = did_info["did"] did_hash = did.delete_prefix("did:oyd:") did10 = did_hash[0,10] if doc_location.to_s == "" if did_hash.include?(LOCATION_PREFIX) hash_split = did_hash.split(LOCATION_PREFIX) did_hash = hash_split[0] doc_location = hash_split[1] end end # collect relevant information from previous did did_old = did.dup did10_old = did10.dup log_old = did_info["log"] msg = "" if [:old_doc_key].nil? if [:old_doc_enc].nil? if [:old_doc_pwd].nil? privateKey_old = read_private_storage(did10_old + "_private_key.enc") else privateKey_old, msg = generate_private_key([:old_doc_pwd].to_s, [:key_type]+'-priv', ) end else privateKey_old = [:old_doc_enc].to_s end else privateKey_old, msg = read_private_key([:old_doc_key].to_s, ) end if privateKey_old.nil? return [nil, "invalid or missing old private document key"] end if [:old_rev_key].nil? if [:old_rev_enc].nil? if [:old_rev_pwd].nil? revocationKey_old = read_private_storage(did10_old + "_revocation_key.enc") else revocationKey_old, msg = generate_private_key([:old_rev_pwd].to_s, [:key_type]+'-priv', ) end else revocationKey_old = [:old_rev_enc].to_s end else revocationKey_old, msg = read_private_key([:old_rev_key].to_s, ) end if revocationKey_old.nil? return [nil, "invalid or missing old private revocation key"] end if [:rev_key].nil? && [:rev_pwd].nil? && [:rev_enc].nil? # revocationKey, msg = read_private_key(did10 + "_revocation_key.enc", options) revocationLog = read_private_storage(did10 + "_revocation.json") else # check if provided old keys are native DID keys or delegates ================== msg = "" if [:doc_key].nil? if [:doc_enc].nil? old_privateKey, msg = generate_private_key([:old_doc_pwd].to_s, [:key_type]+'-priv', ) else old_privateKey = [:old_doc_enc].to_s end else old_privateKey, msg = read_private_key([:old_doc_key].to_s, ) end if [:rev_key].nil? if [:rev_enc].nil? old_revocationKey, msg = generate_private_key([:old_rev_pwd].to_s, [:key_type]+'-priv', ) else old_revocationKey = [:old_rev_enc].to_s end else old_revocationKey, msg = read_private_key([:old_rev_key].to_s, ) end old_publicDocKey = public_key(old_privateKey, {}).first old_publicRevKey = public_key(old_revocationKey, {}).first old_did_key = old_publicDocKey + ":" + old_publicRevKey # compare old keys with existing DID Document & generate revocation record if old_did_key.to_s == did_info["doc"]["key"].to_s # provided keys are native DID keys ------------------ # re-build revocation document old_did_doc = did_info["doc"]["doc"] old_ts = did_info["log"].last["ts"] old_subDid = {"doc": old_did_doc, "key": old_did_key}.to_json old_subDidHash = multi_hash(canonical(old_subDid), LOG_HASH_OPTIONS).first old_signedSubDidHash = sign(old_subDidHash, old_revocationKey, LOG_HASH_OPTIONS).first revocationLog = { "ts": old_ts, "op": 1, # REVOKE "doc": old_subDidHash, "sig": old_signedSubDidHash }.transform_keys(&:to_s).to_json else # proviced keys are either delegates or invalid ------ # * check validity of key-doc delegate pubKeys, msg = getDelegatedPubKeysFromDID(did, "doc") if !pubKeys.include?(old_publicDocKey) return [nil, "invalid or missing private document key"] end # * check validity of key-rev delegate pubKeys, msg = getDelegatedPubKeysFromDID(did, "rev") if !pubKeys.include?(old_publicRevKey) return [nil, "invalid or missing private revocation key"] end # retrieve revocationLog from previous in key-rev delegate revoc_log = nil log_old.each do |item| if !item["encrypted-revocation-log"].nil? revoc_log = item["encrypted-revocation-log"] end end if revoc_log.nil? return [nil, "cannot retrieve revocation log"] end revocationLog, msg = decrypt(revoc_log.to_json, old_revocationKey.to_s) if revocationLog.nil? return [nil, "cannot decrypt revocation log entry: " + msg] end end # compare old keys with existing DID Document end if revocationLog.nil? return [nil, "private revocation key not found"] end # check if REVOCATION hash matches hash in TERMINATION if did_info["log"][did_info["termination_log_id"]]["doc"] != multi_hash(canonical(revocationLog), LOG_HASH_OPTIONS).first return [nil, "invalid revocation information"] end revoc_log = JSON.parse(revocationLog) revoc_log["previous"] = [ multi_hash(canonical(log_old[did_info["doc_log_id"].to_i]), LOG_HASH_OPTIONS).first, multi_hash(canonical(log_old[did_info["termination_log_id"].to_i]), LOG_HASH_OPTIONS).first, ] return [revoc_log, ""] end |
.revoke_publish(did, revoc_log, options) ⇒ Object
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 |
# File 'lib/oydid.rb', line 951 def self.revoke_publish(did, revoc_log, ) did_hash = did.delete_prefix("did:oyd:") did10 = did_hash[0,10] doc_location = [:doc_location] if did_hash.include?(LOCATION_PREFIX) hash_split = did_hash.split(LOCATION_PREFIX) did_hash = hash_split[0] doc_location = hash_split[1] end if doc_location.to_s == "" doc_location = DEFAULT_LOCATION end # publish revocation log based on location case doc_location.to_s when /^http/ retVal = HTTParty.post(doc_location.to_s + "/log/" + did_hash.to_s, headers: { 'Content-Type' => 'application/json' }, body: {"log": revoc_log}.to_json ) if retVal.code != 200 msg = retVal.parsed_response("error").to_s rescue "invalid response from " + doc_location.to_s + "/log/" + did_hash.to_s return [nil, msg] end else File.write(did10 + ".log", revoc_log.to_json) if !did_old.nil? File.write(did10_old + ".log", revoc_log.to_json) end end return [did, ""] end |
.sign(message, private_key, options = {}) ⇒ Object
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 |
# File 'lib/oydid/basic.rb', line 357 def self.sign(, private_key, = {}) key_type = get_keytype(private_key) case key_type when 'ed25519-priv' code, length, digest = multi_decode(private_key).first.unpack('SCa*') encoded = multi_encode(Ed25519::SigningKey.new(digest).sign(), ) if encoded.first.nil? return [nil, encoded.last] else return [encoded.first, ""] end when 'p256-priv' # non-deterministic signing # ec_key = decode_private_key(private_key).first # dgst_bin = OpenSSL::Digest::SHA256.digest(message) # der = ec_key.dsa_sign_asn1(dgst_bin) # asn1 = OpenSSL::ASN1.decode(der) # r_hex = asn1.value[0].value.to_s(16).rjust(64, '0') # s_hex = asn1.value[1].value.to_s(16).rjust(64, '0') # sig_bin = [r_hex + s_hex].pack('H*') # encoded_signature = Base64.strict_encode64(sig_bin).tr('+/', '-_').delete('=') # === deterministic signing with P-256 ===== # key & constants ec_key = decode_private_key(private_key).first if (ec_key.respond_to?(:private_key) ? ec_key.private_key : nil).nil? return [nil, "invalild private key"] end group = OpenSSL::PKey::EC::Group.new('prime256v1') n = group.order qlen = n.num_bits holen = 256 bx = ec_key.private_key # hash message h1_bin = OpenSSL::Digest::SHA256.digest() h1_int = h1_bin.unpack1('H*').to_i(16) # helper (RFC 6979 Section 2.3) int2octets = lambda do |int| bin = int.to_s(16).rjust((qlen + 7) / 8 * 2, '0') [bin].pack('H*') end bits2octets = lambda do |bits| z1 = bits % n int2octets.call(z1) end # initialize HMAC-DRBG v = "\x01" * (holen / 8) k = "\x00" * (holen / 8) key_oct = int2octets.call(bx) hash_oct = bits2octets.call(h1_int) k = OpenSSL::HMAC.digest('SHA256', k, v + "\x00" + key_oct + hash_oct) v = OpenSSL::HMAC.digest('SHA256', k, v) k = OpenSSL::HMAC.digest('SHA256', k, v + "\x01" + key_oct + hash_oct) v = OpenSSL::HMAC.digest('SHA256', k, v) # identify k (step H in RFC 6979) loop do v = OpenSSL::HMAC.digest('SHA256', k, v) t = v.unpack1('H*').to_i(16) k_candidate = t % n if k_candidate.positive? && k_candidate < n k_bn = OpenSSL::BN.new(k_candidate) # calculate signature (r,s) r_bn = group.generator.mul(k_bn).to_bn # Point → BN (uncompressed) # extract x r_int = r_bn.to_s(2)[1, 32].unpack1('H*').to_i(16) % n next if r_int.zero? kinv = k_bn.mod_inverse(n) s_int = (kinv * (h1_int + bx.to_i * r_int)) % n next if s_int.zero? s_int = n.to_i - s_int if s_int > n.to_i / 2 # encode r||s -> URL-safe Base64 r_hex = r_int.to_s(16).rjust(64, '0') s_hex = s_int.to_s(16).rjust(64, '0') sig = [r_hex + s_hex].pack('H*') return [Base64.urlsafe_encode64(sig, padding: false), ""] end k = OpenSSL::HMAC.digest('SHA256', k, v + "\x00") v = OpenSSL::HMAC.digest('SHA256', k, v) end return [encoded_signature, ""] else return [nil, "unsupported key codec"] end end |
.simulate_did(content, did, mode, options) ⇒ Object
176 177 178 179 180 |
# File 'lib/oydid.rb', line 176 def self.simulate_did(content, did, mode, ) did_doc, did_key, did_log, msg = generate_base(content, did, mode, ) user_did = did_doc[:did] return [user_did, msg] end |
.to_varint(n) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/oydid/basic.rb', line 115 def self.to_varint(n) bytes = [] loop do byte = n & 0x7F n >>= 7 if n == 0 bytes << byte break else bytes << (byte | 0x80) end end bytes end |
.token_from_challenge(host, pwd, options = {}) ⇒ Object
DID Auth for data container with challenge —
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/oydid/didcomm.rb', line 126 def self.token_from_challenge(host, pwd, = {}) sid = SecureRandom.hex(20).to_s public_key = public_key(generate_private_key(pwd, ).first, ).first retVal = HTTParty.post(host + "/oydid/init", headers: { 'Content-Type' => 'application/json' }, body: { "session_id": sid, "public_key": public_key }.to_json ) challenge = retVal.parsed_response["challenge"] signed_challenge = sign(challenge, Oydid.generate_private_key(pwd, ).first, ).first retVal = HTTParty.post(host + "/oydid/token", headers: { 'Content-Type' => 'application/json' }, body: { "session_id": sid, "signed_challenge": signed_challenge, "public_key": public_key }.to_json) return retVal.parsed_response["access_token"] end |
.update(content, did, options) ⇒ Object
172 173 174 |
# File 'lib/oydid.rb', line 172 def self.update(content, did, ) return write(content, did, "update", ) end |
.vc_proof(vc, proof, private_key_encoded, options) ⇒ Object
Verifiable Credential hash vc = “type”, “issuer”, “issuanceDate”, “credentialSubject”
but no "proof"!
proof = “verificationMethod”, “proofPurpose”, “created”
but no "proofValue"
private_key_encoded (string) “z…” www.w3.org/TR/vc-di-eddsa/#representation-ed25519signature2020
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/oydid/vc.rb', line 117 def self.vc_proof(vc, proof, private_key_encoded, ) vc, vc_hash, errmsg = vc_proof_prep(vc, proof) if vc.nil? return [nil, errmsg] end code, length, digest = multi_decode(private_key_encoded).first.unpack('SCa*') case Multicodecs[code].name when 'ed25519-priv' signing_key = Ed25519::SigningKey.new(digest) vc["proof"]["proofValue"] = multi_encode(signing_key.sign([vc_hash].pack('H*')).bytes, ).first when 'p256-priv' vc["proof"]["proofValue"] = sign("message", private_key_encoded, ) else return [nil, "unsupported key codec"] end return [vc, nil] end |
.vc_proof_prep(vc, proof) ⇒ Object
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 |
# File 'lib/oydid/vc.rb', line 68 def self.vc_proof_prep(vc, proof) cntxt = vc["@context"].dup if !cntxt.is_a?(Array) cntxt = [cntxt] end cntxt << ED25519_SECURITY_SUITE unless cntxt.include?(ED25519_SECURITY_SUITE) vc["@context"] = cntxt.dup vc.delete("proof") vc = JSON::LD::API.compact(JSON.parse(vc.to_json), JSON.parse(cntxt.to_json)) graph = RDF::Graph.new << JSON::LD::Reader.new(vc.to_json) norm_graph = graph.dump(:normalize).to_s if norm_graph.strip == "" return [nil, nil, "empty VC"] end hash1 = Multibases.pack("base16", RbNaCl::Hash.sha256(norm_graph)).to_s[1..] remove_context = false if proof["@context"].nil? proof["@context"] = cntxt.dup remove_context = true else cntxt = proof["@context"] end if proof["created"].nil? proof["created"] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") end proof.delete("proofValue") proof = JSON::LD::API.compact(JSON.parse(proof.to_json), JSON.parse(cntxt.to_json)) graph = RDF::Graph.new << JSON::LD::Reader.new(proof.to_json) norm_graph = graph.dump(:normalize).to_s if norm_graph.strip == "" return [nil, nil, "empty proof"] end hash2 = Multibases.pack("base16", RbNaCl::Hash.sha256(norm_graph)).to_s[1..] if remove_context proof.delete("@context") end vc["proof"] = proof return [vc, hash2+hash1, nil] end |
.verify(message, signature, public_key) ⇒ Object
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 |
# File 'lib/oydid/basic.rb', line 453 def self.verify(, signature, public_key) begin pubkey = multi_decode(public_key).first if pubkey.bytes.length == 34 code = pubkey.bytes.first digest = pubkey[-32..] else if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT')) code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub) # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT') else code = pubkey.unpack('n').first end digest = pubkey[-1*(pubkey.bytes.length-2)..] end case Multicodecs[code].name when 'ed25519-pub' verify_key = Ed25519::VerifyKey.new(digest) signature_verification = false begin verify_key.verify(multi_decode(signature).first, ) signature_verification = true rescue Ed25519::VerifyError signature_verification = false end return [signature_verification, ""] when 'p256-pub' asn1_public_key = OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'), OpenSSL::ASN1::ObjectId.new('prime256v1') ]), OpenSSL::ASN1::BitString.new(digest) ]) key = OpenSSL::PKey::EC.new(asn1_public_key.to_der) sig_raw = Base64.urlsafe_decode64(signature + "=" * ((4 - signature.size % 4) % 4)) r_hex = sig_raw[0, 32].unpack1("H*") s_hex = sig_raw[32, 32].unpack1("H*") asn_r = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(r_hex, 16)) asn_s = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(s_hex, 16)) sig_der = OpenSSL::ASN1::Sequence.new([asn_r, asn_s]).to_der = OpenSSL::Digest::SHA256.new valid = key.dsa_verify_asn1(.digest(), sig_der) return [valid, ""] else return [nil, "unsupported key codec"] end rescue return [nil, "unknown key codec"] end end |
.verify_vc(content, options) ⇒ Object
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 |
# File 'lib/oydid/vc.rb', line 460 def self.verify_vc(content, ) retVal = {} vercred = content.to_json_c14n rescue nil if vercred.nil? return [nil, "invalid verifiableCredential input"] end retVal[:id] = content["id"] rescue nil if retVal[:id].nil? retVal[:id] = content["identifier"] rescue nil if retVal[:id].nil? return [nil, "invalid VC (missing id)"] end end issuer = content["issuer"].to_s rescue nil if issuer.nil? return [nil, "invalid VC (unknown issuer)"] exit end publicKey, msg = getPubKeyFromDID(issuer) if publicKey.nil? return [nil, "cannot verify public key"] exit end vc, vc_hash, errmsg = vc_proof_prep(JSON.parse(content.to_json), JSON.parse(content["proof"].to_json)) begin pubkey = Oydid.multi_decode(publicKey).first code = pubkey.bytes.first digest = pubkey[-32..] case Multicodecs[code].name when 'ed25519-pub' verify_key = Ed25519::VerifyKey.new(digest) signature_verification = false begin verify_key.verify(multi_decode(content["proof"]["proofValue"]).first, [vc_hash].pack('H*')) signature_verification = true rescue Ed25519::VerifyError signature_verification = false end if signature_verification return [retVal, nil] else return [nil, "proof signature does not match VC"] end else return [nil, "unsupported key codec"] end rescue return [nil, "unknown key codec"] end end |
.verify_vp(content, options) ⇒ Object
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 |
# File 'lib/oydid/vc.rb', line 511 def self.verify_vp(content, ) retVal = {} verpres = content.to_json_c14n rescue nil if verpres.nil? return [nil, "invalid verifiablePresetation input"] end retVal[:id] = content["id"] rescue nil if retVal[:id].nil? retVal[:id] = content["identifier"] rescue nil if retVal[:id].nil? return [nil, "invalid VP (missing id)"] end end holder = content["proof"]["verificationMethod"].to_s rescue nil if holder.nil? return [nil, "invalid VP (unknown holder"] end publicKey, msg = getPubKeyFromDID(holder) if publicKey.nil? return [nil, "cannot verify public key"] end # begin key_type = get_keytype(publicKey) case key_type # pubkey = Oydid.multi_decode(publicKey).first # code = pubkey.bytes.first # digest = pubkey[-32..] # case Multicodecs[code].name when 'ed25519-pub' pubkey = Oydid.multi_decode(publicKey).first code = pubkey.bytes.first digest = pubkey[-32..] verify_key = Ed25519::VerifyKey.new(digest) vp, vp_hash, errmsg = vc_proof_prep(JSON.parse(content.to_json), JSON.parse(content["proof"].to_json)) signature_verification = false begin verify_key.verify(multi_decode(content["proof"]["proofValue"]).first, [vp_hash].pack('H*')) signature_verification = true rescue Ed25519::VerifyError signature_verification = false end if signature_verification return [retVal, nil] else return [nil, "proof signature does not match VP"] end when 'p256-pub' jws = content["proof"]["jws"] head_b64, _, sig_b64 = jws.split('.') verpres = JSON.parse(verpres) verpres["proof"].delete("jws") verpres.delete("identifier") encoded_payload = Base64.urlsafe_encode64(verpres.to_json_c14n, padding: false) data_to_sign = "#{head_b64}.#{encoded_payload}" # puts 'data_to_sign: ' + data_to_sign.to_s # puts 'encoded_signature: ' + sig_b64.to_s # puts 'publicKey: ' + publicKey.to_s valid = verify(data_to_sign, sig_b64, publicKey).first if valid return [retVal, nil] else return [nil, "proof signature does not match VP"] end else return [nil, "unsupported key codec"] end # rescue # return [nil, "unknown key codec"] # end end |
.w3c(did_info, options) ⇒ Object
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 |
# File 'lib/oydid.rb', line 1101 def self.w3c(did_info, ) # check if doc is already W3C DID is_already_w3c_did = (did_info.transform_keys(&:to_s)["doc"]["doc"].has_key?("@context") && did_info.transform_keys(&:to_s)["doc"]["doc"].has_key?("id") && did_info.transform_keys(&:to_s)["doc"]["doc"]["id"].split(":").first == "did") rescue false if is_already_w3c_did return did_info.transform_keys(&:to_s)["doc"]["doc"] end did = percent_encode(did_info["did"]) if !did.start_with?("did:oyd:") did = "did:oyd:" + did end didDoc = did_info.dup.transform_keys(&:to_s)["doc"] pubDocKey = didDoc["key"].split(":")[0] rescue "" pubRevKey = didDoc["key"].split(":")[1] rescue "" delegateDocKeys = getDelegatedPubKeysFromDID(did, "doc").first - [pubDocKey] rescue [] if delegateDocKeys.is_a?(String) if delegateDocKeys == pubDocKey delegateDocKeys = nil else delegateDocKeys = [delegateDocKeys] end end delegateRevKeys = getDelegatedPubKeysFromDID(did, "rev").first - [pubRevKey] rescue [] if delegateRevKeys.is_a?(String) if delegateRevKeys == pubRevKey delegateRevKeys = nil else delegateRevKeys = [delegateRevKeys] end end oyd_context = ["https://www.w3.org/ns/did/v1"] pubkey = multi_decode(pubDocKey).first if pubkey.bytes.length == 34 code = pubkey.bytes.first digest = pubkey[-32..] else if pubkey.start_with?("\x80\x24".dup.force_encoding('ASCII-8BIT')) code = 4608 # Bytes 0x80 0x24 sind das Varint-Encoding des Multicodec-Codes 0x1200 (p256-pub) # 4608 == Oydid.read_varint("\x80$") oder "\x80\x24".force_encoding('ASCII-8BIT') else code = pubkey.unpack('n').first end digest = pubkey[-1*(pubkey.bytes.length-2)..] end case Multicodecs[code].name when 'ed25519-pub' oyd_context << "https://w3id.org/security/suites/ed25519-2020/v1" when 'p256-pub' oyd_context << "https://w3id.org/security/suites/jws-2020/v1" else return {"error": "unsupported key codec (" + Multicodecs[code].name.to_s + ")"} end wd = {} if didDoc["doc"].is_a?(Hash) if didDoc["doc"]["@context"].nil? wd["@context"] = oyd_context else if didDoc["doc"]["@context"].is_a?(Array) wd["@context"] = (oyd_context + didDoc["doc"]["@context"]).uniq else oyd_context << didDoc["doc"]["@context"] wd["@context"] = oyd_context.uniq end didDoc["doc"].delete("@context") end else wd["@context"] = oyd_context end wd["id"] = percent_encode(did) case Multicodecs[code].name when 'ed25519-pub' wd["verificationMethod"] = [{ "id": did + "#key-doc", "type": "Ed25519VerificationKey2020", "controller": did, "publicKeyMultibase": pubDocKey },{ "id": did + "#key-rev", "type": "Ed25519VerificationKey2020", "controller": did, "publicKeyMultibase": pubRevKey }] when 'p256-pub' pubDocKey_jwk, msg = public_key_to_jwk(pubDocKey) if pubDocKey_jwk.nil? return {"error": "document key: " + msg.to_s} end pubRevKey_jwk, msg = public_key_to_jwk(pubRevKey) if pubRevKey_jwk.nil? return {"error": "revocation key: " + msg.to_s} end wd["verificationMethod"] = [{ "id": did + "#key-doc", "type": "JsonWebKey2020", "controller": did, "publicKeyJwk": pubDocKey_jwk },{ "id": did + "#key-rev", "type": "JsonWebKey2020", "controller": did, "publicKeyJwk": pubRevKey_jwk }] else return {"error": "unsupported key codec (" + Multicodecs[code].name.to_s + ")"} end if !delegateDocKeys.nil? && delegateDocKeys.count > 0 i = 0 wd["capabilityDelegation"] = [] delegateDocKeys.each do |key| i += 1 delegaton_object = { "id": did + "#key-delegate-doc-" + i.to_s, "type": "Ed25519VerificationKey2020", "controller": did, "publicKeyMultibase": key } wd["capabilityDelegation"] << delegaton_object end end if !delegateRevKeys.nil? && delegateRevKeys.count > 0 i = 0 if wd["capabilityDelegation"].nil? wd["capabilityDelegation"] = [] end delegateRevKeys.each do |key| i += 1 delegaton_object = { "id": did + "#key-delegate-rev-" + i.to_s, "type": "Ed25519VerificationKey2020", "controller": did, "publicKeyMultibase": key } wd["capabilityDelegation"] << delegaton_object end end equivalentIds = [] did_info["log"].each do |log| if log["op"] == 2 || log["op"] == 3 eid = percent_encode("did:oyd:" + log["doc"]) if eid != did equivalentIds << eid end end end unless did_info["log"].nil? if equivalentIds.length > 0 wd["alsoKnownAs"] = equivalentIds end if didDoc["doc"].is_a?(Hash) && !didDoc["doc"]["service"].nil? location = [:location] if location.nil? location = get_location(did_info["did"].to_s) end wd = wd.merge(didDoc["doc"]) if wd["service"] != [] if wd["service"].is_a?(Array) wdf = wd["service"].first else wdf = wd["service"] end wdf = { "id": did + "#payload", "type": "Custom", "serviceEndpoint": location }.transform_keys(&:to_s).merge(wdf) if wdf["id"][0] == '#' wdf["id"] = did + wdf["id"] end wd["service"] = [wdf] + wd["service"].drop(1) end else payload = nil if didDoc["doc"].is_a?(Hash) if didDoc["doc"] != {} didDoc = didDoc["doc"] # special handling for Verification Methods vms = [ "authentication", "assertionMethod", "keyAgreement", "capabilityInvocation", "capabilityDelegation" ] vms.each do |vm| if didDoc[vm].to_s != "" new_entries = [] didDoc[vm].each do |el| if el.is_a?(String) new_entries << percent_encode(did) + el else new_el = el.transform_keys(&:to_s) new_el["id"] = percent_encode(did) + new_el["id"] new_entries << new_el end end unless didDoc[vm].nil? if new_entries.length > 0 wd[vm] = new_entries else wd[vm] = didDoc[vm] end didDoc.delete(vm) end end if didDoc["alsoKnownAs"].to_s != "" if didDoc["alsoKnownAs"].is_a?(Array) dda = didDoc["alsoKnownAs"] else dda = [didDoc["alsoKnownAs"]] end if wd["alsoKnownAs"].nil? wd["alsoKnownAs"] = dda else wd["alsoKnownAs"] += dda end didDoc.delete("alsoKnownAs") end payload = didDoc if payload == {} payload = nil end end else payload = didDoc["doc"] end if !payload.nil? location = [:location] if location.nil? location = get_location(did_info["did"].to_s) end if payload.is_a?(Array) && payload.length == 1 && payload.first.is_a?(Hash) && !payload.first["id"].nil? && !payload.first["type"].nil? && !payload.first["serviceEndpoint"].nil? wd["service"] = payload else wd["service"] = [{ "id": did + "#payload", "type": "Custom", "serviceEndpoint": location, "payload": payload }] end end end return wd end |
.w3c_legacy(did_info, options) ⇒ Object
1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 |
# File 'lib/oydid.rb', line 1355 def self.w3c_legacy(did_info, ) did = did_info["did"] if !did.start_with?("did:oyd:") did = "did:oyd:" + did end didDoc = did_info.transform_keys(&:to_s)["doc"] pubDocKey = didDoc["key"].split(":")[0] rescue "" pubRevKey = didDoc["key"].split(":")[1] rescue "" wd = {} wd["@context"] = ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"] wd["id"] = percent_encode(did) wd["verificationMethod"] = [{ "id": did + "#key-doc", "type": "Ed25519VerificationKey2020", "controller": did, "publicKeyMultibase": pubDocKey },{ "id": did + "#key-rev", "type": "Ed25519VerificationKey2020", "controller": did, "publicKeyMultibase": pubRevKey }] if didDoc["@context"] == ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"] didDoc.delete("@context") end if !didDoc["doc"].nil? newDidDoc = [] if didDoc.is_a?(Hash) if didDoc["authentication"].to_s != "" wd["authentication"] = didDoc["authentication"] didDoc.delete("authentication") end if didDoc["service"].to_s != "" if didDoc["service"].is_a?(Array) newDidDoc = didDoc.dup newDidDoc.delete("service") if newDidDoc == {} newDidDoc = [] else if !newDidDoc.is_a?(Array) newDidDoc=[newDidDoc] end end newDidDoc << didDoc["service"] newDidDoc = newDidDoc.flatten else newDidDoc = didDoc["service"] end else newDidDoc = didDoc["doc"] end else newDidDoc = didDoc["doc"] end wd["service"] = newDidDoc end return wd end |
.write(content, did, mode, options) ⇒ Object
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 |
# File 'lib/oydid.rb', line 638 def self.write(content, did, mode, ) did_doc, did_key, did_log, msg = generate_base(content, did, mode, ) if msg != "" if msg == "cmsm" return [did_doc, 'cmsm'] end return [nil, msg] end did = did_doc[:did] didDocument = did_doc[:didDocument] did_old = did_doc[:did_old] revoc_log = did_log[:revoc_log] l1 = did_log[:l1] l2 = did_log[:l2] r1 = did_log[:r1] r1_encrypted = did_log[:r1_encrypted] log_old = did_log[:log_old] privateKey = did_key[:privateKey] revocationKey = did_key[:revocationKey] # did, didDocument, revoc_log, l1, l2, r1, privateKey, revocationKey, did_old, log_old, msg = generate_base(content, did, mode, options) did_hash = did.delete_prefix("did:oyd:") did10 = did_hash[0,10] did_old_hash = did_old.delete_prefix("did:oyd:") rescue nil did10_old = did_old_hash[0,10] rescue nil doc_location = [:doc_location] if doc_location.to_s == "" if did_hash.include?(LOCATION_PREFIX) hash_split = did_hash.split(LOCATION_PREFIX) did_hash = hash_split[0] doc_location = hash_split[1] else doc_location = DEFAULT_LOCATION end end case doc_location.to_s when /^http/ logs = [revoc_log, l1, l2, r1_encrypted].flatten.compact else logs = [log_old, revoc_log, l1, l2].flatten.compact if !did_old.nil? write_private_storage([log_old, revoc_log, l1, l2].flatten.compact.to_json, did10_old + ".log") end end success, msg = publish(did, didDocument, logs, ) if success didDocumentBackup = Marshal.load(Marshal.dump(didDocument)) w3c_input = { "did" => did.clone, "doc" => didDocument.clone } doc_w3c = w3c(w3c_input, ) didDocument = didDocumentBackup retVal = { "did" => did, "doc" => didDocument, "doc_w3c" => doc_w3c, "log" => logs } if [:return_secrets] retVal["private_key"] = privateKey retVal["revocation_key"] = revocationKey retVal["revocation_log"] = r1 else write_private_storage(privateKey, did10 + "_private_key.enc") write_private_storage(revocationKey, did10 + "_revocation_key.enc") write_private_storage(r1.to_json, did10 + "_revocation.json") end return [retVal, ""] else return [nil, msg] end end |
.write_log(did, log, options = {}) ⇒ Object
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 |
# File 'lib/oydid.rb', line 717 def self.write_log(did, log, = {}) # validate log if !log.is_a?(Hash) return [nil, "invalid log input"] end log = log.transform_keys(&:to_s) if log["ts"].nil? return [nil, "missing timestamp in log"] end if log["op"].nil? return [nil, "missing operation in log"] end if log["doc"].nil? return [nil, "missing doc entry in log"] end if log["sig"].nil? return [nil, "missing signature in log"] end # validate did if did.include?(LOCATION_PREFIX) tmp = did.split(LOCATION_PREFIX) did = tmp[0] source_location = tmp[1] log_location = tmp[1] end if did.include?(CGI.escape LOCATION_PREFIX) tmp = did.split(CGI.escape LOCATION_PREFIX) did = tmp[0] source_location = tmp[1] log_location = tmp[1] end if source_location.to_s == "" if [:doc_location].nil? source_location = DEFAULT_LOCATION else source_location = [:doc_location] end if [:log_location].nil? log_location = DEFAULT_LOCATION else log_location = [:log_location] end end [:doc_location] = source_location [:log_location] = log_location source_did, msg = read(did, ) if source_did.nil? return [nil, "cannot resolve DID (on writing logs)"] end if source_did["error"] != 0 return [nil, source_did["message"].to_s] end if source_did["doc_log_id"].nil? return [nil, "cannot parse DID log"] end # write log source_location = source_location.gsub("%3A",":") source_location = source_location.gsub("%2F%2F","//") retVal = HTTParty.post(source_location + "/log/" + did, headers: { 'Content-Type' => 'application/json' }, body: {"log": log}.to_json ) code = retVal.code rescue 500 if code != 200 err_msg = retVal.parsed_response["error"].to_s rescue "invalid response from " + source_location.to_s + "/log" return ["", err_msg] end log_hash = retVal.parsed_response["log"] rescue "" if log_hash == "" err_msg = "missing log hash from " + source_location.to_s + "/log" return ["", err_msg] end return [log_hash, nil] end |
.write_private_storage(payload, filename) ⇒ Object
storage functions —————————–
1008 1009 1010 |
# File 'lib/oydid/basic.rb', line 1008 def self.write_private_storage(payload, filename) File.open(filename, 'w') {|f| f.write(payload)} end |
Instance Method Details
#jweh(key) ⇒ Object
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 |
# File 'lib/oydid/basic.rb', line 621 def jweh(key) pub_key=key[-64..-1] prv_key=key[0..-65] hex_pub=pub_key bin_pub=[hex_pub].pack('H*') int_pub=RbNaCl::PublicKey.new(bin_pub) len_pub=int_pub.to_bytes.bytesize enc_pub=multi_encode([Multicodecs["x25519-pub"].code,len_pub,int_pub].pack("CCa#{len_pub}"),{}).first hex_prv=prv_key bin_prv=[hex_prv].pack('H*') int_prv=RbNaCl::PrivateKey.new(bin_prv) len_prv=int_prv.to_bytes.bytesize enc_prv=multi_encode([Multicodecs["ed25519-priv"].code,len_prv,int_prv].pack("SCa#{len_prv}"),{}).first return [enc_pub, enc_prv] end |