302 lines
12 KiB
Java
302 lines
12 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.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<NameValuePair> params, final String name, final String value)
|
|
{
|
|
if (value != null)
|
|
{
|
|
params.add(new BasicNameValuePair(name, value));
|
|
}
|
|
}
|
|
}
|