|
| 1 | +"""Utilities for signing ssh-certificates.""" |
| 2 | +import io |
| 3 | + |
| 4 | +from . import formats, util |
| 5 | + |
| 6 | + |
| 7 | +def _parse_stringlist(i): |
| 8 | + res = [] |
| 9 | + length, = util.recv(i, '>L') |
| 10 | + while length >= 4: |
| 11 | + size, = util.recv(i, '>L') |
| 12 | + length -= 4 |
| 13 | + size = min(size, length) |
| 14 | + if size == 0: |
| 15 | + continue |
| 16 | + res.append(util.recv(i, size).decode('utf8')) |
| 17 | + length -= size |
| 18 | + return res |
| 19 | + |
| 20 | + |
| 21 | +def parse(blob): |
| 22 | + """Parses a data blob to a to-be-signed ssh-certificate.""" |
| 23 | + # https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys |
| 24 | + res = {} |
| 25 | + i = io.BytesIO(blob) |
| 26 | + firstString = util.read_frame(i) |
| 27 | + if firstString. endswith( b'[email protected]'): |
| 28 | + res['isCertificate'] = True |
| 29 | + _certificate_key_type = firstString |
| 30 | + _nonce = util.read_frame(i) |
| 31 | + if (_certificate_key_type.startswith(b'ssh-rsa')): |
| 32 | + _pub_key = {} |
| 33 | + _pub_key['e'] = util.read_frame(i) |
| 34 | + _pub_key['n'] = util.read_frame(i) |
| 35 | + elif (_certificate_key_type.startswith(b'ssh-dsa')): |
| 36 | + _pub_key = {} |
| 37 | + _pub_key['p'] = util.read_frame(i) |
| 38 | + _pub_key['q'] = util.read_frame(i) |
| 39 | + _pub_key['g'] = util.read_frame(i) |
| 40 | + _pub_key['y'] = util.read_frame(i) |
| 41 | + elif (_certificate_key_type.startswith(b'ecdsa-sha2-nistp')): |
| 42 | + _curve = util.read_frame(i) |
| 43 | + _pub_key = util.read_frame(i) |
| 44 | + elif (_certificate_key_type.startswith(b'ssh-ed25519')): |
| 45 | + _pub_key = util.read_frame(i) |
| 46 | + else: |
| 47 | + raise ValueError('unknown certificate key type: '+_certificate_key_type.decode('utf8')) |
| 48 | + _serial_number, = util.recv(i, '>Q') |
| 49 | + res['certificate_type'], = util.recv(i, '>L') |
| 50 | + _key_id_ = util.read_frame(i) |
| 51 | + res['principals'] = _parse_stringlist(i) |
| 52 | + res['principals'] = ', '.join(res['principals']) |
| 53 | + _valid_after, = util.recv(i, '>Q') |
| 54 | + _valid_before, = util.recv(i, '>Q') |
| 55 | + _critical_options = _parse_stringlist(i) |
| 56 | + _extensions = _parse_stringlist(i) |
| 57 | + _reserved = util.read_frame(i) |
| 58 | + _signature_key = util.read_frame(i) |
| 59 | + assert not i.read() |
| 60 | + return res |
| 61 | + res['isCertificate'] = False |
| 62 | + i.close() |
| 63 | + return res |
| 64 | + |
| 65 | + |
| 66 | +def format(certificate): |
| 67 | + """ |
| 68 | + Makes certificate better human readable. |
| 69 | +
|
| 70 | + Formats list properties to comma seperated strings and |
| 71 | + the signature key to human readable string. |
| 72 | + """ |
| 73 | + certificate['principals'] = ', '.join(certificate['principals']) |
| 74 | + certificate['critical_options'] = ', '.join(certificate['critical_options']) |
| 75 | + certificate['extensions'] = ', '.join(certificate['extensions']) |
| 76 | + certificate['signature_key'] = formats.parse_pubkey(certificate['signature_key']) |
0 commit comments