com.google.nigori.server.DatabaseNigoriProtocol.java Source code

Java tutorial

Introduction

Here is the source code for com.google.nigori.server.DatabaseNigoriProtocol.java

Source

/*
 * Copyright (C) 2012 Daniel R. Thomas (drt24)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.nigori.server;

import static com.google.nigori.common.MessageLibrary.toBytes;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.codec.binary.Base64;

import com.google.nigori.common.DSASignature;
import com.google.nigori.common.DSAVerify;
import com.google.nigori.common.MessageLibrary;
import com.google.nigori.common.NigoriMessages.AuthenticateRequest;
import com.google.nigori.common.NigoriMessages.DeleteRequest;
import com.google.nigori.common.NigoriMessages.GetIndicesRequest;
import com.google.nigori.common.NigoriMessages.GetIndicesResponse;
import com.google.nigori.common.NigoriMessages.GetRequest;
import com.google.nigori.common.NigoriMessages.GetResponse;
import com.google.nigori.common.NigoriMessages.GetRevisionsRequest;
import com.google.nigori.common.NigoriMessages.GetRevisionsResponse;
import com.google.nigori.common.NigoriMessages.PutRequest;
import com.google.nigori.common.NigoriMessages.RegisterRequest;
import com.google.nigori.common.NigoriMessages.UnregisterRequest;
import com.google.nigori.common.NigoriProtocol;
import com.google.nigori.common.Nonce;
import com.google.nigori.common.NotFoundException;
import com.google.nigori.common.RevValue;
import com.google.nigori.common.UnauthorisedException;
import com.google.nigori.common.Util;

/**
 * Take messages from the {@link NigoriProtocol} and translate them to the {@link Database} if they
 * are valid
 * 
 * @author drt24
 * 
 */
public class DatabaseNigoriProtocol implements NigoriProtocol {

    private static final Logger log = Logger.getLogger(DatabaseNigoriProtocol.class.getSimpleName());

    private static void warning(String message, Exception exception) {
        log.log(Level.WARNING, message, exception);
    }

    private static void severe(String message, Exception exception) {
        log.log(Level.SEVERE, message, exception);
    }

    private final Database database;

    public DatabaseNigoriProtocol(Database database) {
        this.database = database;
    }

    /**
     * Check {@code nonce} is valid. If so, this method returns; else throws a ServletException
     * 
     * @param schnorrS
     * @param schnorrE
     * @param message
     * @throws ServletException
     */
    private User authenticateUser(AuthenticateRequest auth, String command, byte[]... payload)
            throws UnauthorisedException, CryptoException {

        byte[] publicHash = auth.getPublicKey().toByteArray();
        List<byte[]> byteSig = Util.splitBytes(auth.getSig().toByteArray());
        byte[] dsaR = byteSig.get(0);
        byte[] dsaS = byteSig.get(1);
        Nonce nonce = new Nonce(auth.getNonce().toByteArray());
        String serverName = auth.getServerName();
        try {
            byte[] publicKey = database.getPublicKey(publicHash);

            DSASignature sig = new DSASignature(dsaR, dsaS, Util.joinBytes(toBytes(serverName), nonce.nt(),
                    nonce.nr(), toBytes(command), Util.joinBytes(payload)));
            try {
                DSAVerify v = new DSAVerify(publicKey);

                if (v.verify(sig)) {
                    boolean validNonce = database.checkAndAddNonce(nonce, publicHash);

                    boolean userExists = database.haveUser(publicHash);
                    if (validNonce && userExists) {
                        try {
                            return database.getUser(publicHash);
                        } catch (UserNotFoundException e) {
                            // TODO(drt24): potential security vulnerability - user existence oracle.
                            // Should not happen often - is only possible due to concurrency
                            throw new UnauthorisedException("No such user");
                        }
                    } else {
                        throw new UnauthorisedException("Invalid nonce or no such user");
                    }
                } else {
                    throw new UnauthorisedException("The signature is invalid");
                }
            } catch (NoSuchAlgorithmException nsae) {
                severe("authenticateUser", nsae);
                throw new CryptoException("Internal error attempting to verify signature");
            }
        } catch (UserNotFoundException e1) {
            warning("authenticateUser", e1);
            throw new UnauthorisedException("No such user");
        }
    }

    @Override
    public boolean authenticate(AuthenticateRequest request) throws IOException {
        try {
            authenticateUser(request, MessageLibrary.REQUEST_AUTHENTICATE);
        } catch (UnauthorisedException e) {
            warning("unauthorized authenticate", e);
            return false;
        }
        return true;
    }

    @Override
    public boolean register(RegisterRequest request) throws IOException {
        // TODO(drt24): validate request.getToken()
        byte[] publicKey = request.getPublicKey().toByteArray();
        try {
            return database.addUser(publicKey, Util.hashKey(publicKey));
        } catch (NoSuchAlgorithmException e) {
            throw new IOException(e);
        }
    }

    @Override
    public boolean unregister(UnregisterRequest request) throws IOException, UnauthorisedException {
        AuthenticateRequest auth = request.getAuth();
        User user = authenticateUser(auth, MessageLibrary.REQUEST_UNREGISTER);

        return database.deleteUser(user);
    }

    @Override
    public GetResponse get(GetRequest request) throws IOException, NotFoundException, UnauthorisedException {
        byte[] index = request.getKey().toByteArray();
        AuthenticateRequest auth = request.getAuth();
        User user;
        byte[] revision = null;
        if (request.hasRevision()) {
            revision = request.getRevision().toByteArray();
            user = authenticateUser(auth, MessageLibrary.REQUEST_GET, index, revision);
        } else {
            user = authenticateUser(auth, MessageLibrary.REQUEST_GET, index);
        }

        Collection<RevValue> value;

        if (request.hasRevision()) {

            value = new ArrayList<RevValue>(1);
            RevValue revVal = database.getRevision(user, index, revision);
            if (revVal == null) {
                throw new NotFoundException("Cannot find requested index with revision");
            }
            value.add(revVal);
        } else {
            value = database.getRecord(user, index);
        }
        if (value == null) {
            throw new NotFoundException("No value for that index");
        }
        return MessageLibrary.getResponseAsProtobuf(value);
    }

    @Override
    public GetIndicesResponse getIndices(GetIndicesRequest request)
            throws IOException, NotFoundException, UnauthorisedException {
        AuthenticateRequest auth = request.getAuth();
        User user = authenticateUser(auth, MessageLibrary.REQUEST_GET_INDICES);

        Collection<byte[]> value = database.getIndices(user);

        if (value == null) {
            throw new NotFoundException("Cannot find indices");
        }
        return MessageLibrary.getIndicesResponseAsProtobuf(value);
    }

    @Override
    public GetRevisionsResponse getRevisions(GetRevisionsRequest request)
            throws IOException, NotFoundException, UnauthorisedException {
        byte[] index = request.getKey().toByteArray();
        AuthenticateRequest auth = request.getAuth();
        User user = authenticateUser(auth, MessageLibrary.REQUEST_GET_REVISIONS, index);

        Collection<byte[]> value = database.getRevisions(user, index);

        if (value == null) {
            throw new NotFoundException("Cannot find requested key");
        }
        return MessageLibrary.getRevisionsResponseAsProtobuf(value);
    }

    @Override
    public boolean put(PutRequest request) throws IOException, UnauthorisedException {
        AuthenticateRequest auth = request.getAuth();

        byte[] index = request.getKey().toByteArray();
        byte[] revision = request.getRevision().toByteArray();
        byte[] value = request.getValue().toByteArray();
        User user = authenticateUser(auth, MessageLibrary.REQUEST_PUT, index, revision, value);

        return database.putRecord(user, index, revision, value);
    }

    @Override
    public boolean delete(DeleteRequest request) throws IOException, NotFoundException, UnauthorisedException {
        AuthenticateRequest auth = request.getAuth();

        byte[] index = request.getKey().toByteArray();
        User user = authenticateUser(auth, MessageLibrary.REQUEST_DELETE, index);

        boolean exists = database.getRecord(user, index) != null;
        if (!exists) {
            throw new NotFoundException("No such index: " + Base64.encodeBase64(request.getKey().toByteArray()));
        }

        return database.deleteRecord(user, index);
    }

    public static class CryptoException extends IOException {
        private static final long serialVersionUID = 1L;

        public CryptoException(String message) {
            super(message);
        }
    }
}