package com.fosspowered.peist.service; import com.fosspowered.peist.model.dao.Paste; import com.fosspowered.peist.model.exceptions.PeistAccessDeniedException; import com.fosspowered.peist.model.exceptions.PeistInternalException; import com.fosspowered.peist.model.exceptions.PeistInvalidRequestException; import com.fosspowered.peist.model.exceptions.PeistNotFoundException; import com.fosspowered.peist.model.json.PasteRequest; import com.fosspowered.peist.model.json.PasteResponse; import com.fosspowered.peist.repository.PasteRepository; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.Date; import java.util.ArrayList; import java.util.Base64; import java.util.Calendar; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service @Log4j2 public class PasteService { private final PasteRepository pasteRepository; private final String systemKey; PasteService(PasteRepository pasteRepository, @Value("${system-key}") String systemKey) { this.pasteRepository = pasteRepository; this.systemKey = systemKey; } public PasteResponse addPaste(PasteRequest request) { Paste paste = buildPasteData(request); this.pasteRepository.save(paste); return buildPasteResponse(paste); } private String convertToCipher(Date creationDate, String key, String text) { SecretKeySpec keySpec = buildSpec(creationDate, key); try { Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] cipherBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(cipherBytes); } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException e) { throw new RuntimeException(e); } } private String convertFromCipher(Date creationDate, String key, String cipherText) { SecretKeySpec keySpec = buildSpec(creationDate, key); try { Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, keySpec); return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText))); } catch (BadPaddingException e) { throw new PeistAccessDeniedException("Access denied for paste."); } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException e) { log.error("Error while processing", e); throw new PeistInternalException("Error while processing"); } } private SecretKeySpec buildSpec(Date creationDate, String key) { String keyStr = String.format( "%s%s%s", systemKey, StringUtils.isNotBlank(key) ? key : "", creationDate.getTime()); try { MessageDigest sha = MessageDigest.getInstance("SHA-256"); return new SecretKeySpec(sha.digest(keyStr.getBytes(StandardCharsets.UTF_8)), "AES"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } public PasteResponse fetchPaste(String urlId) { return fetchPaste(urlId, null); } public PasteResponse fetchPaste(String urlId, String key) { Paste paste = fetchPasteFromDB(urlId); String text = convertFromCipher(paste.getCreationDate(), key, paste.getPasteData()); paste.setPasteData(text); return buildPasteResponse(paste); } private Paste fetchPasteFromDB(String urlId) { if (StringUtils.isBlank(urlId)) { throw new PeistInvalidRequestException("Invalid request as urlId is invalid: " + urlId); } Optional paste = this.pasteRepository.findByUrlId(urlId); if (paste.isPresent()) { return paste.get(); } else { throw new PeistNotFoundException("Paste not found for urlId: " + urlId); } } public List fetchRecentPastes() { List pasteList = new ArrayList<>(this.pasteRepository.findAll()); return pasteList.stream().map(this::buildPasteResponse).collect(Collectors.toList()); } private PasteResponse buildPasteResponse(Paste paste) { return PasteResponse.builder() .urlId(paste.getUrlId()) .creationDate(paste.getCreationDate()) .author(paste.getAuthor()) .language(paste.getLanguage()) .title(paste.getTitle()) .expiryDate(paste.getExpiryDate()) .isVisible(paste.getIsVisible()) .pasteData(paste.getPasteData()) .build(); } private Paste buildPasteData(PasteRequest pasteRequest) { Calendar now = Calendar.getInstance(); Date creationDate = new Date(now.getTime().getTime()); now.add(Calendar.SECOND, pasteRequest.getTtl()); Date expiryDate = new Date(now.getTime().getTime()); String secretKey = pasteRequest.getSecretKey(); boolean isVisible = StringUtils.isNotBlank(pasteRequest.getSecretKey()); String text = convertToCipher(creationDate, secretKey, pasteRequest.getPasteData()); Paste paste = new Paste(); paste.setUrlId(UUID.randomUUID().toString()); paste.setCreationDate(creationDate); paste.setTitle(pasteRequest.getTitle()); paste.setAuthor(pasteRequest.getAuthor()); paste.setLanguage(pasteRequest.getLanguage()); paste.setExpiryDate(expiryDate); paste.setIsVisible(isVisible); paste.setPasteData(text); return paste; } }