Esempio n. 1
0
def _decode_caveat_v2_v3(version, key, caveat):
    '''Decodes a version 2 or version 3 caveat.
    '''
    if (len(caveat) < 1 + _PUBLIC_KEY_PREFIX_LEN + _KEY_LEN +
            nacl.public.Box.NONCE_SIZE + 16):
        raise macaroonbakery.VerificationError('caveat id too short')
    original_caveat = caveat
    caveat = caveat[1:]  # skip version (already checked)

    pk_prefix = caveat[:_PUBLIC_KEY_PREFIX_LEN]
    caveat = caveat[_PUBLIC_KEY_PREFIX_LEN:]
    if key.public_key.encode(raw=True)[:_PUBLIC_KEY_PREFIX_LEN] != pk_prefix:
        raise macaroonbakery.VerificationError('public key mismatch')

    first_party_pub = caveat[:_KEY_LEN]
    caveat = caveat[_KEY_LEN:]
    nonce = caveat[:nacl.public.Box.NONCE_SIZE]
    caveat = caveat[nacl.public.Box.NONCE_SIZE:]
    fp_public_key = nacl.public.PublicKey(first_party_pub)
    box = nacl.public.Box(key.key, fp_public_key)
    data = box.decrypt(caveat, nonce)
    root_key, condition, ns = _decode_secret_part_v2_v3(version, data)
    return macaroonbakery.ThirdPartyCaveatInfo(
        condition=condition.decode('utf-8'),
        first_party_public_key=macaroonbakery.PublicKey(fp_public_key),
        third_party_key_pair=key,
        root_key=root_key,
        caveat=original_caveat,
        version=version,
        namespace=ns)
Esempio n. 2
0
def decode_caveat(key, caveat):
    '''Decode caveat by decrypting the encrypted part using key.

    @param key the nacl private key to decode.
    @param caveat bytes.
    @return ThirdPartyCaveatInfo
    '''
    if len(caveat) == 0:
        raise macaroonbakery.VerificationError('empty third party caveat')

    first = caveat[:1]
    if first == b'e':
        # 'e' will be the first byte if the caveatid is a base64
        # encoded JSON object.
        return _decode_caveat_v1(key, caveat)
    first_as_int = six.byte2int(first)
    if (first_as_int == macaroonbakery.BAKERY_V2
            or first_as_int == macaroonbakery.BAKERY_V3):
        if (len(caveat) < _VERSION3_CAVEAT_MIN_LEN
                and first_as_int == macaroonbakery.BAKERY_V3):
            # If it has the version 3 caveat tag and it's too short, it's
            # almost certainly an id, not an encrypted payload.
            raise macaroonbakery.VerificationError(
                'caveat id payload not provided for caveat id {}'.format(
                    caveat))
        return _decode_caveat_v2_v3(first_as_int, key, caveat)
    raise macaroonbakery.VerificationError('unknown version for caveat')
Esempio n. 3
0
def _decode_macaroon_id(id):
    storage_id = b''
    base64_decoded = False
    first = id[:1]
    if first == b'A':
        # The first byte is not a version number and it's 'A', which is the
        # base64 encoding of the top 6 bits (all zero) of the version number 2
        # or 3, so we assume that it's the base64 encoding of a new-style
        # macaroon id, so we base64 decode it.
        #
        # Note that old-style ids always start with an ASCII character >= 4
        # (> 32 in fact) so this logic won't be triggered for those.
        try:
            dec = utils.raw_b64decode(id.decode('utf-8'))
            # Set the id only on success.
            id = dec
            base64_decoded = True
        except:
            # if it's a bad encoding, we'll get an error which is fine
            pass

    # Trim any extraneous information from the id before retrieving
    # it from storage, including the UUID that's added when
    # creating macaroons to make all macaroons unique even if
    # they're using the same root key.
    first = six.byte2int(id[:1])
    if first == macaroonbakery.BAKERY_V2:
        # Skip the UUID at the start of the id.
        storage_id = id[1 + 16:]
    if first == macaroonbakery.BAKERY_V3:
        try:
            id1 = id_pb2.MacaroonId.FromString(id[1:])
        except google.protobuf.message.DecodeError:
            raise macaroonbakery.VerificationError(
                'no operations found in macaroon')
        if len(id1.ops) == 0 or len(id1.ops[0].actions) == 0:
            raise macaroonbakery.VerificationError(
                'no operations found in macaroon')

        ops = []
        for op in id1.ops:
            for action in op.actions:
                ops.append(macaroonbakery.Op(op.entity, action))
        return id1.storageId, ops

    if not base64_decoded and _is_lower_case_hex_char(first):
        # It's an old-style id, probably with a hyphenated UUID.
        # so trim that off.
        last = id.rfind(b'-')
        if last >= 0:
            storage_id = id[0:last]
    return storage_id, [macaroonbakery.LOGIN_OP]
    def macaroon_ops(self, ms):
        if len(ms) == 0:
            raise ValueError('no macaroons provided')

        m_id = json.loads(ms[0].identifier_bytes.decode('utf-8'))
        root_key = self._root_key_store.get(
            base64.urlsafe_b64decode(m_id['id'].encode('utf-8')))

        v = Verifier()

        class NoValidationOnFirstPartyCaveat(FirstPartyCaveatVerifierDelegate):
            def verify_first_party_caveat(self, verifier, caveat, signature):
                return True

        v.first_party_caveat_verifier_delegate = \
            NoValidationOnFirstPartyCaveat()
        ok = v.verify(macaroon=ms[0], key=root_key, discharge_macaroons=ms[1:])
        if not ok:
            raise macaroonbakery.VerificationError('invalid signature')
        conditions = []
        for m in ms:
            cavs = m.first_party_caveats()
            for cav in cavs:
                conditions.append(cav.caveat_id_bytes.decode('utf-8'))
        ops = []
        for op in m_id['ops']:
            ops.append(macaroonbakery.Op(entity=op[0], action=op[1]))
        return ops, conditions
def discharge(ctx, id, caveat, key, checker, locator):
    ''' Creates a macaroon to discharge a third party caveat.

    The given parameters specify the caveat and how it should be checked.
    The condition implicit in the caveat is checked for validity using checker.
    If it is valid, a new macaroon is returned which discharges the caveat.
    The macaroon is created with a version derived from the version that was
    used to encode the id.

    :param id: (bytes) holds the id to give to the discharge macaroon.
    If Caveat is empty, then the id also holds the encrypted third party
    caveat.
    :param caveat: (bytes) holds the encrypted third party caveat.
    If this is None, id will be used.
    :param key: holds the key to use to decrypt the third party caveat
    information and to encrypt any additional third party caveats returned by
    the caveat checker.
    :param checker: used to check the third party caveat, and may also return
    further caveats to be added to the discharge macaroon.
    :param locator: used to information on third parties referred to by third
    party caveats returned by the Checker.
    '''
    caveat_id_prefix = []
    if caveat is None:
        # The caveat information is encoded in the id itself.
        caveat = id
    else:
        # We've been given an explicit id, so when extra third party
        # caveats are added, use that id as the prefix
        # for any more ids.
        caveat_id_prefix = id
    cav_info = macaroonbakery.decode_caveat(key, caveat)

    # Note that we don't check the error - we allow the
    # third party checker to see even caveats that we can't
    # understand.
    try:
        cond, arg = checkers.parse_caveat(cav_info.condition)
    except ValueError as exc:
        raise macaroonbakery.VerificationError(exc.args[0])

    if cond == checkers.COND_NEED_DECLARED:
        cav_info = cav_info._replace(condition=arg.encode('utf-8'))
        caveats = _check_need_declared(ctx, cav_info, checker)
    else:
        caveats = checker.check_third_party_caveat(ctx, cav_info)

    # Note that the discharge macaroon does not need to
    # be stored persistently. Indeed, it would be a problem if
    # we did, because then the macaroon could potentially be used
    # for normal authorization with the third party.
    m = macaroonbakery.Macaroon(cav_info.root_key, id, '', cav_info.version,
                                cav_info.namespace)
    m._caveat_id_prefix = caveat_id_prefix
    if caveats is not None:
        for cav in caveats:
            m.add_caveat(cav, key, locator)
    return m
Esempio n. 6
0
    def macaroon_ops(self, macaroons):
        ''' This method makes the oven satisfy the MacaroonOpStore protocol
        required by the Checker class.

        For macaroons minted with previous bakery versions, it always
        returns a single LoginOp operation.

        :param macaroons:
        :return:
        '''
        if len(macaroons) == 0:
            raise ValueError('no macaroons provided')

        storage_id, ops = _decode_macaroon_id(macaroons[0].identifier_bytes)
        root_key = self.root_keystore_for_ops(ops).get(storage_id)
        if root_key is None:
            raise bakery.VerificationError('macaroon key not found in storage')
        v = Verifier()
        conditions = []

        def validator(condition):
            # Verify the macaroon's signature only. Don't check any of the
            # caveats yet but save them so that we can return them.
            conditions.append(condition)
            return True

        v.satisfy_general(validator)
        try:
            v.verify(macaroons[0], root_key, macaroons[1:])
        except (MacaroonUnmetCaveatException,
                MacaroonInvalidSignatureException) as exc:
            raise bakery.VerificationError('verification failed: {}'.format(
                exc.args[0]))

        if (self.ops_store is not None and len(ops) == 1
                and ops[0].entity.startswith('multi-')):
            # It's a multi-op entity, so retrieve the actual operations
            # it's associated with.
            ops = self.ops_store.get_ops(ops[0].entity)

        return ops, conditions
def _check_need_declared(ctx, cav_info, checker):
    arg = cav_info.condition.decode('utf-8')
    i = arg.find(' ')
    if i <= 0:
        raise macaroonbakery.VerificationError(
            'need-declared caveat requires an argument, got %q'.format(arg))
    need_declared = arg[0:i].split(',')
    for d in need_declared:
        if d == '':
            raise macaroonbakery.VerificationError('need-declared caveat with '
                                                   'empty required attribute')
    if len(need_declared) == 0:
        raise macaroonbakery.VerificationError('need-declared caveat with no '
                                               'required attributes')
    cav_info = cav_info._replace(condition=arg[i + 1:].encode('utf-8'))
    caveats = checker.check_third_party_caveat(ctx, cav_info)
    declared = {}
    for cav in caveats:
        if cav.location is not None and cav.location != '':
            continue
        # Note that we ignore the error. We allow the service to
        # generate caveats that we don't understand here.
        try:
            cond, arg = checkers.parse_caveat(cav.condition)
        except ValueError:
            continue
        if cond != checkers.COND_DECLARED:
            continue
        parts = arg.split()
        if len(parts) != 2:
            raise macaroonbakery.VerificationError('declared caveat has no '
                                                   'value')
        declared[parts[0]] = True
    # Add empty declarations for everything mentioned in need-declared
    # that was not actually declared.
    for d in need_declared:
        if not declared.get(d, False):
            caveats.append(checkers.declared_caveat(d, ''))
    return caveats
Esempio n. 8
0
def _decode_secret_part_v2_v3(version, data):
    if len(data) < 1:
        raise macaroonbakery.VerificationError('secret part too short')
    got_version = six.byte2int(data[:1])
    data = data[1:]
    if version != got_version:
        raise macaroonbakery.VerificationError(
            'unexpected secret part version, got {} want {}'.format(
                got_version, version))
    root_key_length, read = decode_uvarint(data)
    data = data[read:]
    root_key = data[:root_key_length]
    data = data[root_key_length:]
    if version >= macaroonbakery.BAKERY_V3:
        namespace_length, read = decode_uvarint(data)
        data = data[read:]
        ns_data = data[:namespace_length]
        data = data[namespace_length:]
        ns = checkers.deserialize_namespace(ns_data)
    else:
        ns = macaroonbakery.legacy_namespace()
    return root_key, data, ns