Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small AIP-2 updates #1056

Closed
2 changes: 1 addition & 1 deletion aries_cloudagent/commands/tests/test_provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class TestProvision(AsyncTestCase):
def test_bad_calls(self):
with self.assertRaises(ArgsParseError):
with self.assertRaises(SystemExit):
test_module.execute([])

with self.assertRaises(SystemExit):
Expand Down
2 changes: 1 addition & 1 deletion aries_cloudagent/commands/tests/test_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class TestStart(AsyncTestCase):
def test_bad_args(self):
with self.assertRaises(ArgsParseError):
with self.assertRaises(SystemExit):
test_module.execute([])

with self.assertRaises(SystemExit):
Expand Down
34 changes: 12 additions & 22 deletions aries_cloudagent/config/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ def get_settings(args: Namespace):
for group in group_inst:
settings.update(group.get_settings(args))
except ArgsParseError as e:
parser.print_help()
raise e
# this is defined to exit or raise an exception and is
# consistent with an error being raised while the arguments
# are being parsed
parser.error(str(e))
return settings

return get_settings
Expand Down Expand Up @@ -651,6 +653,13 @@ class ProtocolGroup(ArgumentGroup):

def add_arguments(self, parser: ArgumentParser):
"""Add protocol-specific command line arguments to the parser."""
parser.add_argument(
"--aip-version",
type=BoundedInt(1, 2),
default=1,
env_var="ACAPY_AIP_VERSION",
help="Set the Aries Interop Profile compatibility version (default 1).",
)
parser.add_argument(
"--auto-ping-connection",
action="store_true",
Expand Down Expand Up @@ -725,22 +734,6 @@ def add_arguments(self, parser: ArgumentParser):
env_var="ACAPY_PRESERVE_EXCHANGE_RECORDS",
help="Keep credential exchange records after exchange has completed.",
)
parser.add_argument(
"--emit-new-didcomm-prefix",
action="store_true",
env_var="ACAPY_EMIT_NEW_DIDCOMM_PREFIX",
help="Emit protocol messages with new DIDComm prefix; i.e.,\
'https://didcomm.org/' instead of (default) prefix\
'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/'.",
)
parser.add_argument(
"--emit-new-didcomm-mime-type",
action="store_true",
env_var="ACAPY_EMIT_NEW_DIDCOMM_MIME_TYPE",
help="Send packed agent messages with the DIDComm MIME type\
as of RFC 0044; i.e., 'application/didcomm-envelope-enc'\
instead of 'application/ssi-agent-wire'.",
)
parser.add_argument(
"--exch-use-unencrypted-tags",
action="store_true",
Expand All @@ -752,6 +745,7 @@ def add_arguments(self, parser: ArgumentParser):
def get_settings(self, args: Namespace) -> dict:
"""Get protocol settings."""
settings = {}
settings["aip_version"] = args.aip_version
if args.auto_ping_connection:
settings["auto_ping_connection"] = True
if args.invite_base_url:
Expand Down Expand Up @@ -796,10 +790,6 @@ def get_settings(self, args: Namespace) -> dict:
raise ArgsParseError("Error writing trace event " + str(e))
if args.preserve_exchange_records:
settings["preserve_exchange_records"] = True
if args.emit_new_didcomm_prefix:
settings["emit_new_didcomm_prefix"] = True
if args.emit_new_didcomm_mime_type:
settings["emit_new_didcomm_mime_type"] = True
if args.exch_use_unencrypted_tags:
settings["exch_use_unencrypted_tags"] = True
environ["EXCH_UNENCRYPTED_TAGS"] = "True"
Expand Down
4 changes: 3 additions & 1 deletion aries_cloudagent/config/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import logging

from typing import Tuple

from ..core.error import ProfileNotFoundError
from ..core.profile import Profile, ProfileManager
from ..wallet.base import BaseWallet, DIDInfo
Expand All @@ -17,7 +19,7 @@

async def wallet_config(
context: InjectionContext, provision: bool = False
) -> (Profile, DIDInfo):
) -> Tuple[Profile, DIDInfo]:
"""Initialize the root profile."""

mgr = context.inject(ProfileManager)
Expand Down
7 changes: 6 additions & 1 deletion aries_cloudagent/connections/base_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,15 @@ async def create_did_document(

for (endpoint_index, svc_endpoint) in enumerate(svc_endpoints or []):
endpoint_ident = "indy" if endpoint_index == 0 else f"indy{endpoint_index}"
service_type = (
"did-communication"
if self._session.settings.get("aip_version", 1) >= 2
else "IndyAgent"
)
service = Service(
did_info.did,
endpoint_ident,
"IndyAgent",
service_type,
[pk],
routing_keys,
svc_endpoint,
Expand Down
134 changes: 75 additions & 59 deletions aries_cloudagent/connections/models/diddoc/diddoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@

from .publickey import PublicKey, PublicKeyType
from .service import Service
from .util import canon_did, canon_ref, ok_did, resource
from .util import canon_ref

LOGGER = logging.getLogger(__name__)


def _parseError(msg: str):
LOGGER.debug(msg)
raise ValueError(msg)


class DIDDoc:
"""
DID document, grouping a DID with verification keys and services.
Expand All @@ -38,7 +43,8 @@ class DIDDoc:
everything else as URIs (oriented toward W3C-facing operations).
"""

CONTEXT = "https://w3id.org/did/v1"
CONTEXT_V0 = "https://w3id.org/did/v1"
CONTEXT_V1 = "https://www.w3.org/ns/did/v1"

def __init__(self, did: str = None) -> None:
"""
Expand All @@ -55,7 +61,7 @@ def __init__(self, did: str = None) -> None:

"""

self._did = canon_did(did) if did else None # allow specification post-hoc
self._did = did
self._pubkey = {}
self._service = {}

Expand All @@ -78,7 +84,7 @@ def did(self, value: str) -> None:

"""

self._did = canon_did(value) if value else None
self._did = value

@property
def pubkey(self) -> dict:
Expand Down Expand Up @@ -121,29 +127,47 @@ def set(self, item: Union[Service, PublicKey]) -> "DIDDoc":
"Cannot add item {} to DIDDoc on DID {}".format(item, self.did)
)

def serialize(self) -> dict:
def serialize(self, *, version: int = 0) -> dict:
"""
Dump current object to a JSON-compatible dictionary.

Args:
version: Define the version of the spec to use in serialization

Returns:
dict representation of current DIDDoc

"""

return {
"@context": DIDDoc.CONTEXT,
"id": canon_ref(self.did, self.did),
"publicKey": [pubkey.to_dict() for pubkey in self.pubkey.values()],
"authentication": [
{
"type": pubkey.type.authn_type,
"publicKey": canon_ref(self.did, pubkey.id),
}
for pubkey in self.pubkey.values()
if pubkey.authn
],
"service": [service.to_dict() for service in self.service.values()],
}
if version == 0:
return {
"@context": DIDDoc.CONTEXT_V0,
"id": self.did,
"publicKey": [pubkey.to_dict() for pubkey in self.pubkey.values()],
"authentication": [
{
"type": pubkey.type.authn_type,
"publicKey": canon_ref(self.did, pubkey.id),
}
for pubkey in self.pubkey.values()
if pubkey.authn
],
"service": [service.to_dict() for service in self.service.values()],
}
elif version == 1:
return {
"@context": DIDDoc.CONTEXT_V1,
"id": self.did,
"verificationMethod": [
pubkey.to_dict() for pubkey in self.pubkey.values()
],
"authentication": [
pubkey.id for pubkey in self.pubkey.values() if pubkey.authn
],
"service": [service.to_dict() for service in self.service.values()],
}
else:
raise ValueError(f"Unsupported version for serialization: {version}")

def to_json(self) -> str:
"""
Expand Down Expand Up @@ -228,61 +252,53 @@ def deserialize(cls, did_doc: dict) -> "DIDDoc":

"""

rv = None
if "id" in did_doc:
rv = DIDDoc(did_doc["id"])
else:
# heuristic: get DID to serve as DID document identifier from
# the first OK-looking public key
for section in ("publicKey", "authentication"):
if rv is None and section in did_doc:
for key_spec in did_doc[section]:
try:
pubkey_did = canon_did(resource(key_spec.get("id", "")))
if ok_did(pubkey_did):
rv = DIDDoc(pubkey_did)
break
except ValueError: # no identifier here, move on to next
break
if rv is None:
LOGGER.debug("no identifier in DID document")
raise ValueError("No identifier in DID document")
if "id" not in did_doc:
_parseError("no identifier in DID document")

for pubkey in did_doc.get(
"publicKey", {}
): # include all public keys, authentication pubkeys by reference
pubkey_type = PublicKeyType.get(pubkey["type"])
authn = any(
canon_ref(rv.did, ak.get("publicKey", ""))
== canon_ref(rv.did, pubkey["id"])
for ak in did_doc.get("authentication", {})
if isinstance(ak.get("publicKey", None), str)
)
key = PublicKey( # initialization canonicalizes id
rv.did,
pubkey["id"],
pubkey[pubkey_type.specifier],
pubkey_type,
canon_did(pubkey["controller"]),
authn,
)
rv.pubkey[key.id] = key
rv = DIDDoc(did_doc["id"])

auth_key_ids = set()
for akey in did_doc.get(
"authentication", {}
): # include embedded authentication keys
if "publicKey" not in akey: # not yet got it with public keys
if isinstance(akey, str):
auth_key_ids.add(canon_ref(rv.did, akey))
elif "publicKey" in akey:
# v0 representation
auth_key_ids.add(canon_ref(rv.did, akey["publicKey"]))
else:
pubkey_type = PublicKeyType.get(akey["type"])
key = PublicKey( # initialization canonicalized id
rv.did,
akey["id"],
akey[pubkey_type.specifier],
pubkey_type,
canon_did(akey["controller"]),
akey["controller"],
True,
)
if key.id in rv.pubkey:
_parseError(f"duplicate key id: {key.id}")
rv.pubkey[key.id] = key

pubkeys = did_doc.get("verificationMethod", did_doc.get("publicKey")) or {}
for (
pubkey
) in pubkeys: # include all public keys, authentication pubkeys by reference
pubkey_type = PublicKeyType.get(pubkey["type"])
key = PublicKey( # initialization canonicalizes id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could lose the comment now: it is an orphan

rv.did,
pubkey["id"],
pubkey[pubkey_type.specifier],
pubkey_type,
pubkey["controller"],
False,
)
if key.id in auth_key_ids:
key.authn = True
if key.id in rv.pubkey:
_parseError(f"duplicate key id: {key.id}")
rv.pubkey[key.id] = key

for service in did_doc.get("service", {}):
endpoint = service["serviceEndpoint"]
svc = Service( # initialization canonicalizes id
Expand Down
6 changes: 3 additions & 3 deletions aries_cloudagent/connections/models/diddoc/publickey.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from collections import namedtuple
from enum import Enum

from .util import canon_did, canon_ref
from .util import canon_ref


LinkedDataKeySpec = namedtuple("LinkedDataKeySpec", "ver_type authn_type specifier")
Expand Down Expand Up @@ -126,11 +126,11 @@ def __init__(

"""

self._did = canon_did(did)
self._did = did
self._id = canon_ref(self._did, ident)
self._value = value
self._type = pk_type or PublicKeyType.ED25519_SIG_2018
self._controller = canon_did(controller) if controller else self._did
self._controller = controller or self._did
self._authn = authn

@property
Expand Down
4 changes: 2 additions & 2 deletions aries_cloudagent/connections/models/diddoc/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from typing import List, Sequence, Union

from .util import canon_did, canon_ref
from .util import canon_ref
from .publickey import PublicKey


Expand Down Expand Up @@ -62,7 +62,7 @@ def __init__(

"""

self._did = canon_did(did)
self._did = did
self._id = canon_ref(self._did, ident, ";")
self._type = typ
self._recip_keys = (
Expand Down
Loading