All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.biscuitsec.biscuit.token.format.SerializedBiscuit Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
package org.biscuitsec.biscuit.token.format;

import biscuit.format.schema.Schema;
import io.vavr.Tuple2;
import org.biscuitsec.biscuit.crypto.KeyDelegate;
import org.biscuitsec.biscuit.crypto.KeyPair;
import org.biscuitsec.biscuit.crypto.PublicKey;
import org.biscuitsec.biscuit.datalog.SymbolTable;
import org.biscuitsec.biscuit.error.Error;
import org.biscuitsec.biscuit.token.Block;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import io.vavr.Tuple3;
import io.vavr.control.Either;
import io.vavr.control.Option;
import net.i2p.crypto.eddsa.EdDSAEngine;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.*;
import java.util.*;

import static io.vavr.API.Left;
import static io.vavr.API.Right;

/**
 * Intermediate representation of a token before full serialization
 */
public class SerializedBiscuit {
    public SignedBlock authority;
    public List blocks;
    public Proof proof;
    public Option root_key_id;

    public static int MIN_SCHEMA_VERSION = 3;
    public static int MAX_SCHEMA_VERSION = 5;

    /**
     * Deserializes a SerializedBiscuit from a byte array
     *
     * @param slice
     * @return
     */
    static public SerializedBiscuit from_bytes(byte[] slice, org.biscuitsec.biscuit.crypto.PublicKey root) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
        try {
            Schema.Biscuit data = Schema.Biscuit.parseFrom(slice);

            return from_bytes_inner(data, root);
        } catch (InvalidProtocolBufferException e) {
            throw new Error.FormatError.DeserializationError(e.toString());
        }
    }

    /**
     * Deserializes a SerializedBiscuit from a byte array
     *
     * @param slice
     * @return
     */
    static public SerializedBiscuit from_bytes(byte[] slice, KeyDelegate delegate) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
        try {
            Schema.Biscuit data = Schema.Biscuit.parseFrom(slice);

            Option root_key_id = Option.none();
            if (data.hasRootKeyId()) {
                root_key_id = Option.some(data.getRootKeyId());
            }

            Option root = delegate.root_key(root_key_id);
            if (root.isEmpty()) {
                throw new InvalidKeyException("unknown root key id");
            }

            return from_bytes_inner(data, root.get());
        } catch (InvalidProtocolBufferException e) {
            throw new Error.FormatError.DeserializationError(e.toString());
        }
    }

    static SerializedBiscuit from_bytes_inner(Schema.Biscuit data, org.biscuitsec.biscuit.crypto.PublicKey root) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
        SerializedBiscuit b = SerializedBiscuit.deserialize(data);
        if (data.hasRootKeyId()) {
            b.root_key_id = Option.some(data.getRootKeyId());
        }

        Either res = b.verify(root);
        if (res.isLeft()) {
            throw res.getLeft();
        } else {
            return b;
        }

    }

    /**
     * Warning: this deserializes without verifying the signature
     *
     * @param slice
     * @return SerializedBiscuit
     * @throws Error.FormatError.DeserializationError
     */
    static public SerializedBiscuit unsafe_deserialize(byte[] slice) throws Error.FormatError.DeserializationError {
        try {
            Schema.Biscuit data = Schema.Biscuit.parseFrom(slice);
            return SerializedBiscuit.deserialize(data);
        } catch (InvalidProtocolBufferException e) {
            throw new Error.FormatError.DeserializationError(e.toString());
        }
    }

    /**
     * Warning: this deserializes without verifying the signature
     *
     * @param data
     * @return SerializedBiscuit
     * @throws Error.FormatError.DeserializationError
     */
    static private SerializedBiscuit deserialize(Schema.Biscuit data) throws Error.FormatError.DeserializationError {
        if(data.getAuthority().hasExternalSignature()) {
            throw new Error.FormatError.DeserializationError("the authority block must not contain an external signature");
        }

        SignedBlock authority = new SignedBlock(
                data.getAuthority().getBlock().toByteArray(),
                org.biscuitsec.biscuit.crypto.PublicKey.deserialize(data.getAuthority().getNextKey()),
                data.getAuthority().getSignature().toByteArray(),
                Option.none()
        );

        ArrayList blocks = new ArrayList<>();
        for (Schema.SignedBlock block : data.getBlocksList()) {
            Option external = Option.none();
            if(block.hasExternalSignature() && block.getExternalSignature().hasPublicKey()
                && block.getExternalSignature().hasSignature()) {
                Schema.ExternalSignature ex = block.getExternalSignature();
                external = Option.some(new ExternalSignature(
                        org.biscuitsec.biscuit.crypto.PublicKey.deserialize(ex.getPublicKey()),
                        ex.getSignature().toByteArray()));

            }
            blocks.add(new SignedBlock(
                    block.getBlock().toByteArray(),
                    org.biscuitsec.biscuit.crypto.PublicKey.deserialize(block.getNextKey()),
                    block.getSignature().toByteArray(),
                    external
            ));
        }

        Option secretKey = Option.none();
        if (data.getProof().hasNextSecret()) {
            secretKey = Option.some(new org.biscuitsec.biscuit.crypto.KeyPair(data.getProof().getNextSecret().toByteArray()));
        }

        Option signature = Option.none();
        if (data.getProof().hasFinalSignature()) {
            signature = Option.some(data.getProof().getFinalSignature().toByteArray());
        }

        if (secretKey.isEmpty() && signature.isEmpty()) {
            throw new Error.FormatError.DeserializationError("empty proof");
        }
        Proof proof = new Proof(secretKey, signature);

        return new SerializedBiscuit(authority, blocks, proof);
    }


    /**
     * Serializes a SerializedBiscuit to a byte array
     *
     * @return
     */
    public byte[] serialize() throws Error.FormatError.SerializationError {
        Schema.Biscuit.Builder biscuitBuilder = Schema.Biscuit.newBuilder();
        Schema.SignedBlock.Builder authorityBuilder = Schema.SignedBlock.newBuilder();
        {
            SignedBlock block = this.authority;
            authorityBuilder.setBlock(ByteString.copyFrom(block.block));
            authorityBuilder.setNextKey(block.key.serialize());
            authorityBuilder.setSignature(ByteString.copyFrom(block.signature));
        }
        biscuitBuilder.setAuthority(authorityBuilder.build());

        for (SignedBlock block : this.blocks) {
            Schema.SignedBlock.Builder blockBuilder = Schema.SignedBlock.newBuilder();
            blockBuilder.setBlock(ByteString.copyFrom(block.block));
            blockBuilder.setNextKey(block.key.serialize());
            blockBuilder.setSignature(ByteString.copyFrom(block.signature));

            if (block.externalSignature.isDefined()) {
                ExternalSignature externalSignature = block.externalSignature.get();
                Schema.ExternalSignature.Builder externalSignatureBuilder = Schema.ExternalSignature.newBuilder();
                externalSignatureBuilder.setPublicKey(externalSignature.key.serialize());
                externalSignatureBuilder.setSignature(ByteString.copyFrom(externalSignature.signature));
                blockBuilder.setExternalSignature(externalSignatureBuilder.build());
            }

            biscuitBuilder.addBlocks(blockBuilder.build());
        }

        Schema.Proof.Builder proofBuilder = Schema.Proof.newBuilder();
        if (!this.proof.secretKey.isEmpty()) {
            proofBuilder.setNextSecret(ByteString.copyFrom(this.proof.secretKey.get().toBytes()));
        } else {
            proofBuilder.setFinalSignature(ByteString.copyFrom(this.proof.signature.get()));
        }

        biscuitBuilder.setProof(proofBuilder.build());
        if (!this.root_key_id.isEmpty()) {
            biscuitBuilder.setRootKeyId(this.root_key_id.get());
        }

        Schema.Biscuit biscuit = biscuitBuilder.build();

        try {
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            biscuit.writeTo(stream);
            return stream.toByteArray();
        } catch (IOException e) {
            throw new Error.FormatError.SerializationError(e.toString());
        }

    }

    static public Either make(final org.biscuitsec.biscuit.crypto.KeyPair root,
                                                                    final Block authority, final org.biscuitsec.biscuit.crypto.KeyPair next) {

        return make(root, Option.none(), authority, next);
    }

    static public Either make(final org.biscuitsec.biscuit.crypto.KeyPair root, final Option root_key_id,
                                                                    final Block authority, final org.biscuitsec.biscuit.crypto.KeyPair next) {
        Schema.Block b = authority.serialize();
        try {
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            b.writeTo(stream);
            byte[] block = stream.toByteArray();
            org.biscuitsec.biscuit.crypto.PublicKey next_key = next.public_key();
            ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
            algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber()));
            algo_buf.flip();

            Signature sgr = KeyPair.generateSignature(root.public_key().algorithm);
            sgr.initSign(root.private_key);
            sgr.update(block);
            sgr.update(algo_buf);
            sgr.update(next_key.toBytes());
            byte[] signature = sgr.sign();

            SignedBlock signedBlock = new SignedBlock(block, next_key, signature, Option.none());
            Proof proof = new Proof(next);

            return Right(new SerializedBiscuit(signedBlock, new ArrayList<>(), proof, root_key_id));
        } catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
            return Left(new Error.FormatError.SerializationError(e.toString()));
        }
    }

    public Either append(final org.biscuitsec.biscuit.crypto.KeyPair next,
                                                               final Block newBlock, Option externalSignature) {
        if (this.proof.secretKey.isEmpty()) {
            return Left(new Error.FormatError.SerializationError("the token is sealed"));
        }

        Schema.Block b = newBlock.serialize();
        try {
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            b.writeTo(stream);

            byte[] block = stream.toByteArray();
            org.biscuitsec.biscuit.crypto.PublicKey next_key = next.public_key();
            ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
            algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber()));
            algo_buf.flip();

            Signature sgr = new EdDSAEngine(MessageDigest.getInstance(org.biscuitsec.biscuit.crypto.KeyPair.ed25519.getHashAlgorithm()));
            sgr.initSign(this.proof.secretKey.get().private_key);
            sgr.update(block);
            if(externalSignature.isDefined()) {
                sgr.update(externalSignature.get().signature);
            }
            sgr.update(algo_buf);
            sgr.update(next_key.toBytes());
            byte[] signature = sgr.sign();

            SignedBlock signedBlock = new SignedBlock(block, next_key, signature, externalSignature);

            ArrayList blocks = new ArrayList<>();
            for (SignedBlock bl : this.blocks) {
                blocks.add(bl);
            }
            blocks.add(signedBlock);

            Proof proof = new Proof(next);

            return Right(new SerializedBiscuit(this.authority, blocks, proof, root_key_id));
        } catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
            return Left(new Error.FormatError.SerializationError(e.toString()));
        }
    }

    public Either verify(org.biscuitsec.biscuit.crypto.PublicKey root) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        org.biscuitsec.biscuit.crypto.PublicKey current_key = root;
        ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
        {
            Either res = verifyBlockSignature(this.authority, current_key);
            if(res.isRight()) {
                current_key = res.get();
            } else {
                return Left(res.getLeft());
            }
        }

        for (SignedBlock b : this.blocks) {
            Either res = verifyBlockSignature(b, current_key);
            if(res.isRight()) {
                current_key = res.get();
            } else {
                return Left(res.getLeft());
            }
        }

        //System.out.println("signatures verified, checking proof");

        if (!this.proof.secretKey.isEmpty()) {
            //System.out.println("checking secret key");
            //System.out.println("current key: "+current_key.toHex());
            //System.out.println("key from proof: "+this.proof.secretKey.get().public_key().toHex());
            if (this.proof.secretKey.get().public_key().equals(current_key)) {
                //System.out.println("public keys are equal");

                return Right(null);
            } else {
                //System.out.println("public keys are not equal");

                return Left(new Error.FormatError.Signature.InvalidSignature("signature error: Verification equation was not satisfied"));
            }
        } else {
            //System.out.println("checking final signature");

            byte[] finalSignature = this.proof.signature.get();

            SignedBlock b;
            if (this.blocks.isEmpty()) {
                b = this.authority;
            } else {
                b = this.blocks.get(this.blocks.size() - 1);
            }

            byte[] block = b.block;
            org.biscuitsec.biscuit.crypto.PublicKey next_key = b.key;
            byte[] signature = b.signature;
            algo_buf.clear();
            algo_buf.putInt(next_key.algorithm.getNumber());
            algo_buf.flip();

            Signature sgr = new EdDSAEngine(MessageDigest.getInstance(org.biscuitsec.biscuit.crypto.KeyPair.ed25519.getHashAlgorithm()));

            sgr.initVerify(current_key.key);
            sgr.update(block);
            sgr.update(algo_buf);
            sgr.update(next_key.toBytes());
            sgr.update(signature);

            if (sgr.verify(finalSignature)) {
                return Right(null);
            } else {
                return Left(new Error.FormatError.Signature.SealedSignature());
            }

        }
    }

    static Either verifyBlockSignature(SignedBlock signedBlock, org.biscuitsec.biscuit.crypto.PublicKey publicKey)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {

        byte[] block = signedBlock.block;
        org.biscuitsec.biscuit.crypto.PublicKey next_key = signedBlock.key;
        byte[] signature = signedBlock.signature;
        if (signature.length != 64) {
            return Either.left(new Error.FormatError.Signature.InvalidSignatureSize(signature.length));
        }
        ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
        algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber()));
        algo_buf.flip();

        Signature sgr = KeyPair.generateSignature(publicKey.algorithm);

        sgr.initVerify(publicKey.key);
        sgr.update(block);
        if(signedBlock.externalSignature.isDefined()) {
            sgr.update(signedBlock.externalSignature.get().signature);
        }
        sgr.update(algo_buf);
        sgr.update(next_key.toBytes());
        if (!sgr.verify(signature)) {
            return Left(new Error.FormatError.Signature.InvalidSignature("signature error: Verification equation was not satisfied"));
        }

        if(signedBlock.externalSignature.isDefined()) {
            ByteBuffer algo_buf2 = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
            algo_buf2.putInt(Integer.valueOf(publicKey.algorithm.getNumber()));
            algo_buf2.flip();

            Signature sgr2 = new EdDSAEngine(MessageDigest.getInstance(KeyPair.ed25519.getHashAlgorithm()));
            sgr2.initVerify(signedBlock.externalSignature.get().key.key);
            sgr2.update(block);
            sgr2.update(algo_buf2);
            sgr2.update(publicKey.toBytes());

            if (!sgr2.verify(signedBlock.externalSignature.get().signature)) {
                return Left(new Error.FormatError.Signature.InvalidSignature("external signature error: Verification equation was not satisfied"));
            }
        }

        return Right(next_key);
    }

    public Tuple2> extractBlocks(SymbolTable symbols) throws Error {
        ArrayList> blockExternalKeys = new ArrayList<>();
        Either authRes = Block.from_bytes(this.authority.block, Option.none());
        if (authRes.isLeft()) {
            throw authRes.getLeft();
        }
        Block authority = authRes.get();
        for(org.biscuitsec.biscuit.crypto.PublicKey pk: authority.publicKeys()) {
            symbols.insert(pk);
        }
        blockExternalKeys.add(Option.none());

        for (String s : authority.symbols().symbols) {
            symbols.add(s);
        }

        ArrayList blocks = new ArrayList<>();
        for (SignedBlock bdata : this.blocks) {
            Option externalKey = Option.none();
            if(bdata.externalSignature.isDefined()) {
                externalKey = Option.some(bdata.externalSignature.get().key);
            }
            Either blockRes = Block.from_bytes(bdata.block, externalKey);
            if (blockRes.isLeft()) {
                throw blockRes.getLeft();
            }
            Block block = blockRes.get();

            // blocks with external signatures keep their own symbol table
            if(bdata.externalSignature.isDefined()) {
                //symbols.insert(bdata.externalSignature.get().key);
                blockExternalKeys.add(Option.some(bdata.externalSignature.get().key));
            } else {
                blockExternalKeys.add(Option.none());
                for (String s : block.symbols().symbols) {
                    symbols.add(s);
                }
                for(org.biscuitsec.biscuit.crypto.PublicKey pk: block.publicKeys()) {
                    symbols.insert(pk);
                }
            }

            blocks.add(block);
        }

        return new Tuple2<>(authority, blocks);
    }

    public Either seal() throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
        if (this.proof.secretKey.isEmpty()) {
            return Left(new Error.Sealed());
        }

        SignedBlock block;
        if (this.blocks.isEmpty()) {
            block = this.authority;
        } else {
            block = this.blocks.get(this.blocks.size() - 1);
        }

        Signature sgr = new EdDSAEngine(MessageDigest.getInstance(KeyPair.ed25519.getHashAlgorithm()));
        ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
        algo_buf.putInt(Integer.valueOf(block.key.algorithm.getNumber()));
        algo_buf.flip();


        sgr.initSign(this.proof.secretKey.get().private_key);
        sgr.update(block.block);
        sgr.update(algo_buf);
        sgr.update(block.key.toBytes());
        sgr.update(block.signature);

        byte[] signature = sgr.sign();

        this.proof.secretKey = Option.none();
        this.proof.signature = Option.some(signature);

        return Right(null);
    }

    public List revocation_identifiers() {
        ArrayList l = new ArrayList<>();
        l.add(this.authority.signature);

        for (SignedBlock block : this.blocks) {
            l.add(block.signature);
        }
        return l;
    }

    SerializedBiscuit(SignedBlock authority, List blocks, Proof proof) {
        this.authority = authority;
        this.blocks = blocks;
        this.proof = proof;
        this.root_key_id = Option.none();
    }

    SerializedBiscuit(SignedBlock authority, List blocks, Proof proof, Option root_key_id) {
        this.authority = authority;
        this.blocks = blocks;
        this.proof = proof;
        this.root_key_id = root_key_id;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy