package de.tum.in.tumcampusapp.auxiliary; import android.util.Base64; import com.google.common.base.Charsets; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.List; import de.tum.in.tumcampusapp.models.tumcabe.ChatMessage; import de.tum.in.tumcampusapp.models.tumcabe.ChatPublicKey; /** * A class allowing a convenient API to check the validity of ChatMessages based * on a set of known public keys. * <p/> * The public keys against which the messages are to be validated need to be injected * at construct-time. */ public class ChatMessageValidator { /** * A list of public keys to be used to validate given chat messages. */ private final List<ChatPublicKey> chatPublicKeys; /** * A list holding the given chat public keys converted to their {@link PublicKey} * instance. * <p/> * This list is constructed lazily, i.e. at the point when it is first required. */ private List<PublicKey> publicKeys; /** * Constructs a new validator for the given public keys. * * @param publicKeys public key */ public ChatMessageValidator(List<ChatPublicKey> publicKeys) { this.chatPublicKeys = publicKeys; } /** * Private helper method which performs the actual validation of a signature * attached to a particular text, given a {@link PublicKey} * * @param text The text the signature has signed * @param signature The signature encoded as a base64 string * @param key a {@link PublicKey} instance to use to verify the signature * @return Returns true if signature is valid */ private static boolean verifySignature(String text, String signature, PublicKey key) { Signature sig = RSASigner.getSignatureInstance(); try { sig.initVerify(key); } catch (InvalidKeyException e) { Utils.log(e); return false; } byte[] textBytes = text.getBytes(Charsets.UTF_8); try { sig.update(textBytes); } catch (SignatureException e) { Utils.log(e); return false; } try { return sig.verify(decodeByteRepresentation(signature)); } catch (SignatureException e) { Utils.log(e); return false; } } /** * Private helper method which converts the public key representation returned by the * TCA endpoint to a {@link PublicKey} instance. * * @param chatPublicKey A model representing a TCA public key * @return {@link PublicKey} which corresponds to the given TCA public key */ private static PublicKey convertToPublicKey(ChatPublicKey chatPublicKey) { // Base64 string -> Bytes byte[] keyBytes = decodeByteRepresentation(chatPublicKey.getKey()); if (keyBytes == null) { return null; } KeyFactory keyFactory = AuthenticationManager.getKeyFactoryInstance(); // Bytes -> PublicKey try { return keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes)); } catch (InvalidKeySpecException e) { Utils.log(e); return null; } } /** * Private helper method decoding the given key text from its Base64 encoded * representation into an array of bytes. * * @param text The text to be decoded as a String * @return An array of bytes representing the given text */ private static byte[] decodeByteRepresentation(String text) { if (text == null) { return null; } return Base64.decode(text.trim(), Base64.DEFAULT); } /** * Perform the validation of the signature attached to the given chat message * instance. * <p/> * The validation is performed against the list of public keys given to the validator * instance at construction-time. * * @param message The message to be validated * @return True if the signature is valid, False otherwise */ public boolean validate(ChatMessage message) { if (publicKeys == null) { this.generatePublicKeys(); } // Generate hash of the received message String text = message.getText(); String signature = message.getSignature(); // Try validating the message using any of the known public keys // If any of them succeed, the message is valid for (PublicKey key : publicKeys) { if (verifySignature(text, signature, key)) { return true; } } return false; } /** * Private helper method which builds a list of valid {@link PublicKey} * instances based on the given list of TCA public keys. */ private void generatePublicKeys() { publicKeys = new ArrayList<>(); for (ChatPublicKey chatPublicKey : chatPublicKeys) { PublicKey key = convertToPublicKey(chatPublicKey); if (key != null) { publicKeys.add(key); } } } }