/** * Copyright (C) 2017-2021 Christian Pierre MOMON * Copyright (C) 2011-2013 Nicolas Vinot * * This file is part of (April) Hebdobot. * * Hebdobot is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Hebdobot is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Hebdobot. If not, see */ package org.april.hebdobot.privatebin; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Arrays; import java.util.Base64; import java.util.Random; import java.util.zip.Deflater; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.HttpsURLConnection; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.ClientProtocolException; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fr.devinsy.strings.StringList; /** * The Class PastebinClient. */ public class PrivatebinClient extends PrivatebinSettings { private static final Logger logger = LoggerFactory.getLogger(PrivatebinClient.class); /** * Instantiates a new privatebin client. * * @param settings * the settings */ public PrivatebinClient(final PrivatebinSettings settings) { super(settings); } /** * Instantiates a new privatebin client. * * @param url * the url */ public PrivatebinClient(final URL url) { super(); setServerUrl(url); } /** * Paste. * * @param text * the text * @return the string * @throws PrivatebinException * the privatebin exception */ public String paste(final String text) throws PrivatebinException { String result; result = null; try { // Note: the following code is based on: // https://github.com/PrivateBin/PrivateBin/wiki/API // https://github.com/kkingsley-BF/PrivateBin-Groovy/blob/master/Paste.groovy // Build message to encrypt. JSONObject pasteDataJson = new JSONObject(); pasteDataJson.put("paste", text); String pasteData = pasteDataJson.toJSONString(); // logger.debug("pasteData={}", pasteData); // Compression. byte[] pasteDataBytes; if (this.compression == Compression.ZLIB) { Deflater zipDeflater = new Deflater(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); zipDeflater.setInput(pasteData.getBytes()); zipDeflater.finish(); byte[] buffer = new byte[1024]; while (!zipDeflater.finished()) { int count = zipDeflater.deflate(buffer); stream.write(buffer, 0, count); } byte[] output; output = stream.toByteArray(); stream.close(); zipDeflater.end(); // Need to remove the header pasteDataBytes = Arrays.copyOfRange(output, 2, output.length - 4); } else { pasteDataBytes = pasteData.getBytes(); } // Generate password. KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(192); String randomPassword = Base64.getEncoder().encodeToString(keyGen.generateKey().getEncoded()); String customPassword = randomPassword + this.password; // Generate IV. byte[] cipherIVBytes = new byte[16]; new Random().nextBytes(cipherIVBytes); String cipherIVEncoded = Base64.getEncoder().encodeToString(cipherIVBytes); // Generate salt. byte[] kdfSaltBytes = new byte[8]; new Random().nextBytes(kdfSaltBytes); String kdfSaltEncoded = Base64.getEncoder().encodeToString(kdfSaltBytes); // Generate secret key for cipher. SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec passwordBasedEncryptionKeySpec = new PBEKeySpec(customPassword.toCharArray(), kdfSaltBytes, 100000, 256); SecretKey secret = new SecretKeySpec(factory.generateSecret(passwordBasedEncryptionKeySpec).getEncoded(), "AES"); // Cipher AAD. StringList gcmTagString = new StringList(); gcmTagString.append("["); gcmTagString.append("["); gcmTagString.append("\"").append(cipherIVEncoded).append("\"").append(","); gcmTagString.append("\"").append(kdfSaltEncoded).append("\"").append(","); gcmTagString.append("100000,256,128,"); gcmTagString.append("\"").append("aes").append("\"").append(","); gcmTagString.append("\"").append("gcm").append("\"").append(","); gcmTagString.append("\"").append(this.compression.toString()).append("\""); gcmTagString.append("]"); gcmTagString.append(","); gcmTagString.append("\"").append(this.formatter.toString()).append("\"").append(","); gcmTagString.append(this.openDiscussion.toString()).append(","); gcmTagString.append(this.burnAfterReading.toString()); gcmTagString.append("]"); byte[] gcmBytes = gcmTagString.toString().getBytes(); // Generate cipher text. Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec spec = new GCMParameterSpec(128, cipherIVBytes); cipher.init(Cipher.ENCRYPT_MODE, secret, spec); cipher.updateAAD(gcmBytes); byte[] cipherTextBytes = cipher.doFinal(pasteDataBytes); String cipherText = Base64.getEncoder().encodeToString(cipherTextBytes); // logger.debug("cipherText={}", cipherText); // Create POST payload. StringList payload = new StringList(); payload.append("{"); payload.append("\"v\":2,"); payload.append("\"adata\":").append(gcmTagString.toString()).append(","); payload.append("\"ct\":\"").append(cipherText).append("\","); payload.append("\"meta\":{\"expire\":\"").append(this.expiration).append("\"}"); payload.append("}"); // logger.debug("PAYLOAD={}", payload.toString()); // POST Request. HttpsURLConnection pasteRequest = (HttpsURLConnection) this.serverUrl.openConnection(); pasteRequest.setRequestMethod("POST"); pasteRequest.setDoOutput(true); pasteRequest.setRequestProperty("X-Requested-With", "JSONHttpRequest"); pasteRequest.getOutputStream().write(payload.toString().getBytes()); // Server response. int responseCode = pasteRequest.getResponseCode(); logger.debug("Server response: {}", responseCode); if (responseCode == 200) { String out = IOUtils.toString(pasteRequest.getInputStream(), "UTF-8"); logger.info("===> {}", out); JSONObject parser = (JSONObject) new JSONParser().parse(out); String status = parser.get("status").toString(); if (StringUtils.equals(status, "0")) { String id = parser.get("id").toString(); String pasteURL = parser.get("url").toString(); String deleteToken = parser.get("deletetoken").toString(); String finalURL = this.serverUrl + pasteURL + "#" + Base58.encode(randomPassword.getBytes()); String deleteURL = this.serverUrl + pasteURL + "&deletetoken=" + deleteToken; logger.info("Pastebin SUCESS"); logger.debug("Paste URL: {}", finalURL); logger.debug("Delete URL: {}", deleteURL); result = finalURL; } else { String output = parser.get("message").toString(); logger.warn("message={}", output); throw new PrivatebinException(out); } } } catch (BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException | InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | ParseException | UnsupportedEncodingException | ClientProtocolException exception) { exception.printStackTrace(); throw new PrivatebinException(exception); } catch (IOException exception) { exception.printStackTrace(); throw new PrivatebinException(exception); } // return result; } }