Java tutorial
/* * 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); } } }