Skip to content

Commit b6b3aa7

Browse files
authored
Merge pull request #3
flic-07
2 parents e5d1696 + 4a496b3 commit b6b3aa7

File tree

142 files changed

+4089
-1410
lines changed

Some content is hidden

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

142 files changed

+4089
-1410
lines changed

README.md

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

ccnpy/apps/cli_utils.py

Lines changed: 108 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,55 +11,137 @@
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
25+
from ccnpy.flic.RsaOaepCtx.RsaOaepEncryptor import RsaOaepEncryptor
2126
from ccnpy.flic.aeadctx.AeadDecryptor import AeadDecryptor
2227
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
2333

2434

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+
2550
def add_encryption_cli_args(parser):
2651

2752
parser.add_argument('-k', dest="key_file", default=None,
2853
help="RSA key in PEM format to sign the root manifest")
2954
parser.add_argument('-p', dest="key_pass", default=None,
3055
help="RSA key password (otherwise will prompt)")
3156

32-
parser.add_argument('--enc-key', dest="enc_key", default=None, help="AES encryption key (hex string)")
33-
parser.add_argument("--aes-mode", dest="aes_mode", default='GCM', choices=['GCM', 'CCM'], help="Encryption algorithm, default GCM")
34-
parser.add_argument('--key-num', dest="key_num", type=int, default=None,
57+
parser.add_argument('--wrap-key', dest="wrap_key", default=None, help="Wrapping key for RSA-OAEP mode.")
58+
parser.add_argument('--wrap-pass', dest="wrap_pass", default=None, help="Wrapping key key password (otherwise will prompt).")
59+
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,
3567
help="Key number of pre-shared key (defaults to key hash)")
3668
parser.add_argument('--salt', dest="salt", type=lambda x: int(x,0), default=None,
3769
help="Upto a 4-byte salt to include in the IV with the nonce.")
3870

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+
3984
def aes_key_from_cli_args(args) -> Optional[AeadKey]:
4085
if args.enc_key is None:
4186
return None
4287

43-
key_bytes = bytearray.fromhex(args.enc_key)
4488
if args.aes_mode == 'GCM':
45-
return AeadGcm(key=key_bytes)
89+
return AeadGcm(key=args.enc_key)
4690
elif args.aes_mode == 'CCM':
47-
return AeadCcm(key=key_bytes)
91+
return AeadCcm(key=args.enc_key)
4892
else:
4993
raise ValueError(f'aes_mode must be GCM or CCM, got {args.aes_mode}.')
5094

51-
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]:
52118
if args.enc_key is not None:
53-
key = aes_key_from_cli_args(args)
54-
return AeadEncryptor(key=key, key_number=args.key_num, salt=args.salt)
119+
return AeadEncryptor(aead_parameters_from_cli(args))
55120
return None
56121

57-
def aead_decryptor_from_cli_args(args):
122+
def aead_decryptor_from_cli_args(args) -> Optional[AeadDecryptor]:
58123
if args.enc_key is not None:
59-
key = aes_key_from_cli_args(args)
60-
return AeadDecryptor(key=key, key_number=args.key_num, salt=args.salt)
124+
return AeadDecryptor(aead_parameters_from_cli(args))
61125
return None
62126

127+
def rsa_oaep_encryptor_from_cli_args(args):
128+
if args.wrap_key is None:
129+
raise ValueError("You must specify a wrapping key for RSA-OAEP mode.")
130+
131+
wrapping_key = RsaKey.load_pem_key(args.wrap_key, args.wrap_pass)
132+
if args.enc_key is None:
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
138+
139+
def encryptor_from_cli_args(args):
140+
if args.wrap_key is None:
141+
return aead_encryptor_from_cli_args(args)
142+
else:
143+
return rsa_oaep_encryptor_from_cli_args(args)
144+
63145
def rsa_signer_from_cli_args(args):
64146
if args.key_file is not None:
65147
signing_key = RsaKey.load_pem_key(args.key_file, args.key_pass)
@@ -75,20 +157,26 @@ def rsa_verifier_from_cli_args(args):
75157
def fixup_key_password(args, ask_for_pass: bool = True):
76158
if args.key_pass is None:
77159
if ask_for_pass:
78-
args.key_pass = getpass.getpass(prompt="Private key password")
79-
else:
80-
return
160+
args.key_pass = getpass.getpass(prompt="Signing private key password")
161+
if len(args.key_pass) == 0:
162+
args.key_pass = None
81163

82-
if len(args.key_pass) == 0:
83-
args.key_pass = None
164+
if args.wrap_key is not None and args.wrap_pass is None:
165+
if ask_for_pass:
166+
args.wrap_pass = getpass.getpass(prompt="Wrapping key password")
167+
if len(args.wrap_pass) == 0:
168+
args.wrap_pass = None
84169

85170
def create_keystore(args):
86171
keystore = InsecureKeystore()
87-
aes_key = aes_key_from_cli_args(args)
88-
if aes_key is not None:
89-
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)
90175

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

179+
if args.wrap_key is not None:
180+
keystore.add_rsa_key(name='wrap', key=RsaKey.load_pem_key(args.wrap_key, args.wrap_pass))
181+
94182
return keystore

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: 6 additions & 3 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
@@ -24,8 +25,7 @@
2425
from ccnpy.flic.name_constructor.SchemaType import SchemaType
2526
from ccnpy.flic.tlvs.Locators import Locators
2627
from ccnpy.flic.tree.TreeIO import TreeIO
27-
from .cli_utils import add_encryption_cli_args, rsa_signer_from_cli_args, aead_encryptor_from_cli_args, \
28-
fixup_key_password
28+
from .cli_utils import add_encryption_cli_args, rsa_signer_from_cli_args, fixup_key_password, encryptor_from_cli_args
2929

3030

3131
class ManifestWriter:
@@ -59,7 +59,7 @@ def _create_tree_options(self, args):
5959
manifest_expiry_time=self._parse_time(args.node_expiry),
6060
data_expiry_time=self._parse_time(args.data_expiry),
6161

62-
manifest_encryptor=aead_encryptor_from_cli_args(args),
62+
manifest_encryptor=encryptor_from_cli_args(args),
6363

6464
add_node_subtree_size=True,
6565

@@ -107,6 +107,9 @@ def _create_manifest_tree(self) -> Packet:
107107

108108

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

112115
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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
class DisplayFormatter:
2222
@classmethod
2323
def hexlify(cls, value):
24-
return str(binascii.hexlify(value), 'utf-8')
24+
if value is None:
25+
return "None"
26+
return f'0x{str(binascii.hexlify(value), 'utf-8')}'
2527

2628
@classmethod
2729
def prettify(cls, value):

0 commit comments

Comments
 (0)