hebdobot/src/org/april/hebdobot/privatebin/PrivatebinClient.java

254 lines
10 KiB
Java

/**
* Copyright (C) 2017-2021 Christian Pierre MOMON <cmomon@april.org>
* Copyright (C) 2011-2013 Nicolas Vinot <aeris@imirhil.fr>
*
* 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 <http://www.gnu.org/licenses/>
*/
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;
}
}