From 77383dd67f20ccdca7d9d3cc071ca386ba6a6ac3 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 19 Oct 2020 17:45:49 +0200 Subject: [PATCH] SHA256 hashes support for SSH keys. --- src/ssh_keys/admin.py | 6 +-- .../migrations/0006_sshkey_sha256_hash.py | 19 ++++++++ src/ssh_keys/models.py | 2 + src/ssh_keys/utils.py | 43 +++++++++++++++---- src/ssh_keys/views.py | 13 ++++-- 5 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 src/ssh_keys/migrations/0006_sshkey_sha256_hash.py diff --git a/src/ssh_keys/admin.py b/src/ssh_keys/admin.py index 6e75313..af736bb 100644 --- a/src/ssh_keys/admin.py +++ b/src/ssh_keys/admin.py @@ -4,9 +4,9 @@ from .models import SSHKey class SSHKeyAdmin(admin.ModelAdmin): - fields = ['user', 'key', 'algorithm', 'bit_length', 'md5_hash', 'created_at', 'last_seen_at'] - readonly_fields = ['algorithm', 'bit_length', 'md5_hash', 'created_at', 'last_seen_at'] - list_display = ['comment', 'last_seen_at', 'user', 'md5_hash', 'algorithm', 'bit_length', 'created_at'] + fields = ['user', 'key', 'algorithm', 'bit_length', 'sha256_hash', 'md5_hash', 'created_at', 'last_seen_at'] + readonly_fields = ['algorithm', 'bit_length', 'sha256_hash', 'md5_hash', 'created_at', 'last_seen_at'] + list_display = ['comment', 'last_seen_at', 'user', 'sha256_hash', 'algorithm', 'bit_length', 'created_at'] ordering = (F('last_seen_at').desc(nulls_last=True),) admin.site.register(SSHKey, SSHKeyAdmin) diff --git a/src/ssh_keys/migrations/0006_sshkey_sha256_hash.py b/src/ssh_keys/migrations/0006_sshkey_sha256_hash.py new file mode 100644 index 0000000..316ff6b --- /dev/null +++ b/src/ssh_keys/migrations/0006_sshkey_sha256_hash.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.8 on 2020-10-19 15:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ssh_keys', '0005_auto_20190401_0938'), + ] + + operations = [ + migrations.AddField( + model_name='sshkey', + name='sha256_hash', + field=models.CharField(default='', editable=False, max_length=128, verbose_name='SHA256 hash'), + preserve_default=False, + ), + ] diff --git a/src/ssh_keys/models.py b/src/ssh_keys/models.py index e4841ca..3bd49f8 100644 --- a/src/ssh_keys/models.py +++ b/src/ssh_keys/models.py @@ -10,6 +10,7 @@ class SSHKey(models.Model): comment = models.CharField(_('comment'), max_length=255, editable=False) algorithm = models.CharField(_('algorithm'), max_length=32, editable=False) bit_length = models.IntegerField(_('bit length'), null=True, editable=False) + sha256_hash = models.CharField(_('SHA256 hash'), max_length=128, editable=False) md5_hash = models.CharField(_('MD5 hash'), max_length=128, editable=False) created_at = models.DateTimeField(_('created at'), auto_now_add=True) last_seen_at = models.DateTimeField(_('last seen at'), null=True, editable=False) @@ -29,4 +30,5 @@ class SSHKey(models.Model): self.algorithm = det['algo'] self.bit_length = det['bits'] self.md5_hash = det['md5'] + self.sha256_hash = det['sha256'] return super().save(*args, **kwargs) diff --git a/src/ssh_keys/utils.py b/src/ssh_keys/utils.py index e8756a2..c48d83c 100644 --- a/src/ssh_keys/utils.py +++ b/src/ssh_keys/utils.py @@ -6,10 +6,10 @@ import subprocess def get_key_details(key): """ >>> get_key_details('ssh-dss AAAAB3NzaC1kc3MAAACBAJxrocPXtCxwgg5yvOc1NLFFz/Fql4+7sOgMOkwWO6pxpJ4bPZgzZ0B17/HGKxQaot3Nc7vzdkC3MBrDDbKrX4n9qB9yJBd0Kkr5X0K7SnBKU+7fbg+rloUdYE78LS6ap05+xlJ8dU918DnS3KqcT/YQQXaTLrt/2DUOM1qxCI1XAAAAFQCJXLN0vYx7SIYMQ0zhv9IUT5WhgQAAAIAT2new16avxvs56zU87t1QQe0qwbQEIUygWW6vqnc9Lo9aSf21sM5WAHTkEnTVyiFSI6K6Q6bD2OUMvS2oWaoariW8EFKzg7/pufmThG0oAxkloc3j8gMO2+xuw7yHzP2pd6xgosNkqivpsGT1PKo+vM6x8p9B6PvipHPqhgFHWQAAAIEAhgFE3+gfPpfDIhaPP5Adx4Hm0VO3xBgOtafvunv3kP54kvHuTaD2uLwgcdOsMedv1/tqhJddh4+9ibwhlKbxKLHrQIcGSHCIY/BoA4RnpSBlGoXEc2buLoZ9IwANCIa2mp19Q/v4wwLnTJHabdMkNCiUn8NPEiHUPjgIj1uoCgo= epsilon') - {'algo': 'DSA', 'bits': 1024, 'md5': 'f6:78:a7:9e:6b:41:dc:31:f4:39:12:5f:2b:0c:7a:11', 'comment': 'epsilon'} + {'algo': 'DSA', 'bits': 1024, 'sha256': 'o2DscHLwvUsYJ7a5eYgc2B4hQO4rf19u17W9Hh/H2h8', 'comment': 'epsilon', 'md5': 'f6:78:a7:9e:6b:41:dc:31:f4:39:12:5f:2b:0c:7a:11'} """ process = subprocess.run( - ['ssh-keygen', '-lEmd5', '-f-'], + ['ssh-keygen', '-lEsha256', '-f-'], input=key.encode('utf-8'), stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -18,16 +18,33 @@ def get_key_details(key): output = process.stdout.decode('utf-8').rstrip() m = re.match( - r'^(?P\d+) MD5:(?P[a-f0-9:]+) (?P.*) \((?P.*)\)$', + r'^(?P\d+) SHA256:(?P[^ ]+) (?P.*) \((?P.*)\)$', output ) - return { + data = { 'algo': m.group('algo'), 'bits': int(m.group('bits')), - 'md5': m.group('md5'), + 'sha256': m.group('sha256'), 'comment': m.group('comment'), } + process = subprocess.run( + ['ssh-keygen', '-lEmd5', '-f-'], + input=key.encode('utf-8'), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if process.returncode != 0: + raise ValueError(process.stderr.decode('utf-8')) + + output = process.stdout.decode('utf-8').rstrip() + m = re.match( + r'^(?P\d+) MD5:(?P[a-f0-9:]+) (?P.*) \((?P.*)\)$', + output + ) + data['md5'] = m.group('md5') + + return data + def parse_log_line(line, year=None, allow_future_days=7): """ @@ -35,8 +52,13 @@ def parse_log_line(line, year=None, allow_future_days=7): ... 'Jan 1 2:34:56 heta sshd[4112]: Accepted publickey for localuser from 0.0.0.0 port 33980 ssh2: RSA f6:78:a7:9e:6b:41:dc:31:f4:39:12:5f:2b:0c:7a:11', ... year=2019).items()) [('algo', 'RSA'), ('datetime', datetime.datetime(2019, 1, 1, 2, 34, 56)), ('host', 'heta'), ('ip', '0.0.0.0'), ('md5', 'f6:78:a7:9e:6b:41:dc:31:f4:39:12:5f:2b:0c:7a:11'), ('user', 'localuser')] + >>> sorted(parse_log_line( + ... 'Jan 1 2:34:56 heta sshd[4112]: Accepted publickey for localuser from 0.0.0.0 port 33980 ssh2: RSA SHA256:9FLLdMdArtybwaoS45qtNZSmcBsr1pR2t8c9O+XODBw', + ... year=2019).items()) + [('algo', 'RSA'), ('datetime', datetime.datetime(2019, 1, 1, 2, 34, 56)), ('host', 'heta'), ('ip', '0.0.0.0'), ('sha256', '9FLLdMdArtybwaoS45qtNZSmcBsr1pR2t8c9O+XODBw'), ('user', 'localuser')] + """ - logline_re = r'^(?P