Device Name System


Provision A Name


#documentation




Architecture <

This is a free RESTful service providing Public Key and IPv6 Phonebook. Subject to passing Turing test (CAPTCHA) you can associate an arbitrary Unicode name, whether it is pseudonym, email or your real name, with an EdDSA public key. Once this association is established (you have provisioned a name), applications can automatically change existing or add more public keys as well as adding or changing IPv6 address associated with the name.

The service was originally designed as a backend for applications enabling peer-to-peer secure exchange. It will also help as a DNS complementary, where domain structure of names is too much to ask in the age of IoT. The names live on the system two years past the last query for associated address or key. Details of use can be seen from the following examples given in python or on Linux command line.

Currently, there are three supported requests:

https://densys.net/<name>/key

https://densys.net/<name>/pki

https://densys.net/<name>/gua

Each file can be up to 4096 bytes in size and returned on HTTP GET as a binary file. First 32 bytes of the key file is EdDSA verification (public) key, no requirements for other bytes – your application can use them as you see fit. The key file of 32 bytes is the only file created in provisioning.

Public Key Infrastructure – pki file must start with Open PGP (aka gpg) key, where primary key must be Ed25519, the key itself must match the first 32 bytes of https://densys.net/<name>/key, and user ID on the gpg key must be signed. No requirements for secondary keys or bytes behind.

Global Unicast Address – gua file must start from a valid string representation of IPv6 address followed by a new line byte (\n) if this is not the only content of the file. You will only be able to set this file if the POST request comes from the IPv6 address being set, see example below for a recommended way to manage it.

Provisioning <

Pynacle/Libsodium can be used to manipulate cryptographic elements.

from nacl.signing import SigningKey, VerifyKey
signKey = SigningKey.generate()
signKey_private_bytes = signKey.encode()
len(signKey_private_bytes)
Out: 32 # bytes for secret storage locally

signKey_public_bytes = signKey.verify_key.encode()
len(signKey_public_bytes)
Out: 32 # public key bytes to pass around
signKey_public_bytes.hex()
Out: 'f67d78fbc759dd1500b8461f9a0a35263c5b84ca4562009ea6f78533bd2719af'

Provision a name with the key and CAPTCHA:

https://densys.net/prov.html?name=funштraße&key=f67d78fbc759dd1500b8461f9a0a35263c5b84ca4562009ea6f78533bd2719af

We use URL parameters for convenience. A user can type in the name by hand. In fact, the latter is preferred as the name is automatically checked for uniqueness, and provisioning would only proceed when the name field was green-lit.

Basic Secure Exchange <

When a name is provisioned on the system any member of public can obtain verification (public) key associated with the name and rigorously establish integrity and authenticity of whatever message signed by the private (secret) key’s holder. This process is described here https://pynacl.readthedocs.io/en/latest/signing/.

Furthermore, a member of public can package encrypted message for a name like so:

import requests
from nacl.public import SealedBox
from nacl.signing import VerifyKey
r = requests.get('https://densys.net/funштraße/key')
len(r.content)
Out: 32 # verification key bytes; converting them into encryption key :
public_key_obj = VerifyKey(r.content).to_curve25519_public_key()
encryptor = SealedBox(public_key_obj)
# ^ SealedBox provides secrecy and integrity but not authenticity
anon_msg_encrypted = encryptor.encrypt(b'Attack at Dawn!')

'''transit'''
# at funштraße’s den where signing (private) key belongs :

from nacl.public import SealedBox
from nacl.signing import SigningKey
sign_key_obj = SigningKey(signKey_private_bytes) # from local storage
private_key_obj = sign_key_obj.to_curve25519_private_key()
decryptor = SealedBox(private_key_obj)
decryptor.decrypt(anon_msg_encrypted).decode()
Out: 'Attack at Dawn!'

Using a single pair of keys for signing and encryption is a bad idea. To maintain cryptographic strength you should use unrelated keys in those workflows.

Adding more keys <

We should use unrelated keys in signing and encryption workflows. As ed25519 signature verification key is published by provisioning, we are now adding cv25519 encryption public key as the second chunk of 32 bytes to the key file:

import requests
r = requests.get('https://densys.net/funштraße/key')
len(r.content)
Out: 32
content = r.content # our signKey_public_bytes

from nacl.public import PrivateKey
encryptKey = PrivateKey.generate()
encryptKey_private_bytes = encryptKey.encode()
len(encryptKey_private_bytes)
Out: 32 # bytes for secret storage locally

encryptKey_public_bytes = encryptKey.public_key.encode()
len(encryptKey_public_bytes)
Out: 32 # public key bytes to pass around
content += encryptKey_public_bytes
len(content)
Out: 64 # signKey_public + encryptKey_public

# as content consists of public bytes - everybody could make it,
# here we sign with our secret signing key to establish authenticity :
from nacl.signing import SigningKey
sign_key_obj = SigningKey(signKey_private_bytes) # from local storage
signed = sign_key_obj.sign(content)

r = requests.post('https://densys.net/FUNШтraße/key', data=\
                  signed.signature + signed.message)
r.status_code
Out: 202 # success as the server updates the record asynchronously

r = requests.get('https://densys.net/funштraße/key')
len(r.content)
Out: 64 # now we have two keys in the same file
# ^ remeber that signKey_public_bytes must come first -
# that's where the server takes them to validate further updates

GNU Privacy Guard (Open PGP) keys <

You may choose to stick with gpg key management included with major Linux distributions. Note that cryptographic strength of cv25519 family is on par with RSA keys of ~3000 bits in length. You may still opt for a stronger key gpg provides. You can use pki file to publish your gpg key for RESTful access. However, in order to delete pki file you would have to resort to the common procedure. In this section we provision a name and upload gpg key from *nix command line:

uid="Alice" # has nothing to do with densys name
# create primary ed25519 key – no passphrase, sign capability, no expiration :
gpg --batch --passphrase '' --quick-generate-key $uid ed25519 sign never
# take fingerprint as id of the key created :
fpr=`gpg --with-colons --fingerprint $uid |awk -F: '$1 == "fpr" {print$10;exit}'`
# add arbitrary subkey, here with encryption capability :
gpg --batch --passphrase '' --quick-add-key $fpr rsa4096 encrypt never
# ^ could be cv25519 in place of rsa4096 or any other algo
# export public component from a keyring where the key lives :
gpg --export $fpr >payload # store it in a file
# print hex representation of the signature verification key :
gpg -v --list-packets payload

# off=0 ctb=98 tag=6 hlen=2 plen=51 :public key packet: version 4, algo 22, created 1609250595, expires 0 pkey[0]: 092B06010401DA470F01 ed25519 (1.3.6.1.4.1.11591.15.1) pkey[1]: 40BD1BCF51EB6D4C697BBA9AEB8E6278C98370CA740DF34A708D3FDC19F0002873 ...

Note that "EdDSA" describes its own compression scheme which is used by default; the non-standard first byte '0x40' may optionally be used to explicitly flag the use of the algorithm’s native compression method. We can now go and provision an arbitrary name like so:

https://densys.net/prov.html?name=Þrændalög&key=BD1BCF51EB6D4C697BBA9AEB8E6278C98370CA740DF34A708D3FDC19F0002873

After provisioning gpg key can be uploaded and downloaded like so:

# uploading gpg key :
curl -v --data-binary @payload https://densys.net/Þrændalög/pki
# Now a member of public can download the key and use it like so :
curl -o gpgkey https://densys.net/Þrændalög/pki # saving gpg key to a file
# importing the file to a local keyring and taking fingerprint to id the import :
fpr=`gpg --with-colons --import-options import-show --import gpgkey |awk -F: '$1 == "fpr" {print$10;exit}'`
# setting ultimate local trust to the import :
gpg --export-ownertrust && echo $fpr:6: |gpg --import-ownertrust

It may be a good idea to keep gpg user id and densys name in sync but that is not required.

Updating Global Unicast Address <

Devices normally use temporary random IPv6 addresses as per RFC4941 to preclude them from becoming personal identifiers. Applications should not publish static addresses and, instead, update gua record following expiration of a temporary address. Any string representation of IPv6 address as per RFC4291 will be accepted, e.g.

tmp_ipv6_1 = '2bc1:4a00:8744:ee25::d87'
from nacl.signing import SigningKey
sign_key_obj = SigningKey(signKey_private_bytes) # from local storage
# gua file content :
data = sign_key_obj.sign((tmp_ipv6_1 + '\nhello world\n').encode())
# instruct requests module to use this IPv6 as source address (and automatic port 0) :
import requests
s = requests.Session()
s.adapters['https://'].poolmanager =\
      requests.urllib3.PoolManager(source_address=(tmp_ipv6_1,0))
# finally update gua record of the name :
s.request('POST', 'https://densys.net/funштraße/gua', data=data)

Deleting files <

There is just one simple way of file deletion. Deleting key file also deletes the name itself and all associated files. Using HTTP method DELETE with payload of the signed file content deletes the file. For example, let’s delete pki file with gpg private key on local keyring; *nix command line:

gpg --export-secret-key Alice |gpg -v --list-packets |head

# off=0 ctb=94 tag=5 hlen=2 plen=88 :secret key packet: version 4, algo 22, created 1610291477, expires 0 pkey[0]: 092B06010401DA470F01 ed25519 (1.3.6.1.4.1.11591.15.1) pkey[1]: 40F5F855DF9F4F80FE430F4BB2A4C4F99D2408192A896C5EED5F9046DBDD02E0FF skey[2]: 0174625FAEBB5CC29F49914E8564EAA508EE5C17F8681B354C14F90814FA8069 checksum: 0f66 ...

In Python:

import requests
from nacl.signing import SigningKey
signKey = SigningKey(bytes.fromhex(\
'0174625FAEBB5CC29F49914E8564EAA508EE5C17F8681B354C14F90814FA8069'))
r = requests.get('https://densys.net/Þrændalög/pki')
len(r.content)
Out: 856 # gpg key with RSA4096 subkey
signed = signKey.sign(r.content)
requests.request('DELETE', 'https://densys.net/Þrændalög/pki', data=\
                 signed.signature + signed.message)