Skip to content

Commit 4a496b3

Browse files
committed
I think everythig is working, including the CLIs. The README should be about up to date, needs a proofread.
1 parent 233b7c2 commit 4a496b3

File tree

106 files changed

+1339
-392
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+1339
-392
lines changed

README.md

Lines changed: 293 additions & 10 deletions
Large diffs are not rendered by default.

ccnpy/apps/cli_utils.py

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,42 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
import argparse
1415
import getpass
16+
import logging
17+
import uuid
1518
from typing import Optional
1619

1720
from ccnpy.crypto.AeadKey import AeadKey, AeadGcm, AeadCcm
21+
from ccnpy.crypto.HpkeKdfIdentifiers import HpkeKdfIdentifiers
1822
from ccnpy.crypto.InsecureKeystore import InsecureKeystore
1923
from ccnpy.crypto.RsaKey import RsaKey
2024
from ccnpy.crypto.RsaSha256 import RsaSha256Signer, RsaSha256Verifier
2125
from ccnpy.flic.RsaOaepCtx.RsaOaepEncryptor import RsaOaepEncryptor
2226
from ccnpy.flic.aeadctx.AeadDecryptor import AeadDecryptor
2327
from ccnpy.flic.aeadctx.AeadEncryptor import AeadEncryptor
28+
from ccnpy.flic.aeadctx.AeadParameters import AeadParameters
29+
from ccnpy.flic.tlvs.KdfAlg import KdfAlg
30+
from ccnpy.flic.tlvs.KdfData import KdfData
31+
from ccnpy.flic.tlvs.KdfInfo import KdfInfo
32+
from ccnpy.flic.tlvs.KeyNumber import KeyNumber
2433

2534

35+
logger = logging.getLogger(__name__)
36+
37+
38+
def _str_to_array(value: str):
39+
"""
40+
Accept a string in the forms "0xabcd..." or "abcd..." (both in hex) and output
41+
an array['B', ...]
42+
"""
43+
if value.startswith('0x'):
44+
offset=2
45+
else:
46+
offset=0
47+
48+
return bytes.fromhex(value[offset:])
49+
2650
def add_encryption_cli_args(parser):
2751

2852
parser.add_argument('-k', dest="key_file", default=None,
@@ -33,35 +57,71 @@ def add_encryption_cli_args(parser):
3357
parser.add_argument('--wrap-key', dest="wrap_key", default=None, help="Wrapping key for RSA-OAEP mode.")
3458
parser.add_argument('--wrap-pass', dest="wrap_pass", default=None, help="Wrapping key key password (otherwise will prompt).")
3559

36-
parser.add_argument('--enc-key', dest="enc_key", default=None, help="AES encryption key (hex string)")
37-
parser.add_argument("--aes-mode", dest="aes_mode", default='GCM', choices=['GCM', 'CCM'], help="Encryption algorithm, default GCM")
38-
parser.add_argument('--key-num', dest="key_num", type=int, default=None,
60+
parser.add_argument('--enc-key', dest="enc_key",
61+
type=_str_to_array,
62+
default=None, help="AES encryption key (hex string)")
63+
parser.add_argument("--aes-mode", dest="aes_mode", default='GCM',
64+
type=lambda x: x.upper(),
65+
choices=['GCM', 'CCM'], help="Encryption algorithm, default GCM")
66+
parser.add_argument('--key-num', dest="key_num", type=KeyNumber, default=None,
3967
help="Key number of pre-shared key (defaults to key hash)")
4068
parser.add_argument('--salt', dest="salt", type=lambda x: int(x,0), default=None,
4169
help="Upto a 4-byte salt to include in the IV with the nonce.")
4270

71+
parser.add_argument('--kdf', dest="kdf_alg", type=lambda x: HpkeKdfIdentifiers.parse(x),
72+
choices=[HpkeKdfIdentifiers.HKDF_SHA256, HpkeKdfIdentifiers.HKDF_SHA384, HpkeKdfIdentifiers.HKDF_SHA512],
73+
default=None,
74+
help="Use a KDF")
75+
parser.add_argument('--kdf-info', dest="kdf_info", type=lambda x: KdfInfo(x),
76+
default=None,
77+
help="KDF INFO string (ascii or 0x hex string)")
78+
parser.add_argument('--kdf-uuid', dest="kdf_uuid", default=False, action=argparse.BooleanOptionalAction,
79+
help="Use a Type 1 UUID for the KdfInfo (overrides --kdf-info)")
80+
parser.add_argument('--kdf-salt', dest="kdf_salt", type=lambda x: int(x,0), default=None,
81+
help="Upto a 4-byte salt to include in the KDF function (do not use with RSA-OAEP).")
82+
83+
4384
def aes_key_from_cli_args(args) -> Optional[AeadKey]:
4485
if args.enc_key is None:
4586
return None
4687

47-
key_bytes = bytearray.fromhex(args.enc_key)
4888
if args.aes_mode == 'GCM':
49-
return AeadGcm(key=key_bytes)
89+
return AeadGcm(key=args.enc_key)
5090
elif args.aes_mode == 'CCM':
51-
return AeadCcm(key=key_bytes)
91+
return AeadCcm(key=args.enc_key)
5292
else:
5393
raise ValueError(f'aes_mode must be GCM or CCM, got {args.aes_mode}.')
5494

55-
def aead_encryptor_from_cli_args(args):
95+
def kdf_data_from_cli(args) -> Optional[KdfData]:
96+
if args.kdf_uuid:
97+
args.kdf_info = KdfInfo(uuid.uuid1())
98+
99+
if args.kdf_alg is not None:
100+
return KdfData(KdfAlg(args.kdf_alg), args.kdf_info)
101+
else:
102+
return None
103+
104+
def aead_parameters_from_cli(args):
105+
key = aes_key_from_cli_args(args)
106+
params = AeadParameters(
107+
key=key,
108+
key_number=args.key_num,
109+
aead_salt=args.salt,
110+
kdf_data=kdf_data_from_cli(args),
111+
kdf_salt=args.kdf_salt)
112+
113+
logger.debug(params)
114+
115+
return params
116+
117+
def aead_encryptor_from_cli_args(args) -> Optional[AeadEncryptor]:
56118
if args.enc_key is not None:
57-
key = aes_key_from_cli_args(args)
58-
return AeadEncryptor(key=key, key_number=args.key_num, salt=args.salt)
119+
return AeadEncryptor(aead_parameters_from_cli(args))
59120
return None
60121

61-
def aead_decryptor_from_cli_args(args):
122+
def aead_decryptor_from_cli_args(args) -> Optional[AeadDecryptor]:
62123
if args.enc_key is not None:
63-
key = aes_key_from_cli_args(args)
64-
return AeadDecryptor(key=key, key_number=args.key_num, salt=args.salt)
124+
return AeadDecryptor(aead_parameters_from_cli(args))
65125
return None
66126

67127
def rsa_oaep_encryptor_from_cli_args(args):
@@ -70,10 +130,11 @@ def rsa_oaep_encryptor_from_cli_args(args):
70130

71131
wrapping_key = RsaKey.load_pem_key(args.wrap_key, args.wrap_pass)
72132
if args.enc_key is None:
73-
return RsaOaepEncryptor.create_with_new_content_key(wrapping_key)
74-
75-
key = aes_key_from_cli_args(args)
76-
return RsaOaepEncryptor(wrapping_key=wrapping_key, key=key, key_number=args.key_num)
133+
encryptor = RsaOaepEncryptor.create_with_new_content_key(wrapping_key=wrapping_key, kdf_data=kdf_data_from_cli(args))
134+
else:
135+
encryptor = RsaOaepEncryptor(wrapping_key=wrapping_key, params=aead_parameters_from_cli(args))
136+
logger.debug(encryptor)
137+
return encryptor
77138

78139
def encryptor_from_cli_args(args):
79140
if args.wrap_key is None:
@@ -108,9 +169,9 @@ def fixup_key_password(args, ask_for_pass: bool = True):
108169

109170
def create_keystore(args):
110171
keystore = InsecureKeystore()
111-
aes_key = aes_key_from_cli_args(args)
112-
if aes_key is not None:
113-
keystore.add_aes_key(args.key_num, aes_key, args.salt)
172+
aead_params = aead_parameters_from_cli(args)
173+
if aead_params.key is not None:
174+
keystore.add_aes_key(aead_params)
114175

115176
if args.key_file is not None:
116177
keystore.add_rsa_key(name='default', key=RsaKey.load_pem_key(args.key_file, args.key_pass))

ccnpy/apps/manifest_reader.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import argparse
16+
import logging
1617
import sys
1718
from abc import ABC, abstractmethod
1819

@@ -72,7 +73,6 @@ def __init__(self, args, keystore: InsecureKeystore):
7273
self._root_hash = args.hash_restriction
7374
self._dir = args.in_dir
7475
self._keystore = keystore
75-
self._decryptors = {}
7676
self._reader = TreeIO.PacketDirectoryReader(self._dir)
7777
self._writer = self._create_writer(args)
7878
self.debug = False
@@ -102,8 +102,7 @@ def read(self):
102102
with self._writer:
103103
traverser = Traversal(packet_input=self._reader,
104104
data_writer=self._writer,
105-
keystore=self._keystore,
106-
debug=self.debug)
105+
keystore=self._keystore)
107106
# this will walk the manifest tree and write the app data to `data_writer`.
108107
traverser.traverse(root_name=self._root_name, hash_restriction=self._root_hash)
109108

@@ -113,6 +112,11 @@ def read(self):
113112

114113

115114
def run():
115+
logging.basicConfig(level=logging.INFO)
116+
logging.getLogger('ccnpy.flic.tree.Traversal').setLevel(logging.DEBUG)
117+
logging.getLogger('ccnpy.flic.RsaOaepCtx.RsaOaepImpl').setLevel(logging.DEBUG)
118+
logging.getLogger('ccnpy.crypto.InsecureKeystore').setLevel(logging.DEBUG)
119+
116120
parser = argparse.ArgumentParser()
117121

118122
parser.add_argument('--name', dest="name", default=None, help='CCNx URI for root manifest', required=True)

ccnpy/apps/manifest_writer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515

1616
import argparse
17+
import logging
1718
from datetime import datetime
1819

1920
from ccnpy.core.ExpiryTime import ExpiryTime
@@ -106,6 +107,9 @@ def _create_manifest_tree(self) -> Packet:
106107

107108

108109
def run():
110+
logging.basicConfig(level=logging.INFO)
111+
# logging.getLogger('ccnpy.apps.cli_utils').setLevel(logging.DEBUG)
112+
109113
max_size = 1500
110114

111115
parser = argparse.ArgumentParser()

ccnpy/apps/packet_reader.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import argparse
16+
import logging
1617
from pathlib import PurePath
1718

1819
from ccnpy.apps.packet_utils import validate_packet, decrypt_manifest
@@ -68,6 +69,10 @@ def read(self):
6869

6970

7071
def run():
72+
logging.basicConfig(level=logging.INFO)
73+
# logging.getLogger('ccnpy.crypto.InsecureKeystore').setLevel(logging.DEBUG)
74+
logging.getLogger('ccnpy.apps.cli_utils').setLevel(logging.DEBUG)
75+
7176
parser = argparse.ArgumentParser()
7277
parser.add_argument('-i', dest="in_dir", default='.', help="input directory (default=%r)" % '.')
7378

ccnpy/apps/packet_utils.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
from ccnpy.core.ValidationAlg import ValidationAlg_Crc32c, ValidationAlg_RsaSha256
1515
from ccnpy.crypto.Crc32c import Crc32cVerifier
1616
from ccnpy.crypto.DecryptionError import DecryptionError
17-
from ccnpy.crypto.InsecureKeystore import InsecureKeystore
17+
from ccnpy.crypto.InsecureKeystore import InsecureKeystore, KeyIdNotFoundError, KeyNumberNotFoundError
1818
from ccnpy.crypto.RsaSha256 import RsaSha256Verifier
19+
from ccnpy.flic.RsaOaepCtx.RsaOaepDecryptor import RsaOaepDecryptor
1920
from ccnpy.flic.aeadctx.AeadDecryptor import AeadDecryptor
2021
from ccnpy.flic.tlvs.AeadCtx import AeadCtx
2122
from ccnpy.flic.tlvs.Manifest import Manifest
23+
from ccnpy.flic.tlvs.RsaOaepCtx import RsaOaepCtx
2224

2325
__static_crc32c_verifier = Crc32cVerifier()
2426

@@ -34,11 +36,8 @@ def validate_packet(keystore: InsecureKeystore, packet):
3436
elif isinstance(alg, ValidationAlg_RsaSha256):
3537
try:
3638
rsa_pub_key = keystore.get_rsa(alg.keyid())
37-
except KeyError:
38-
raise KeyError(f'Could not find RSA key in keystore with keyid {alg.keyid()}')
39-
40-
if rsa_pub_key is None:
41-
print(f"Packet requires RsaSha256 verifier, but no key matching keyid {alg.keyid} found.")
39+
except KeyIdNotFoundError:
40+
print(f'Signature not validated, could not find RSA key in keystore with keyid {alg.keyid()}')
4241
return
4342

4443
# TODO: we should cache these
@@ -53,19 +52,35 @@ def validate_packet(keystore: InsecureKeystore, packet):
5352
print(f"Packet validation success with {verifier}")
5453
return
5554

55+
56+
def _get_aead_decryptor(keystore: InsecureKeystore, security_ctx: AeadCtx):
57+
try:
58+
params = keystore.get_aes_key(security_ctx.key_number())
59+
except KeyNumberNotFoundError:
60+
print(f'Manifest not decrypted, could not find AES key in keystore with {security_ctx.key_number()}')
61+
return None
62+
63+
return AeadDecryptor(params)
64+
65+
66+
def _get_oaep_decryptor(keystore, security_ctx):
67+
return RsaOaepDecryptor(keystore)
68+
69+
5670
def decrypt_manifest(keystore: InsecureKeystore, manifest: Manifest) -> Manifest:
5771
if not manifest.is_encrypted():
5872
return manifest
5973

6074
security_ctx = manifest.security_ctx()
6175
if isinstance(security_ctx, AeadCtx):
62-
key = keystore.get_aes_key(security_ctx.key_number())
63-
salt = keystore.get_aes_salt(security_ctx.key_number())
64-
decryptor = AeadDecryptor(key=key, key_number=security_ctx.key_number(), salt=salt)
65-
66-
try:
67-
return decryptor.decrypt_manifest(manifest)
68-
except DecryptionError as e:
69-
print(f"Failed decryption: {e}")
76+
decryptor = _get_aead_decryptor(keystore, security_ctx)
77+
elif isinstance(security_ctx, RsaOaepCtx):
78+
decryptor = _get_oaep_decryptor(keystore, security_ctx)
7079
else:
7180
print(f"Unsupported encryption mode: {security_ctx}")
81+
return None
82+
83+
try:
84+
return decryptor.decrypt_manifest(manifest)
85+
except DecryptionError as e:
86+
print(f"Failed decryption: {e}")

ccnpy/core/DisplayFormatter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class DisplayFormatter:
2323
def hexlify(cls, value):
2424
if value is None:
2525
return "None"
26-
return str(binascii.hexlify(value), 'utf-8')
26+
return f'0x{str(binascii.hexlify(value), 'utf-8')}'
2727

2828
@classmethod
2929
def prettify(cls, value):

ccnpy/core/HashTlvType.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def __eq__(self, other):
4646
return False
4747
return self._tlv == other._tlv
4848

49+
def __hash__(self):
50+
return hash(self._tlv)
51+
4952
@classmethod
5053
def parse(cls, tlv):
5154
if tlv.type() != cls.class_type():

ccnpy/core/KeyId.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ def class_type(cls):
2323
return cls.__T_KEYID
2424

2525
def __repr__(self):
26-
return "LeafDigest(%r)" % self._digest
26+
return "KeyId(%r)" % self._digest

ccnpy/core/Tlv.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ def __eq__(self, other):
101101

102102
return result
103103

104+
def __hash__(self):
105+
return hash(self._wire_format.tobytes())
106+
104107
@classmethod
105108
def deserialize(cls, buffer):
106109
if len(buffer) < 4:

0 commit comments

Comments
 (0)