oydid 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/oydid.rb +68 -31
  4. data/spec/oydid_spec.rb +56 -0
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e119ee021cb7e09dd4c7ee6273a4ee778bae850e31b6360a31044c11e674df4c
4
- data.tar.gz: 9be71c4690f1e6476c6a339a49172575eac79c11406c6a1ac8a49e66c804ee6e
3
+ metadata.gz: 66decf23cb4e474d0703a8948e20474be619c05083bd8abd5ab8d45857ac4ac0
4
+ data.tar.gz: db83f3c7ba0a20c5e4f8fd8ba8b9977b42b9b78fc56ad11df8a8bf12e9657704
5
5
  SHA512:
6
- metadata.gz: fa57e8c74580e2afa7e6adae48528fd8d43fd306176eb9039a1531c9db46c0090702eef2d036ddaee3aef09e568ef8ad2714c990e6745b2dff4ed080d5e18874
7
- data.tar.gz: 4608b92f08da163850832144c89a9495c182a065155727a8d10bacb83ee00fd47951b442420e9ee24582a5804938349e55649b57fe0e699082aa3311fbc57729
6
+ metadata.gz: 8856635688f0dd47b246443d7fcc7a994ceef87e7d2594530da0f3489445a544f3c7cc9d20327fec7da3988e83872d1ddf3c854bd4db25b367b72fa917ba09c3
7
+ data.tar.gz: '08956cf2da8ed63a35261a7ffa01cb58b11a177c77fe8b5f873f8ed4d59592695c0e80bd72d118c28f4266728b10e420b4eaab7189c406dd6c7731e52f40e38d'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 0.8.2
data/lib/oydid.rb CHANGED
@@ -1058,6 +1058,18 @@ class Oydid
1058
1058
  return [nil, did_info["message"].to_s]
1059
1059
  end
1060
1060
 
1061
+ # A client that holds no private revocation key - a secure element does
1062
+ # not export one - submits the record it kept from creating or updating
1063
+ # the DID. Rebuilding it here is impossible, and re-signing it would not
1064
+ # help: the hash committed in the TERMINATE entry would no longer match.
1065
+ if !options[:log_revoke].nil?
1066
+ verified, vmsg = verify_revocation_log(did_info, options[:log_revoke], "log_revoke", options)
1067
+ if verified.nil?
1068
+ return [nil, vmsg]
1069
+ end
1070
+ return [link_revocation_log(verified, did_info), ""]
1071
+ end
1072
+
1061
1073
  did = did_info["did"]
1062
1074
  did_hash = did.delete_prefix("did:oyd:")
1063
1075
  did10 = did_hash[0,10]
@@ -1224,6 +1236,15 @@ class Oydid
1224
1236
  # the location extracted from the DID may be percent-encoded
1225
1237
  # (e.g. "http%3A%2F%2Flocalhost%3A3000"); decode before using as URL
1226
1238
  doc_location = doc_location.to_s.gsub("%3A", ":").gsub("%2F", "/")
1239
+ # percent_encode strips the default scheme, so a DID published to an
1240
+ # https location carries a bare host ("oydid.ownyourdata.eu"). Without
1241
+ # putting the scheme back this is not recognised as a remote location
1242
+ # and the revocation is written to a local file instead - which is how
1243
+ # the branch below used to be reached at all.
1244
+ if !(doc_location == "" || doc_location == "local") &&
1245
+ !doc_location.start_with?("http")
1246
+ doc_location = "https://" + doc_location
1247
+ end
1227
1248
 
1228
1249
  # publish revocation log based on location
1229
1250
  case doc_location.to_s
@@ -1236,10 +1257,10 @@ class Oydid
1236
1257
  return [nil, msg]
1237
1258
  end
1238
1259
  else
1239
- File.write(did10 + ".log", revoc_log.to_json)
1240
- if !did_old.nil?
1241
- File.write(did10_old + ".log", revoc_log.to_json)
1242
- end
1260
+ # did_old / did10_old were never defined here - they are locals of
1261
+ # generate_base. Writing the revocation next to the previous DID is
1262
+ # the job of the caller that knows about it.
1263
+ write_private_storage(revoc_log.to_json, did10 + ".log")
1243
1264
  end
1244
1265
 
1245
1266
  return [did, ""]
@@ -1367,6 +1388,44 @@ class Oydid
1367
1388
  end
1368
1389
  end
1369
1390
 
1391
+ # Verification relationships in the payload either name a key by fragment
1392
+ # ("#key-doc") or embed a whole verification method. Both need the DID
1393
+ # prefixed onto the identifier; an embedded method additionally needs a
1394
+ # controller, which the client cannot supply - the DID is the hash of the
1395
+ # document that would have to contain it, so it is not known while the
1396
+ # document is being written.
1397
+ def self.expand_verification_methods(payload, wd, did)
1398
+ return wd if !payload.is_a?(Hash)
1399
+
1400
+ [ "authentication",
1401
+ "assertionMethod",
1402
+ "keyAgreement",
1403
+ "capabilityInvocation",
1404
+ "capabilityDelegation" ].each do |vm|
1405
+ next if payload[vm].to_s == ""
1406
+ entries = payload[vm]
1407
+ entries = [entries] if entries.is_a?(Hash) || entries.is_a?(String)
1408
+
1409
+ new_entries = []
1410
+ entries.each do |el|
1411
+ if el.is_a?(String)
1412
+ new_entries << percent_encode(did) + el
1413
+ else
1414
+ new_el = el.transform_keys(&:to_s)
1415
+ new_el["id"] = percent_encode(did) + new_el["id"].to_s
1416
+ if new_el["controller"].to_s == ""
1417
+ new_el["controller"] = percent_encode(did)
1418
+ end
1419
+ new_entries << new_el
1420
+ end
1421
+ end
1422
+
1423
+ wd[vm] = new_entries.length > 0 ? new_entries : payload[vm]
1424
+ payload.delete(vm)
1425
+ end
1426
+ wd
1427
+ end
1428
+
1370
1429
  def self.w3c(did_info, options)
1371
1430
  # check if doc is already W3C DID
1372
1431
  is_already_w3c_did = (did_info.transform_keys(&:to_s)["doc"]["doc"].has_key?("@context") &&
@@ -1527,6 +1586,10 @@ class Oydid
1527
1586
  if location.nil?
1528
1587
  location = get_location(did_info["did"].to_s)
1529
1588
  end
1589
+ # a payload carrying a service used to skip the handling below
1590
+ # entirely and get merged verbatim, which left fragment identifiers
1591
+ # unresolved and embedded methods without a controller
1592
+ wd = expand_verification_methods(didDoc["doc"], wd, did)
1530
1593
  wd = wd.merge(didDoc["doc"])
1531
1594
  if wd["service"] != []
1532
1595
  if wd["service"].is_a?(Array)
@@ -1547,33 +1610,7 @@ class Oydid
1547
1610
  if didDoc["doc"].is_a?(Hash)
1548
1611
  if didDoc["doc"] != {}
1549
1612
  didDoc = didDoc["doc"]
1550
- # special handling for Verification Methods
1551
- vms = [ "authentication",
1552
- "assertionMethod",
1553
- "keyAgreement",
1554
- "capabilityInvocation",
1555
- "capabilityDelegation" ]
1556
-
1557
- vms.each do |vm|
1558
- if didDoc[vm].to_s != ""
1559
- new_entries = []
1560
- didDoc[vm].each do |el|
1561
- if el.is_a?(String)
1562
- new_entries << percent_encode(did) + el
1563
- else
1564
- new_el = el.transform_keys(&:to_s)
1565
- new_el["id"] = percent_encode(did) + new_el["id"]
1566
- new_entries << new_el
1567
- end
1568
- end unless didDoc[vm].nil?
1569
- if new_entries.length > 0
1570
- wd[vm] = new_entries
1571
- else
1572
- wd[vm] = didDoc[vm]
1573
- end
1574
- didDoc.delete(vm)
1575
- end
1576
- end
1613
+ wd = expand_verification_methods(didDoc, wd, did)
1577
1614
  if didDoc["alsoKnownAs"].to_s != ""
1578
1615
  if didDoc["alsoKnownAs"].is_a?(Array)
1579
1616
  dda = didDoc["alsoKnownAs"]
data/spec/oydid_spec.rb CHANGED
@@ -528,4 +528,60 @@ describe "OYDID handling" do
528
528
  end
529
529
  end
530
530
 
531
+ # W3C conversion of verification relationships carried in the payload
532
+ describe "verification relationships in the payload" do
533
+ let(:did) { "did:oyd:zQmaBZTghndXTgxNwfbdpVLWdFf6faYE4oeuN2zzXdQt1kh" }
534
+ let(:keys) do
535
+ "z6MktULudTtAsAhRegYPiZ6631RV3viv12qd4GQF8z1xB22S:" \
536
+ "z6MkqGC3nWZhYieEVTVDKW5v588CiGfsDSmRVG9ZwwWTvLSK"
537
+ end
538
+ let(:attestation) do
539
+ { "id" => "#key-attest",
540
+ "type" => "Ed25519VerificationKey2020",
541
+ "publicKeyMultibase" => "z6Mkg49NtQR2LyYRDCQFK4w1VVHqhypZSSRo7HsyuN7SV7v5" }
542
+ end
543
+
544
+ def convert(payload)
545
+ Oydid.w3c({ "did" => did, "doc" => { "doc" => payload, "key" => keys } }, {})
546
+ end
547
+
548
+ it "prefixes the DID onto a key named by fragment" do
549
+ wd = convert({ "authentication" => ["#key-doc"] })
550
+ expect(wd["authentication"]).to eq ["#{did}#key-doc"]
551
+ end
552
+
553
+ it "prefixes the DID onto an embedded verification method" do
554
+ wd = convert({ "assertionMethod" => [attestation] })
555
+ expect(wd["assertionMethod"].first["id"]).to eq "#{did}#key-attest"
556
+ end
557
+
558
+ # a client cannot supply the controller: the DID is the hash of the
559
+ # document that would contain it
560
+ it "fills in the controller of an embedded verification method" do
561
+ wd = convert({ "assertionMethod" => [attestation] })
562
+ expect(wd["assertionMethod"].first["controller"]).to eq did
563
+ end
564
+
565
+ it "keeps a controller the payload already carries" do
566
+ own = attestation.merge("controller" => "did:oyd:zSomeoneElse")
567
+ wd = convert({ "assertionMethod" => [own] })
568
+ expect(wd["assertionMethod"].first["controller"]).to eq "did:oyd:zSomeoneElse"
569
+ end
570
+
571
+ # regression: a payload carrying a service took a different branch and was
572
+ # merged verbatim, leaving fragments unresolved
573
+ it "handles relationships next to a service entry" do
574
+ wd = convert({ "assertionMethod" => [attestation],
575
+ "service" => [{ "id" => "#payload", "type" => "Custom" }] })
576
+ expect(wd["assertionMethod"].first["id"]).to eq "#{did}#key-attest"
577
+ expect(wd["assertionMethod"].first["controller"]).to eq did
578
+ expect(wd["service"]).to be_an(Array)
579
+ end
580
+
581
+ it "accepts a single embedded method that is not wrapped in an array" do
582
+ wd = convert({ "assertionMethod" => attestation })
583
+ expect(wd["assertionMethod"].first["id"]).to eq "#{did}#key-attest"
584
+ end
585
+ end
586
+
531
587
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oydid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Fabianek
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-08-22 00:00:00.000000000 Z
10
+ date: 2026-08-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: simple_dag