2024-05-24 12:25:45 +00:00
|
|
|
import json
|
|
|
|
import argparse
|
2024-05-30 09:11:37 +00:00
|
|
|
import requests
|
2024-05-24 10:38:48 +00:00
|
|
|
import multicodec
|
|
|
|
import multiformats
|
|
|
|
import nacl.signing
|
|
|
|
import nacl.encoding
|
|
|
|
|
|
|
|
from jwcrypto import jwk
|
2024-05-30 09:11:37 +00:00
|
|
|
from urllib.parse import urlparse
|
2024-05-24 10:38:48 +00:00
|
|
|
from nacl.signing import SigningKey
|
|
|
|
from nacl.encoding import RawEncoder
|
2024-05-30 09:11:37 +00:00
|
|
|
from templates import did_document_tmpl
|
2024-05-24 10:38:48 +00:00
|
|
|
|
|
|
|
|
2024-05-30 09:11:37 +00:00
|
|
|
def key_to_did(public_key_bytes, url):
|
2024-05-24 10:38:48 +00:00
|
|
|
"""did-key-format :=
|
|
|
|
did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))"""
|
|
|
|
|
|
|
|
mc = multicodec.add_prefix('ed25519-pub', public_key_bytes)
|
|
|
|
|
|
|
|
# Multibase encode the hashed bytes
|
|
|
|
did = multiformats.multibase.encode(mc, 'base58btc')
|
|
|
|
|
2024-05-30 09:11:37 +00:00
|
|
|
if url:
|
|
|
|
u = urlparse(url)
|
|
|
|
domain = u.netloc
|
|
|
|
path = u.path.strip("/").replace("/", ":")
|
|
|
|
return f"did:web:{domain}:{path}:{did}"
|
2024-05-24 10:38:48 +00:00
|
|
|
|
|
|
|
return f"did:key:{did}"
|
|
|
|
|
|
|
|
|
2024-05-24 12:25:45 +00:00
|
|
|
def key_read(path_keys):
|
2024-05-24 10:38:48 +00:00
|
|
|
# Save the private JWK to a file
|
2024-05-24 12:25:45 +00:00
|
|
|
with open(path_keys, 'r') as f:
|
2024-05-24 10:38:48 +00:00
|
|
|
private_jwk = f.read()
|
|
|
|
|
2024-05-28 11:40:39 +00:00
|
|
|
return private_jwk
|
2024-05-24 10:38:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_signing_key(jwk_pr):
|
2024-05-27 09:41:33 +00:00
|
|
|
key = json.loads(jwk_pr)
|
|
|
|
private_key_material_str = key['d']
|
2024-05-24 10:38:48 +00:00
|
|
|
missing_padding = len(private_key_material_str) % 4
|
|
|
|
if missing_padding:
|
|
|
|
private_key_material_str += '=' * (4 - missing_padding)
|
|
|
|
|
|
|
|
private_key_material = nacl.encoding.URLSafeBase64Encoder.decode(private_key_material_str)
|
|
|
|
signing_key = SigningKey(private_key_material, encoder=RawEncoder)
|
|
|
|
return signing_key
|
|
|
|
|
|
|
|
|
2024-05-30 09:11:37 +00:00
|
|
|
def generate_did(jwk_pr, url=None):
|
2024-05-24 10:38:48 +00:00
|
|
|
signing_key = get_signing_key(jwk_pr)
|
|
|
|
verify_key = signing_key.verify_key
|
|
|
|
public_key_bytes = verify_key.encode()
|
|
|
|
|
|
|
|
# Generate the DID
|
2024-05-30 09:11:37 +00:00
|
|
|
did = key_to_did(public_key_bytes, url)
|
2024-05-24 10:38:48 +00:00
|
|
|
return did
|
|
|
|
|
|
|
|
def generate_keys():
|
|
|
|
# Generate an Ed25519 key pair
|
|
|
|
key = jwk.JWK.generate(kty='OKP', crv='Ed25519')
|
|
|
|
key['kid'] = 'Generated'
|
2024-05-27 09:41:33 +00:00
|
|
|
key_json = key.export_private(True)
|
|
|
|
return json.dumps(key_json)
|
2024-05-24 10:38:48 +00:00
|
|
|
|
|
|
|
|
2024-05-30 09:11:37 +00:00
|
|
|
def gen_did_document(did, keys):
|
|
|
|
if did[:8] != "did:web:":
|
|
|
|
return "", ""
|
|
|
|
document = did_document_tmpl.copy()
|
|
|
|
webdid_owner = did+"#owner"
|
|
|
|
webdid_revocation = did+"#revocation"
|
|
|
|
document["id"] = did
|
|
|
|
document["verificationMethod"][0]["id"] = webdid_owner
|
|
|
|
document["verificationMethod"][0]["controller"] = did
|
|
|
|
document["verificationMethod"][0]["publicKeyJwk"]["x"] = keys["x"]
|
|
|
|
document["authentication"].append(webdid_owner)
|
|
|
|
document["assertionMethod"].append(webdid_owner)
|
|
|
|
document["service"][0]["id"] = webdid_revocation
|
|
|
|
document_fixed_serialized = json.dumps(document)
|
|
|
|
url = "https://" + "/".join(did.split(":")[2:]) + "/did.json"
|
|
|
|
|
|
|
|
return url, document_fixed_serialized
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_did(did):
|
|
|
|
if did[:8] != "did:web:":
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
url = "https://" + "/".join(did.split(":")[2:]) + "/did.json"
|
|
|
|
response = requests.get(url)
|
|
|
|
except Exception:
|
|
|
|
url = "http://" + "/".join(did.split(":")[2:]) + "/did.json"
|
|
|
|
response = requests.get(url)
|
|
|
|
if 200 <= response.status_code < 300:
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
|
2024-05-24 12:25:45 +00:00
|
|
|
def main():
|
|
|
|
parser=argparse.ArgumentParser(description='Generates a new did or key pair')
|
|
|
|
parser.add_argument("-k", "--key-path", required=False)
|
|
|
|
parser.add_argument("-n", "--new", choices=['keys', 'did'])
|
2024-05-30 09:11:37 +00:00
|
|
|
parser.add_argument("-u", "--url")
|
|
|
|
parser.add_argument("-g", "--gen-doc")
|
2024-05-24 12:25:45 +00:00
|
|
|
args=parser.parse_args()
|
2024-05-24 10:38:48 +00:00
|
|
|
|
2024-05-24 12:25:45 +00:00
|
|
|
if args.new == 'keys':
|
2024-05-24 15:22:35 +00:00
|
|
|
keyspair = generate_keys()
|
2024-05-28 11:40:39 +00:00
|
|
|
print(keyspair)
|
2024-05-24 12:25:45 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if not args.key_path and args.new == 'did':
|
|
|
|
print("error: argument --key-path: expected one argument")
|
|
|
|
return
|
|
|
|
|
2024-05-30 09:11:37 +00:00
|
|
|
if args.new == 'did' and args.url:
|
|
|
|
key = key_read(args.key_path)
|
|
|
|
did = generate_did(key, args.url)
|
|
|
|
print(did)
|
|
|
|
return
|
|
|
|
|
2024-05-24 12:25:45 +00:00
|
|
|
if args.new == 'did':
|
|
|
|
key = key_read(args.key_path)
|
2024-05-24 15:22:35 +00:00
|
|
|
did = generate_did(key)
|
2024-05-28 11:40:39 +00:00
|
|
|
print(did)
|
2024-05-24 12:25:45 +00:00
|
|
|
return
|
|
|
|
|
2024-05-30 09:11:37 +00:00
|
|
|
if args.gen_doc and not args.key_path:
|
|
|
|
print("error: argument --key-path: expected one argument")
|
|
|
|
return
|
|
|
|
|
|
|
|
if args.gen_doc:
|
|
|
|
keys = json.loads(key_read(args.key_path))
|
|
|
|
if not keys.get("x"):
|
|
|
|
print("error: argument --key-path: not is valid")
|
|
|
|
return
|
|
|
|
|
|
|
|
url, doc = gen_did_document(args.gen_doc, keys)
|
|
|
|
# print(url)
|
|
|
|
print(doc)
|
|
|
|
return
|
2024-05-24 12:25:45 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|