aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin H. Johnson <robbat2@gentoo.org>2007-12-24 02:11:57 -0800
committerRobin H. Johnson <robbat2@gentoo.org>2007-12-24 02:11:57 -0800
commit0c573bd3b377e5a2da0c8b7da9e4a18c9a6039ab (patch)
treeb66c37e4e85f33d6f7ff06832fc7609cc74ae79e
parentIgnore pylint ** warning. (diff)
downloadgitosis-dakkar-0c573bd3b377e5a2da0c8b7da9e4a18c9a6039ab.tar.gz
gitosis-dakkar-0c573bd3b377e5a2da0c8b7da9e4a18c9a6039ab.tar.bz2
gitosis-dakkar-0c573bd3b377e5a2da0c8b7da9e4a18c9a6039ab.zip
Add module to deal specifically with SSH public keys properly.
-rw-r--r--gitosis/init.py4
-rw-r--r--gitosis/ssh.py36
-rw-r--r--gitosis/sshkey.py221
-rw-r--r--gitosis/test/test_ssh.py83
-rw-r--r--gitosis/test/test_sshkey.py87
5 files changed, 316 insertions, 115 deletions
diff --git a/gitosis/init.py b/gitosis/init.py
index f1f8121..ffc0215 100644
--- a/gitosis/init.py
+++ b/gitosis/init.py
@@ -12,7 +12,7 @@ from ConfigParser import RawConfigParser
from gitosis import repository
from gitosis import run_hook
-from gitosis import ssh
+from gitosis import sshkey
from gitosis import util
from gitosis import app
@@ -112,7 +112,7 @@ class Main(app.App):
log.info('Reading SSH public key...')
pubkey = read_ssh_pubkey()
- user = ssh.extract_user(pubkey)
+ user = sshkey.extract_user(pubkey)
if user is None:
log.error('Cannot parse user from SSH public key.')
sys.exit(1)
diff --git a/gitosis/ssh.py b/gitosis/ssh.py
index 10784fa..a9ed206 100644
--- a/gitosis/ssh.py
+++ b/gitosis/ssh.py
@@ -1,36 +1,14 @@
"""
-Gitosis code to handle SSH public keys.
+Gitosis code to handle SSH authorized_keys files
"""
import os, errno, re
import logging
+from gitosis import sshkey
# C0103 - 'log' is a special name
# pylint: disable-msg=C0103
log = logging.getLogger('gitosis.ssh')
-_ACCEPTABLE_USER_RE = re.compile(r'^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$')
-
-def isSafeUsername(user):
- """
- Is the username safe to use a a filename?
- """
- match = _ACCEPTABLE_USER_RE.match(user)
- return (match is not None)
-
-class InsecureSSHKeyUsername(Exception):
- """Username contains not allowed characters"""
-
- def __str__(self):
- return '%s: %s' % (self.__doc__, ': '.join(self.args))
-
-def extract_user(pubkey):
- """Find the username for a given SSH public key line."""
- _, user = pubkey.rsplit(None, 1)
- if isSafeUsername(user):
- return user
- else:
- raise InsecureSSHKeyUsername(repr(user))
-
def readKeys(keydir):
"""
Read SSH public keys from ``keydir/*.pub``
@@ -42,7 +20,7 @@ def readKeys(keydir):
if ext != '.pub':
continue
- if not isSafeUsername(basename):
+ if not sshkey.isSafeUsername(basename):
log.warn('Unsafe SSH username in keyfile: %r', filename)
continue
@@ -66,8 +44,6 @@ def generateAuthorizedKeys(keys):
for (user, key) in keys:
yield TEMPLATE % dict(user=user, key=key)
-#Protocol 1 public keys consist of the following space-separated fields: options, bits, exponent, modulus, comment.
-#Protocol 2 public key consist of: options, keytype, base64-encoded key, comment.
_COMMAND_OPTS_SAFE_CMD = \
'command="(/[^ "]+/)?gitosis-serve [^"]+"'
_COMMAND_OPTS_SAFE = \
@@ -83,9 +59,9 @@ _COMMAND_OPTS_UNSAFE = \
+'|tunnel="[^"]+"'
_COMMAND_RE = re.compile(
- '^'+_COMMAND_OPTS_SAFE_CMD \
- +'(,('+_COMMAND_OPTS_SAFE+'))+' \
- +' .*')
+'^'+_COMMAND_OPTS_SAFE_CMD \
++'(,('+_COMMAND_OPTS_SAFE+'))+' \
++' .*')
def filterAuthorizedKeys(fp):
"""
diff --git a/gitosis/sshkey.py b/gitosis/sshkey.py
new file mode 100644
index 0000000..d160b69
--- /dev/null
+++ b/gitosis/sshkey.py
@@ -0,0 +1,221 @@
+"""
+Gitosis code to intelligently handle SSH public keys.
+
+"""
+from shlex import shlex
+from StringIO import StringIO
+import re
+
+# The 'ecc' and 'ecdh' types are speculative, based on the Internet Draft
+# http://www.ietf.org/internet-drafts/draft-green-secsh-ecc-02.txt
+SSH_KEY_PROTO2_TYPES = ['ssh-dsa',
+ 'ssh-ecc',
+ 'ssh-ecdh',
+ 'ssh-rsa']
+
+# These options must not have arguments
+SSH_KEY_OPTS = ['no-agent-forwarding',
+ 'no-port-forwarding',
+ 'no-pty',
+ 'no-X11-forwarding']
+# These options require arguments
+SSH_KEY_OPTS_WITH_ARGS = ['command',
+ 'environment',
+ 'from',
+ 'permitopen',
+ 'tunnel' ]
+
+class MalformedSSHKey(Exception):
+ """Malformed SSH public key"""
+
+class InsecureSSHKeyUsername(Exception):
+ """Username contains not allowed characters"""
+ def __str__(self):
+ return '%s: %s' % (self.__doc__, ': '.join(self.args))
+
+class SSHPublicKey:
+ """Base class for representing an SSH public key"""
+ def __init__(self, opts, keydata, comment):
+ """Create a new instance."""
+ self._options = opts
+ self._comment = comment
+ self._username = comment
+ _ = comment.split(None)
+ if len(_) > 1:
+ self._username = _[0]
+ _ = keydata
+
+ @property
+ def options(self):
+ """Returns a dictionary of options used with the SSH public key."""
+ return self._options
+
+ @property
+ def comment(self):
+ """Returns the comment associated with the SSH public key."""
+ return self._comment
+
+ @property
+ def username(self):
+ """
+ Returns the username from the comment, the first word of the comment.
+ """
+ return self._username
+
+ def options_string(self):
+ """Return the options array as a suitable string."""
+ def _single_option():
+ """Convert a single option to a usable string."""
+ for (key, val) in self._options.items():
+ _ = key
+ if val is not None:
+ _ += "=\"%s\"" % (val.replace('"', '\\"'), )
+ yield _
+ return ','.join(_single_option())
+
+ @property
+ def key(self):
+ """Abstract method"""
+ raise NotImplementedError()
+
+ @property
+ def full_key(self):
+ """Return a full SSH public key line, as found in authorized_keys"""
+ options = self.options_string()
+ if len(options) > 0:
+ options += ' '
+ return '%s%s %s' % (options, self.key, self.comment)
+
+ def __str__(self):
+ return self.full_key
+
+class SSH1PublicKey(SSHPublicKey):
+ """Class for representing an SSH public key, protocol version 1"""
+ def __init__(self, opts, keydata, comment):
+ """Create a new instance."""
+ SSHPublicKey.__init__(self, opts, keydata, comment)
+ (self._key_bits,
+ self._key_exponent,
+ self._key_modulus) = keydata.split(' ')
+ @property
+ def key(self):
+ """Return just the SSH key data, without options or comments."""
+ return '%s %s %s' % (self._key_bits,
+ self._key_exponent,
+ self._key_modulus)
+
+class SSH2PublicKey(SSHPublicKey):
+ """Class for representing an SSH public key, protocol version 2"""
+ def __init__(self, opts, keydata, comment):
+ """Create a new instance."""
+ SSHPublicKey.__init__(self, opts, keydata, comment)
+ (self._key_prefix, self._key_base64) = keydata.split(' ')
+ @property
+ def key(self):
+ """Return just the SSH key data, without options or comments."""
+ return '%s %s' % (self._key_prefix, self._key_base64)
+
+def get_ssh_pubkey(line):
+ """Take an SSH public key, and return an object representing it."""
+ (opts, keydata, comment) = _explode_ssh_key(line)
+ if keydata.startswith('ssh-'):
+ return SSH2PublicKey(opts, keydata, comment)
+ else:
+ return SSH1PublicKey(opts, keydata, comment)
+
+def _explode_ssh_key(line):
+ """
+ Break apart a public-key line correct.
+ - Protocol 1 public keys consist of:
+ options, bits, exponent, modulus, comment.
+ - Protocol 2 public key consist of:
+ options, keytype, base64-encoded key, comment.
+ - For all options that take an argument, having a quote inside the argument
+ is valid, and should be in the file as '\"'
+ - Spaces are also valid in those arguments.
+ - Options must be seperated by commas.
+ Seperately return the options, key data and comment.
+ """
+ opts = {}
+ shl = shlex(StringIO(line), None, True)
+ shl.wordchars += '-'
+ # Treat ',' as whitespace seperation the options
+ shl.whitespace += ','
+ shl.whitespace_split = 1
+ # Handle the options first
+ keydata = None
+ def _check_eof(tok):
+ """See if the end was nigh."""
+ if tok == shl.eof:
+ raise MalformedSSHKey("Unexpected end of key")
+ while True:
+ tok = shl.get_token()
+ _check_eof(tok)
+ # This is the start of the actual key, protocol 1
+ if tok.isdigit():
+ keydata = tok
+ expected_key_args = 2
+ break
+ # This is the start of the actual key, protocol 2
+ if tok in SSH_KEY_PROTO2_TYPES:
+ keydata = tok
+ expected_key_args = 1
+ break
+ if tok in SSH_KEY_OPTS:
+ opts[tok] = None
+ continue
+ if '=' in tok:
+ (tok, _) = tok.split('=', 1)
+ if tok in SSH_KEY_OPTS_WITH_ARGS:
+ opts[tok] = _
+ continue
+ raise MalformedSSHKey("Unknown fragment %r" % (tok, ))
+ # Now handle the key
+ # Protocol 2 keys have only 1 argument besides the type
+ # Protocol 1 keys have 2 arguments after the bit-count.
+ shl.whitespace_split = 1
+ while expected_key_args > 0:
+ _ = shl.get_token()
+ _check_eof(_)
+ keydata += ' '+_
+ expected_key_args -= 1
+ # Everything that remains is a comment
+ comment = ''
+ shl.whitespace = ''
+ while True:
+ _ = shl.get_token()
+ if _ == shl.eof or _ == None:
+ break
+ comment += _
+ return (opts, keydata, comment)
+
+_ACCEPTABLE_USER_RE = re.compile(
+ r'^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$'
+ )
+
+def isSafeUsername(user):
+ """Is the username safe to use a a filename? """
+ match = _ACCEPTABLE_USER_RE.match(user)
+ return (match is not None)
+
+def extract_user(pubkey):
+ """Find the username for a given SSH public key line."""
+ _, user = pubkey.rsplit(None, 1)
+ if isSafeUsername(user):
+ return user
+ else:
+ raise InsecureSSHKeyUsername(repr(user))
+
+#X#key1 = 'no-X11-forwarding,command="x b c , d=e f \\"wham\\" \'
+#before you go-go"
+#ssh-rsa abc robbat2@foo foo\tbar#ignore'
+#X#key2 = 'from=172.16.9.1 768 3 5 sam comment\tfoo'
+#X#key3 = '768 3 5 commentfoo'
+#X## 123456789 123456789 123456789 123456789 123456789
+#X#k = get_ssh_pubkey(key1)
+#X#print 'opts=%r' % (k.options, )
+#X#print 'k=%r' % (k.key, )
+#X#print 'c=%r' % (k.comment, )
+#X#print 'u=%r' % (k.username, )
+#X#print k.full_key
+#X#
diff --git a/gitosis/test/test_ssh.py b/gitosis/test/test_ssh.py
index d9ec2bd..77d7863 100644
--- a/gitosis/test/test_ssh.py
+++ b/gitosis/test/test_ssh.py
@@ -191,86 +191,3 @@ baz
got = readFile(path)
eq(got, '''# foo\nbar\nbaz\n### autogenerated by gitosis, DO NOT EDIT\ncommand="gitosis-serve jdoe",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %(key_1)s\n''' % dict(key_1=KEY_1))
-def test_ssh_extract_user_simple():
- got = ssh.extract_user(
- 'ssh-somealgo '
- +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fakeuser@fakehost')
- eq(got, 'fakeuser@fakehost')
-
-def test_ssh_extract_user_domain():
- got = ssh.extract_user(
- 'ssh-somealgo '
- +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fakeuser@fakehost.example.com')
- eq(got, 'fakeuser@fakehost.example.com')
-
-def test_ssh_extract_user_domain_dashes():
- got = ssh.extract_user(
- 'ssh-somealgo '
- +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fakeuser@ridiculously-long.example.com')
- eq(got, 'fakeuser@ridiculously-long.example.com')
-
-def test_ssh_extract_user_underscore():
- got = ssh.extract_user(
- 'ssh-somealgo '
- +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fake_user@example.com')
- eq(got, 'fake_user@example.com')
-
-def test_ssh_extract_user_dot():
- got = ssh.extract_user(
- 'ssh-somealgo '
- +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fake.u.ser@example.com')
- eq(got, 'fake.u.ser@example.com')
-
-def test_ssh_extract_user_dash():
- got = ssh.extract_user(
- 'ssh-somealgo '
- +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fake.u-ser@example.com')
- eq(got, 'fake.u-ser@example.com')
-
-def test_ssh_extract_user_no_at():
- got = ssh.extract_user(
- 'ssh-somealgo '
- +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fakeuser')
- eq(got, 'fakeuser')
-
-def test_ssh_extract_user_caps():
- got = ssh.extract_user(
- 'ssh-somealgo '
- +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= Fake.User@Domain.Example.Com')
- eq(got, 'Fake.User@Domain.Example.Com')
-
-@raises(ssh.InsecureSSHKeyUsername)
-def test_ssh_extract_user_bad():
- try:
- ssh.extract_user(
- 'ssh-somealgo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
- +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= ER3%#@e%')
- except ssh.InsecureSSHKeyUsername, e:
- eq(str(e), "Username contains not allowed characters: 'ER3%#@e%'")
- raise e
diff --git a/gitosis/test/test_sshkey.py b/gitosis/test/test_sshkey.py
new file mode 100644
index 0000000..f44e250
--- /dev/null
+++ b/gitosis/test/test_sshkey.py
@@ -0,0 +1,87 @@
+from nose.tools import eq_ as eq, assert_raises, raises
+
+from gitosis import sshkey
+
+def test_sshkey_extract_user_simple():
+ got = sshkey.extract_user(
+ 'ssh-somealgo '
+ +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fakeuser@fakehost')
+ eq(got, 'fakeuser@fakehost')
+
+def test_sshkey_extract_user_domain():
+ got = sshkey.extract_user(
+ 'ssh-somealgo '
+ +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fakeuser@fakehost.example.com')
+ eq(got, 'fakeuser@fakehost.example.com')
+
+def test_sshkey_extract_user_domain_dashes():
+ got = sshkey.extract_user(
+ 'ssh-somealgo '
+ +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fakeuser@ridiculously-long.example.com')
+ eq(got, 'fakeuser@ridiculously-long.example.com')
+
+def test_sshkey_extract_user_underscore():
+ got = sshkey.extract_user(
+ 'ssh-somealgo '
+ +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fake_user@example.com')
+ eq(got, 'fake_user@example.com')
+
+def test_sshkey_extract_user_dot():
+ got = sshkey.extract_user(
+ 'ssh-somealgo '
+ +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fake.u.ser@example.com')
+ eq(got, 'fake.u.ser@example.com')
+
+def test_sshkey_extract_user_dash():
+ got = sshkey.extract_user(
+ 'ssh-somealgo '
+ +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fake.u-ser@example.com')
+ eq(got, 'fake.u-ser@example.com')
+
+def test_sshkey_extract_user_no_at():
+ got = sshkey.extract_user(
+ 'ssh-somealgo '
+ +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= fakeuser')
+ eq(got, 'fakeuser')
+
+def test_sshkey_extract_user_caps():
+ got = sshkey.extract_user(
+ 'ssh-somealgo '
+ +'0123456789ABCDEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= Fake.User@Domain.Example.Com')
+ eq(got, 'Fake.User@Domain.Example.Com')
+
+@raises(sshkey.InsecureSSHKeyUsername)
+def test_sshkey_extract_user_bad():
+ try:
+ sshkey.extract_user(
+ 'ssh-somealgo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+ +'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= ER3%#@e%')
+ except sshkey.InsecureSSHKeyUsername, e:
+ eq(str(e), "Username contains not allowed characters: 'ER3%#@e%'")
+ raise e