Skip to content

Commit 1a21615

Browse files
committed
Further work on licensing
1 parent b851ead commit 1a21615

File tree

4 files changed

+107
-23
lines changed

4 files changed

+107
-23
lines changed

GNUmakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ else
4141
endif
4242

4343
# To see all pytest output, uncomment --capture=no, ...
44-
PYTESTOPTS = --capture=no --log-cli-level=INFO
44+
PYTESTOPTS = --capture=no --log-cli-level=5 # INFO
4545

4646
PY3TEST = $(PY3) -m pytest $(PYTESTOPTS)
4747

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
base58 >=2.0.1,<3
22
chacha20poly1305 >=0.0.3
33
click >=8.1.3,<9
4-
crypto-licensing >=3.2.0,<4
4+
crypto-licensing >=3.3.0,<4
55
cx_Freeze >=6.12 ; sys_platform == "win32"
66
fpdf2 >=2.5.7,<3
77
hdwallet >=2.2.1,<3

slip39/invoice/payments.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,16 @@ def reload(
208208
credentials = None
209209
while True:
210210
key,lic = authorizations.send( credentials )
211+
log.detail( f"Reloading key: {key}, lic: {lic}" )
211212
credentials = None
212213
if key is None or lic is None:
213214
if key is not None:
214215
# Found a Keypair (but no License); yield it
215216
yield Process.KEYPAIR, key
217+
continue
218+
219+
# Neither Keypair nor License; authorized has indicated it is out of options and
220+
# is about to give up: unless we provide new credentials.
216221
what = "No License found for Agent ID {}".format(
217222
licensing.into_b64( key.vk )) if key else "(No Agent ID Keypair found)"
218223
if userpass_input:
@@ -234,7 +239,8 @@ def reload(
234239
log.warning( "Asking for username..." )
235240
username_update = (
236241
yield Process.PROMPT, "Enter {} username (leave empty for no change): ".format(
237-
deduce_name( basename=basename, filename=kwds.get( 'filename' ), package=kwds.get( 'package' )))
242+
deduce_name( basename=basename, filename=kwds.get( 'filename' ), package=kwds.get( 'package' ),
243+
default=client and client.servicekey or "the" ))
238244
)
239245
userpass_updated |= bool( username_update )
240246
if username_update:
@@ -243,7 +249,8 @@ def reload(
243249
log.warning( "Asking for password..." )
244250
password_update = (
245251
yield Process.PROMPT, "Enter {} password (leave empty for no change): ".format(
246-
deduce_name( basename=basename, filename=kwds.get( 'filename' ), package=kwds.get( 'package' )))
252+
deduce_name( basename=basename, filename=kwds.get( 'filename' ), package=kwds.get( 'package' ),
253+
default=client and client.servicekey or "the" ))
247254
)
248255
userpass_updated |= bool( password_update )
249256
if password_update:
@@ -294,7 +301,7 @@ def reload(
294301
exc=''.join( traceback.format_exception( *sys.exc_info() )) if log.isEnabledFor( logging.TRACE ) else exc ))
295302
with open( Path( crypto_licensing.__file__ ).resolve().parent / 'licensing' / 'static' / 'txt' / 'CL-KEYPAIR-MISSING.txt', 'r' ) as f:
296303
error = f.read().format(
297-
DISTRIBUTION = deduce_name( basename=basename, filename=kwds.get( 'filename' ), package=kwds.get( 'package' )),
304+
DISTRIBUTION = deduce_name( basename=basename, filename=kwds.get( 'filename' ), package=kwds.get( 'package' ), default=client and client.servicekey or "" ),
298305
KEYPATTERN = licensing.KEYPATTERN,
299306
LICENSE_OPTION = '--license',
300307
)
@@ -377,16 +384,17 @@ def process(
377384
if acquiring is None:
378385
acquiring = True
379386

380-
# We're looking for a License authored by Dominion R&D Corp. This might be encapsulated as
381-
# one of the dependencies of the License we find (eg. if Dominion issues a License to some
382-
# company, which includes the ability to run a crypto-licensing server), but we'll validate
383-
# that the Grant was issued by Dominion R&D Corp. For example, Dominion issues a License to
384-
# awesome-inc.com to sub-License crypto-licensing servers. They, in turn, issue a License
385-
# to Лайка.ru to run a crypto-licensing server, and help them set it up. When
387+
# We're looking for a License authored by an author Agent (eg. Dominion R&D Corp.), for a
388+
# certain product or service. This might be encapsulated as one of the dependencies of the
389+
# License we find (eg. if Dominion issues a License to some company, which includes the ability
390+
# to run a crypto-licensing server), but we'll validate that the Grant was issued by Dominion
391+
# R&D Corp. For example, Dominion issues a License to awesome-inc.com to sub-License
392+
# crypto-licensing servers. They, in turn, issue a License to Лайка.ru to run a
393+
# crypto-licensing server, and help them set it up. When
386394
# http://crypto-licensing.xn--80aa0aec.ru/ (crypto-licensing.Лайка.ru) issues a License for
387-
# their software, part of the fees are paid to awesome-inc.com and some to dominionrnd.com.
388-
# The final software installation uses crypto-licensing's authorized() function, which checks
389-
# that each successive License's dependencies are correctly signed, and carries the original
395+
# their software, part of the fees are paid to awesome-inc.com and some to dominionrnd.com. The
396+
# final software installation uses crypto-licensing's authorized() function, which checks that
397+
# each successive License's dependencies are correctly signed, and carries the original
390398
# 'crypto-licensing' Grant through to the recipient.
391399
dominion = licensing.Agent(
392400
name = licensing.COMPANY,

slip39/invoice/payments_test.py

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ def test_grants( tmp_path ):
9191
name_cl = "client-user"
9292
base_cl = here / name_cl
9393

94-
os.symlink( name_ss + '.crypto-license', base_cl.with_suffix( '.crypto-license' ))
94+
# os.symlink( name_ss + '.crypto-license', base_cl.with_suffix( '.crypto-license' ))
9595

96-
# Get a Client Agent, to use to self-sign the License
96+
# Get a Client Agent Keypair, to use to self-sign the License
9797
seed_cl = licensing.into_bytes( "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" )
9898
user_cl = "[email protected]"
9999
pswd_cl = "password"
@@ -104,12 +104,85 @@ def test_grants( tmp_path ):
104104
password = pswd_cl,
105105
)
106106
assert keyp_cl['vk'] == "fVnFYj3UCnSqTVoyrGRdOz+V2urkwiviVHbdakhvc4I="
107+
keyp_cl_raw = keyp_cl.into_keypair( username=user_cl, password=pswd_cl )
107108

108109
ls = subprocess.run(
109110
[ 'ls', '-l', str( here ) ], stdout=subprocess.PIPE,
110111
)
111112
log.info( f"Test directory:\n{ls.stdout.decode( 'UTF-8' )}\n\n" )
112113

114+
# The licensing.authorized API should now be able to issue this self-signed.crypto-license
115+
# on-demand, IF it can find a Keypair. It will not find it by looking for
116+
# self-signed.crypto-license, b/c self-signed.crypto-keypair is encrypted with different
117+
# credentials.
118+
with pytest.raises( licensing.NotRegistered ) as excinfo:
119+
assert list( licensing.authorized(
120+
author = author,
121+
basename = str( base_ss ),
122+
confirm = False,
123+
registering = False,
124+
acquiring = False,
125+
)) == [ (None,None) ]
126+
# But w/ a Keypair, we can get a self-signed License issued
127+
auth_str = licensing.into_JSON(
128+
[
129+
( licensing.KeypairPlaintext( k ) if k else None, l )
130+
for k,l in licensing.authorized(
131+
author = author,
132+
basename = str( base_ss ),
133+
confirm = False,
134+
registering = False,
135+
acquiring = False,
136+
keypairs = [ keyp_cl_raw ],
137+
)
138+
], indent=4, default=str,
139+
)
140+
print( auth_str )
141+
assert auth_str == """\
142+
[
143+
[
144+
{
145+
"sk":"u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7t9WcViPdQKdKpNWjKsZF07P5Xa6uTCK+JUdt1qSG9zgg==",
146+
"vk":"fVnFYj3UCnSqTVoyrGRdOz+V2urkwiviVHbdakhvc4I="
147+
},
148+
{
149+
"license":{
150+
"author":{
151+
"name":"fVnFYj3UCnSqTVoyrGRdOz+V2urkwiviVHbdakhvc4I=",
152+
"pubkey":"fVnFYj3UCnSqTVoyrGRdOz+V2urkwiviVHbdakhvc4I="
153+
},
154+
"dependencies":[
155+
{
156+
"license":{
157+
"author":{
158+
"domain":"self-signed.com",
159+
"name":"Dominion Research & Development Corp.",
160+
"product":"Self Signed",
161+
"pubkey":"5zTqbCtiV95yNV5HKqBaTEh+a0Y8Ap7TBt8vAbVja1g="
162+
},
163+
"grant":{
164+
"self-signed":{
165+
"some-capability":10
166+
}
167+
}
168+
},
169+
"signature":"I/o9+bacFBOwTPgNfduUwdgldMRPVCQPUEN4h3yynqzv4sDyEe37oCslnB7gjM8VBojp3vdZbdlmO7HhxLJQDA=="
170+
}
171+
]
172+
},
173+
"signature":"/OWpu8M4HczWnTXwW5yPchZpdI7B/k7c2GtaiK++itlzm8UY2Gl0DMe2k4HDZY6cg6t1cPi3EgjjtZXrWuLACQ=="
174+
}
175+
],
176+
[
177+
{
178+
"sk":"u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7t9WcViPdQKdKpNWjKsZF07P5Xa6uTCK+JUdt1qSG9zgg==",
179+
"vk":"fVnFYj3UCnSqTVoyrGRdOz+V2urkwiviVHbdakhvc4I="
180+
},
181+
null
182+
]
183+
]
184+
"""
185+
113186
client = licensing.Agent(
114187
name = "Perry Kundert",
115188
service = "client-user",
@@ -118,17 +191,19 @@ def test_grants( tmp_path ):
118191
log.info( f"Client: {client}" )
119192

120193
# We'll be loading an existing Client Agent keypair, so restrict it from registering a new one.
121-
# It must try to load the Author's License (using the author.service as the basename), and then
122-
# attempt to sign and save an instance of it with the client Keypair. For this, we need access
123-
# to an Agent Keypair suitable for signing (access to decrypted private key); so we'll need the
124-
# credentials.
194+
# We'll look for Licenses issued under the basenames of client.service (an already issued
195+
# sub-license), and author.service (eg. an author-issued License we can sub-license). If no
196+
# already issued License is found, tt must try to load the Author's License (using the
197+
# author.service as the basename), and then attempt to sign and save an instance of it with the
198+
# client Agent's Keypair. For this, we need access to an Agent Keypair suitable for signing
199+
# (access to decrypted private key); so we'll need the credentials.
125200
machine_id_path = test.with_suffix( '' ) / "payments_test.machine-id"
126201
reloader = reload(
127202
author = author,
128203
client = client,
129204
registering = False,
130205
reverse_save = True,
131-
basename = name_cl, # basename of the License, and the Keypair use to self-sign it
206+
basename = None, # name_cl, # basename of the License, and the Keypair use to self-sign it
132207
confirm = False,
133208
extra = [ str( here ) ],
134209
constraints = dict(
@@ -194,10 +269,11 @@ def test_grants( tmp_path ):
194269
log.info( f"Verified self-signed License:\n{constraints}\n\n" )
195270
assert not constraints
196271

197-
# Later, we restore from backup and our Machine ID changes...
272+
# Later, we restore from backup and our Machine ID changes... This will cause a
273+
# LicenseSigned.verify failure.
198274
assert len( licenses ) == 1
199275
with pytest.raises( licensing.LicenseIncompatibility ) as excinfo:
200-
constraints = licenses[0].verify(
276+
constraints = licenses[0].verify(
201277
author_pubkey = client.pubkey,
202278
confirm = False,
203279
)

0 commit comments

Comments
 (0)