/** * 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.List; 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.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.message.BasicNameValuePair; import org.april.hebdobot.HebdobotException; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import fr.devinsy.strings.StringList; /** * The Class PastebinClient. */ public class PrivatebinClient { private String serverUrl; private Compression compression; /** * Instantiates a new privatebin client. * * @param serverUrl * the server url */ public PrivatebinClient(final String serverUrl) { this.serverUrl = serverUrl; this.compression = Compression.ZLIB; } /** * Paste. * * @param serverUrl * the server url * @param expirationDate * the expiration * @param burnAfterReading * the burn after reading * @param openDiscussion * the open discussion * @return the string * @throws HebdobotException * the hebdobot exception * @throws PrivatebinException */ public String paste(final String text, final Expiration expiration, final Formatter formatter, final BurnAfterReading burnAfterReading, final OpenDiscussion openDiscussion) throws PrivatebinException { String result; result = null; try { if (expiration == null) { throw new IllegalArgumentException("Null parameter expiration."); } else if (formatter == null) { throw new IllegalArgumentException("Null parameter formatter."); } else if (burnAfterReading == null) { throw new IllegalArgumentException("Null parameter burn after reading."); } else if (openDiscussion == null) { throw new IllegalArgumentException("Null parameter open discussion."); } else { // 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 // Optional parameters // =================== // Location of local file to attach, leave quotes empty for no // file String localAttachmentFilename = ""; // Paste attachment name, leave quotes empty for no file String pasteAttachmentFilename = ""; // Read file contents into paste, leave quotes empty no no file String plaintextFileLocation = ""; // Set paste password, leave quotes empty for no password String userPastePassword = ""; // // Generate password. KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(192); String randomPassword = Base64.getEncoder().encodeToString(keyGen.generateKey().getEncoded()); String customPassword = randomPassword + userPastePassword; // 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); // Build message to encrypt. String pasteData = "{\"paste\":\"" + text + "\"}"; // Compression. byte[] pasteDataBytes; if (StringUtils.equals(this.compression.toString(), "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 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).append("\""); gcmTagString.append("]"); gcmTagString.append(","); gcmTagString.append("\"").append(formatter).append("\"").append(","); gcmTagString.append(openDiscussion).append(","); gcmTagString.append(burnAfterReading); gcmTagString.append("]"); byte[] gcmBytes = gcmTagString.toString().getBytes(); System.out.println("gcm=" + gcmTagString.toString()); // 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); // 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(expiration).append("\"}"); payload.append("}"); // POST Request. HttpsURLConnection pasteRequest = (HttpsURLConnection) new URL(this.serverUrl).openConnection(); pasteRequest.setRequestMethod("POST"); pasteRequest.setDoOutput(true); pasteRequest.setRequestProperty("X-Requested-With", "JSONHttpRequest"); pasteRequest.getOutputStream().write(payload.toString().getBytes()); System.out.println("PAYLOAD=" + payload.toString()); // Server response. int responseCode = pasteRequest.getResponseCode(); System.out.println("Server response: " + responseCode); if (responseCode == 200) { String out = IOUtils.toString(pasteRequest.getInputStream(), "UTF-8"); System.out.println("===> " + 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; System.out.println("SUCESS"); System.out.println("Paste URL: " + finalURL); System.out.println("Delete URL: " + deleteURL); } else { String output = parser.get("message").toString(); System.out.println("message=" + output); throw new PrivatebinException(out); } } } } catch (BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException | InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | ParseException | UnsupportedEncodingException | ClientProtocolException exception) { throw new PrivatebinException(exception); } catch (IOException exception) { exception.printStackTrace(); throw new PrivatebinException(exception); } // return result; } /** * Sets the parameter. * * @param params * the params * @param name * the name * @param value * the value */ private static void setParameter(final List params, final String name, final String value) { if (value != null) { params.add(new BasicNameValuePair(name, value)); } } }