Ejemplo n.º 1
0
    def __init__(self, verbosity=0, reader=None):
        self._cmd = YkPivCmd(verbosity=verbosity, reader=reader)

        self._state = POINTER(ykpiv_state)()
        if not reader:
            reader = 'Yubikey'

        self._chuid = None
        self._ccc = None
        self._pin_blocked = False
        self._verbosity = verbosity
        self._reader = reader
        self._certs = {}

        check(ykpiv.ykpiv_init(byref(self._state), self._verbosity))
        self._connect()
        self._read_status()

        if not self.chuid:
            try:
                self.set_chuid()
            except ValueError:
                pass  # Not autheniticated, perhaps?

        if not self.ccc:
            try:
                self.set_ccc()
            except ValueError:
                pass  # Not autheniticated, perhaps?
Ejemplo n.º 2
0
    def __init__(self, verbosity=0, reader=None):
        self._cmd = YkPivCmd(verbosity=verbosity, reader=reader)

        self._state = POINTER(ykpiv_state)()
        if not reader:
            reader = 'Yubikey'

        self._chuid = None
        self._ccc = None
        self._pin_blocked = False
        self._verbosity = verbosity
        self._reader = reader
        self._certs = {}

        check(ykpiv.ykpiv_init(byref(self._state), self._verbosity))
        self._connect()
        self._read_status()

        if not self.chuid:
            try:
                self.set_chuid()
            except ValueError:
                pass  # Not autheniticated, perhaps?

        if not self.ccc:
            try:
                self.set_ccc()
            except ValueError:
                pass  # Not autheniticated, perhaps?
Ejemplo n.º 3
0
class YkPiv(object):

    def __init__(self, verbosity=0, reader=None):
        self._cmd = YkPivCmd(verbosity=verbosity, reader=reader)

        self._state = POINTER(ykpiv_state)()
        if not reader:
            reader = 'Yubikey'

        self._chuid = None
        self._ccc = None
        self._pin_blocked = False
        self._verbosity = verbosity
        self._reader = reader
        self._certs = {}

        check(ykpiv.ykpiv_init(byref(self._state), self._verbosity))
        self._connect()
        self._read_status()

        if not self.chuid:
            try:
                self.set_chuid()
            except ValueError:
                pass  # Not autheniticated, perhaps?

        if not self.ccc:
            try:
                self.set_ccc()
            except ValueError:
                pass  # Not autheniticated, perhaps?

    def reconnect(self):
        check(ykpiv.ykpiv_disconnect(self._state))
        self._reset()

    def _connect(self):
        check(ykpiv.ykpiv_connect(self._state, self._reader.encode('utf8')))

        self._read_version()
        self._read_chuid()

    def _read_status(self):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            data = self._cmd.run('-a', 'status')
            lines = data.splitlines()
            chunk = []
            while lines:
                line = lines.pop(0)
                if chunk and not line.startswith(b'\t'):
                    self._parse_status(chunk)
                    chunk = []
                chunk.append(line)
            if chunk:
                self._parse_status(chunk)
            self._status = data
        finally:
            self._reset()

    def _parse_status(self, chunk):
        parts, rest = chunk[0].split(), chunk[1:]
        if parts[0] == b'Slot' and rest:
            self._parse_slot(parts[1][:-1], rest)
        elif parts[0] == b'PIN':
            self._pin_blocked = parts[-1] == '0'

    def _parse_slot(self, slot, lines):
        slot = slot.decode('ascii')
        self._certs[slot] = dict(l.strip().split(b':\t', 1) for l in lines)

    def _read_version(self):
        v = create_string_buffer(10)
        check(ykpiv.ykpiv_get_version(self._state, v, sizeof(v)))
        self._version = v.value

    def _read_chuid(self):
        try:
            chuid_data = self.fetch_object(YKPIV.OBJ.CHUID)[29:29 + 16]
            self._chuid = b2a_hex(chuid_data)
        except PivError:  # No chuid set?
            self._chuid = None

    def _read_ccc(self):
        try:
            ccc_data = self.fetch_object(YKPIV.OBJ.CAPABILITY)[29:29 + 16]
            self._ccc = b2a_hex(ccc_data)
        except PivError:  # No ccc set?
            self._ccc = None

    def __del__(self):
        check(ykpiv.ykpiv_done(self._state))

    def _reset(self):
        self._connect()
        args = self._cmd._base_args
        if '-P' in args:
            self.verify_pin(args[args.index('-P') + 1])
        if '-k' in args:
            self.authenticate(a2b_hex(args[args.index('-k') + 1]))

    @property
    def version(self):
        return self._version

    @property
    def chuid(self):
        return self._chuid

    @property
    def ccc(self):
        return self._ccc

    @property
    def pin_blocked(self):
        return self._pin_blocked

    @property
    def certs(self):
        return dict(self._certs)

    def set_chuid(self):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            self._cmd.run('-a', 'set-chuid')
        finally:
            self._reset()

    def set_ccc(self):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            self._cmd.run('-a', 'set-ccc')
        finally:
            self._reset()

    def authenticate(self, key=None):
        if key is None:
            key = DEFAULT_KEY
        elif len(key) != KEY_LEN:
            raise ValueError('Key must be %d bytes' % KEY_LEN)
        c_key = (c_ubyte * KEY_LEN).from_buffer_copy(key)
        check(ykpiv.ykpiv_authenticate(self._state, c_key))
        self._cmd.set_arg('-k', b2a_hex(key))
        if not self.chuid:
            self.set_chuid()

    def set_authentication(self, key):
        if len(key) != KEY_LEN:
            raise ValueError('Key must be %d bytes' % KEY_LEN)
        c_key = (c_ubyte * len(key)).from_buffer_copy(key)
        check(ykpiv.ykpiv_set_mgmkey(self._state, c_key))
        self._cmd.set_arg('-k', b2a_hex(key))

    def verify_pin(self, pin):
        if isinstance(pin, text_type):
            pin = pin.encode('utf8')
        buf = create_string_buffer(pin)
        tries = c_int(-1)
        rc = ykpiv.ykpiv_verify(self._state, buf, byref(tries))

        if rc == YKPIV.WRONG_PIN:
            if tries.value == 0:
                self._pin_blocked = True
                self._cmd.set_arg('-P', None)
            raise WrongPinError(tries.value)
        check(rc)
        self._cmd.set_arg('-P', pin)

    def set_pin(self, pin):
        if isinstance(pin, text_type):
            pin = pin.encode('utf8')
        if len(pin) > 8:
            raise ValueError(m.pin_too_long)
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            self._cmd.change_pin(pin)
        finally:
            self._reset()

    def reset_pin(self, puk, new_pin):
        if isinstance(new_pin, text_type):
            new_pin = new_pin.encode('utf8')
        if len(new_pin) > 8:
            raise ValueError(m.pin_too_long)
        if isinstance(puk, text_type):
            puk = puk.encode('utf8')
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            self._cmd.reset_pin(puk, new_pin)
        except ValueError as e:
            wrap_puk_error(e)
        finally:
            self._reset()
            self._read_status()

    def set_puk(self, puk, new_puk):
        if isinstance(puk, text_type):
            puk = puk.encode('utf8')
        if isinstance(new_puk, text_type):
            new_puk = new_puk.encode('utf8')
        if len(new_puk) > 8:
            raise ValueError(m.puk_too_long)

        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            self._cmd.change_puk(puk, new_puk)
        except ValueError as e:
            wrap_puk_error(e)
        finally:
            self._reset()

    def reset_device(self):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            self._cmd.run('-a', 'reset')
        finally:
            del self._cmd

    def fetch_object(self, object_id):
        buf = (c_ubyte * 4096)()
        buf_len = c_size_t(sizeof(buf))

        check(ykpiv.ykpiv_fetch_object(self._state, object_id, buf,
                                       byref(buf_len)))
        return b''.join(map(int2byte, buf[:buf_len.value]))

    def save_object(self, object_id, data):
        c_data = (c_ubyte * len(data)).from_buffer_copy(data)
        check(ykpiv.ykpiv_save_object(self._state, object_id, c_data,
                                      len(data)))

    def generate(self, slot, algorithm, pin_policy, touch_policy):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            return self._cmd.generate(slot, algorithm, pin_policy, touch_policy)
        finally:
            self._reset()

    def create_csr(self, subject, pubkey_pem, slot):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            return self._cmd.create_csr(subject, pubkey_pem, slot)
        finally:
            self._reset()

    def create_selfsigned_cert(self, subject, pubkey_pem, slot, valid_days=365):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            return self._cmd.create_ssc(subject, pubkey_pem, slot, valid_days)
        finally:
            self._reset()

    def import_cert(self, cert_pem, slot, frmt='PEM', password=None):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            return self._cmd.import_cert(cert_pem, slot, frmt, password)
        finally:
            self._reset()
            self._read_status()

    def import_key(self, cert_pem, slot, frmt, password, pin_policy,
                   touch_policy):
        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            return self._cmd.import_key(cert_pem, slot, frmt, password,
                                        pin_policy, touch_policy)
        finally:
            self._reset()

    def sign_data(self, slot, hashed, algorithm=YKPIV.ALGO.RSA2048):
        c_hashed = (c_ubyte * len(hashed)).from_buffer_copy(hashed)
        buf = (c_ubyte * 4096)()
        buf_len = c_size_t(sizeof(buf))
        check(ykpiv.ykpiv_sign_data(self._state, c_hashed, len(hashed), buf,
                                    byref(buf_len), algorithm, int(slot, 16)))
        return ''.join(map(int2byte, buf[:buf_len.value]))

    def read_cert(self, slot):
        try:
            data = self.fetch_object(CERT_SLOTS[slot])
        except PivError:
            return None
        cert, rest = der_read(data, 0x70)
        zipped, rest = der_read(rest, 0x71)
        if zipped != b'\0':
            pass  # TODO: cert is compressed, uncompress.
        return cert

    def delete_cert(self, slot):
        if slot not in self._certs:
            raise ValueError('No certificate loaded in slot: %s' % slot)

        try:
            check(ykpiv.ykpiv_disconnect(self._state))
            self._cmd.delete_cert(slot)
            del self._certs[slot]
        finally:
            self._reset()
Ejemplo n.º 4
0
class YkPiv(object):

    def __init__(self, verbosity=0, reader=None):
        self._cmd = YkPivCmd(verbosity=verbosity, reader=reader)

        self._state = POINTER(ykpiv_state)()
        if not reader:
            reader = 'Yubikey'

        self._chuid = None
        self._pin_blocked = False
        self._verbosity = verbosity
        self._reader = reader
        self._certs = {}

        check(ykpiv_init(byref(self._state), self._verbosity))
        self._connect()
        self._read_status()

        if not self.chuid:
            try:
                self.set_chuid()
            except ValueError:
                pass  # Not autheniticated, perhaps?

    def reconnect(self):
        check(ykpiv_disconnect(self._state))
        self._reset()

    def _connect(self):
        check(ykpiv_connect(self._state, self._reader))

        self._read_version()
        self._read_chuid()

    def _read_status(self):
        try:
            check(ykpiv_disconnect(self._state))
            data = self._cmd.run('-a', 'status')
            lines = data.splitlines()
            chunk = []
            while lines:
                line = lines.pop(0)
                if chunk and not line.startswith('\t'):
                    self._parse_status(chunk)
                    chunk = []
                chunk.append(line)
            if chunk:
                self._parse_status(chunk)
            self._status = data
        finally:
            self._reset()

    def _parse_status(self, chunk):
        parts, rest = chunk[0].split(), chunk[1:]
        if parts[0] == 'Slot' and rest:
            self._parse_slot(parts[1][:-1], rest)
        elif parts[0] == 'PIN':
            self._pin_blocked = parts[-1] == '0'

    def _parse_slot(self, slot, lines):
        self._certs[slot] = dict(l.strip().split(':\t', 1) for l in lines)

    def _read_version(self):
        v = create_string_buffer(10)
        check(ykpiv_get_version(self._state, v, sizeof(v)))
        self._version = v.value

    def _read_chuid(self):
        try:
            chuid_data = self.fetch_object(YKPIV_OBJ_CHUID)[29:29 + 16]
            self._chuid = chuid_data.encode('hex')
        except PivError:  # No chuid set?
            self._chuid = None

    def __del__(self):
        check(ykpiv_done(self._state))

    def _reset(self):
        self._connect()
        args = self._cmd._base_args
        if '-P' in args:
            self.verify_pin(args[args.index('-P') + 1])
        if '-k' in args:
            self.authenticate(args[args.index('-k') + 1].decode('hex'))

    @property
    def version(self):
        return self._version

    @property
    def chuid(self):
        return self._chuid

    @property
    def pin_blocked(self):
        return self._pin_blocked

    @property
    def certs(self):
        return dict(self._certs)

    def set_chuid(self):
        try:
            check(ykpiv_disconnect(self._state))
            self._cmd.run('-a', 'set-chuid')
        finally:
            self._reset()

    def authenticate(self, key=None):
        if key is None:
            key = DEFAULT_KEY
        elif len(key) != KEY_LEN:
            raise ValueError('Key must be %d bytes' % KEY_LEN)
        c_key = (c_ubyte * KEY_LEN).from_buffer_copy(key)
        check(ykpiv_authenticate(self._state, c_key))
        self._cmd.set_arg('-k', key.encode('hex'))
        if not self.chuid:
            self.set_chuid()

    def set_authentication(self, key):
        if len(key) != KEY_LEN:
            raise ValueError('Key must be %d bytes' % KEY_LEN)
        c_key = (c_ubyte * len(key)).from_buffer_copy(key)
        check(ykpiv_set_mgmkey(self._state, c_key))
        self._cmd.set_arg('-k', key.encode('hex'))

    def verify_pin(self, pin):
        if isinstance(pin, unicode):
            pin = pin.encode('utf8')
        buf = create_string_buffer(pin)
        tries = c_int(-1)
        rc = ykpiv_verify(self._state, buf, byref(tries))

        if rc == YKPIV_WRONG_PIN:
            if tries.value == 0:
                self._pin_blocked = True
                self._cmd.set_arg('-P', None)
            raise WrongPinError(tries.value)
        check(rc)
        self._cmd.set_arg('-P', pin)

    def set_pin(self, pin):
        if isinstance(pin, unicode):
            pin = pin.encode('utf8')
        if len(pin) > 8:
            raise ValueError(m.pin_too_long)
        try:
            check(ykpiv_disconnect(self._state))
            self._cmd.change_pin(pin)
        finally:
            self._reset()

    def reset_pin(self, puk, new_pin):
        if isinstance(new_pin, unicode):
            new_pin = new_pin.encode('utf8')
        if len(new_pin) > 8:
            raise ValueError(m.pin_too_long)
        if isinstance(puk, unicode):
            puk = puk.encode('utf8')
        try:
            check(ykpiv_disconnect(self._state))
            self._cmd.reset_pin(puk, new_pin)
        except ValueError as e:
            wrap_puk_error(e)
        finally:
            self._reset()
            self._read_status()

    def set_puk(self, puk, new_puk):
        if isinstance(puk, unicode):
            puk = puk.encode('utf8')
        if isinstance(new_puk, unicode):
            new_puk = new_puk.encode('utf8')
        if len(new_puk) > 8:
            raise ValueError(m.puk_too_long)

        try:
            check(ykpiv_disconnect(self._state))
            self._cmd.change_puk(puk, new_puk)
        except ValueError as e:
            wrap_puk_error(e)
        finally:
            self._reset()

    def reset_device(self):
        try:
            check(ykpiv_disconnect(self._state))
            self._cmd.run('-a', 'reset')
        finally:
            del self._cmd

    def fetch_object(self, object_id):
        buf = (c_ubyte * 4096)()
        buf_len = c_size_t(sizeof(buf))

        check(ykpiv_fetch_object(self._state, object_id, buf, byref(buf_len)))
        return ''.join(map(chr, buf[:buf_len.value]))

    def save_object(self, object_id, data):
        c_data = (c_ubyte * len(data)).from_buffer_copy(data)
        check(ykpiv_save_object(self._state, object_id, c_data, len(data)))

    def generate(self, slot, algorithm, pin_policy, touch_policy):
        try:
            check(ykpiv_disconnect(self._state))
            return self._cmd.generate(slot, algorithm, pin_policy, touch_policy)
        finally:
            self._reset()

    def create_csr(self, subject, pubkey_pem, slot):
        try:
            check(ykpiv_disconnect(self._state))
            return self._cmd.create_csr(subject, pubkey_pem, slot)
        finally:
            self._reset()

    def create_selfsigned_cert(self, subject, pubkey_pem, slot):
        try:
            check(ykpiv_disconnect(self._state))
            return self._cmd.create_ssc(subject, pubkey_pem, slot)
        finally:
            self._reset()

    def import_cert(self, cert_pem, slot, frmt='PEM', password=None):
        try:
            check(ykpiv_disconnect(self._state))
            return self._cmd.import_cert(cert_pem, slot, frmt, password)
        finally:
            self._reset()
            self._read_status()

    def import_key(self, cert_pem, slot, frmt, password, pin_policy,
                   touch_policy):
        try:
            check(ykpiv_disconnect(self._state))
            return self._cmd.import_key(cert_pem, slot, frmt, password,
                                        pin_policy, touch_policy)
        finally:
            self._reset()

    def sign_data(self, slot, hashed, algorithm=YKPIV_ALGO_RSA2048):
        c_hashed = (c_ubyte * len(hashed)).from_buffer_copy(hashed)
        buf = (c_ubyte * 4096)()
        buf_len = c_size_t(sizeof(buf))
        check(ykpiv_sign_data(self._state, c_hashed, len(hashed), buf,
                              byref(buf_len), algorithm, int(slot, 16)))
        return ''.join(map(chr, buf[:buf_len.value]))

    def read_cert(self, slot):
        try:
            data = self.fetch_object(CERT_SLOTS[slot])
        except PivError:
            return None
        cert, rest = der_read(data, 0x70)
        zipped, rest = der_read(rest, 0x71)
        if zipped != chr(0):
            pass  # TODO: cert is compressed, uncompress.
        return cert

    def delete_cert(self, slot):
        if slot not in self._certs:
            raise ValueError('No certificate loaded in slot: %s' % slot)

        try:
            check(ykpiv_disconnect(self._state))
            self._cmd.delete_cert(slot)
            del self._certs[slot]
        finally:
            self._reset()